こんにちは!今回は、PHPの標準関数であるsession_create_id()について詳しく解説していきます。カスタムセッションIDを生成できる、セッション管理の柔軟性を高める関数です!
session_create_id関数とは?
session_create_id()関数は、新しいセッションIDを生成する関数です。
PHP 7.1.0で追加された関数で、セッションIDにプレフィックスを付けることができます。これにより、複数のアプリケーション間でセッションを分離したり、セッションIDの構造を制御したりできます!
基本的な構文
session_create_id(string $prefix = ""): string|false
- $prefix: セッションIDのプレフィックス(省略可)
- 戻り値: 新しいセッションID、失敗時は
false
重要な注意点
// プレフィックスに使用できる文字
// 英数字、カンマ(,)、ハイフン(-)のみ
// 正しい例
$id1 = session_create_id('app1-'); // OK
$id2 = session_create_id('user123,'); // OK
$id3 = session_create_id('tenant-001-'); // OK
// 不正な例(警告が発生)
// $id4 = session_create_id('app_1'); // アンダースコア不可
// $id5 = session_create_id('app.1'); // ドット不可
// $id6 = session_create_id('app/1'); // スラッシュ不可
// セッションIDの長さ
// session.sid_length(デフォルト32文字)+ プレフィックスの長さ
// セッション開始前に使用
$newId = session_create_id('myapp-');
session_id($newId); // 生成したIDを設定
session_start(); // セッション開始
セッションIDの形式
// プレフィックスなし
$id = session_create_id();
echo $id; // 例: a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
// プレフィックスあり
$id = session_create_id('app-');
echo $id; // 例: app-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
// 長さの確認
$id = session_create_id('prefix-');
echo strlen($id); // プレフィックス長 + session.sid_length
基本的な使用例
シンプルなセッションID生成
// 新しいセッションIDを生成
$newId = session_create_id();
echo "生成されたID: {$newId}\n";
echo "ID長: " . strlen($newId) . "\n";
// セッションIDとして使用
session_id($newId);
session_start();
echo "現在のセッションID: " . session_id() . "\n";
プレフィックス付きセッションID
// アプリケーション識別子をプレフィックスとして追加
$appId = session_create_id('myapp-');
echo "アプリケーションID: {$appId}\n";
session_id($appId);
session_start();
$_SESSION['data'] = 'test';
session_write_close();
複数アプリケーションでの分離
// アプリケーション1
function startApp1Session() {
$id = session_create_id('app1-');
session_id($id);
session_start();
return session_id();
}
// アプリケーション2
function startApp2Session() {
$id = session_create_id('app2-');
session_id($id);
session_start();
return session_id();
}
// 使用例
$app1Id = startApp1Session();
echo "App1 セッションID: {$app1Id}\n";
session_write_close();
$app2Id = startApp2Session();
echo "App2 セッションID: {$app2Id}\n";
セッションIDの再生成
// 既存のセッション
session_start();
$oldId = session_id();
echo "古いID: {$oldId}\n";
// 新しいIDを生成して置き換え
$newId = session_create_id('renewed-');
session_id($newId);
// セッションデータを保持したまま新しいIDで再開
$sessionData = $_SESSION;
session_destroy();
session_id($newId);
session_start();
$_SESSION = $sessionData;
echo "新しいID: " . session_id() . "\n";
実践的な使用例
例1: マルチテナントセッション管理
class MultiTenantSessionManager {
private $tenantId;
private $sessionPrefix;
/**
* マルチテナントセッションマネージャーを初期化
*/
public function __construct($tenantId) {
$this->tenantId = $tenantId;
$this->sessionPrefix = $this->generatePrefix($tenantId);
}
/**
* テナントIDからプレフィックスを生成
*/
private function generatePrefix($tenantId) {
// テナントIDをハッシュ化して短縮
$hash = substr(md5($tenantId), 0, 8);
return "t{$hash}-";
}
/**
* テナント固有のセッションを開始
*/
public function startSession() {
// カスタムセッションIDを生成
$sessionId = session_create_id($this->sessionPrefix);
if ($sessionId === false) {
throw new Exception("Failed to create session ID");
}
// セッションIDを設定
session_id($sessionId);
// セッション開始
session_start();
// テナント情報を記録
if (!isset($_SESSION['tenant_id'])) {
$_SESSION['tenant_id'] = $this->tenantId;
$_SESSION['session_created_at'] = time();
}
return [
'session_id' => session_id(),
'tenant_id' => $this->tenantId,
'prefix' => $this->sessionPrefix
];
}
/**
* テナントIDを検証
*/
public function validateTenant() {
if (!isset($_SESSION['tenant_id'])) {
return false;
}
return $_SESSION['tenant_id'] === $this->tenantId;
}
/**
* セッションIDからテナントを識別
*/
public static function getTenantFromSessionId($sessionId) {
// プレフィックスを抽出
if (preg_match('/^t([a-f0-9]{8})-/', $sessionId, $matches)) {
return [
'prefix' => $matches[0],
'hash' => $matches[1]
];
}
return null;
}
/**
* テナント情報を取得
*/
public function getTenantInfo() {
if (session_status() !== PHP_SESSION_ACTIVE) {
return null;
}
return [
'tenant_id' => $_SESSION['tenant_id'] ?? null,
'session_id' => session_id(),
'prefix' => $this->sessionPrefix,
'created_at' => $_SESSION['session_created_at'] ?? null,
'is_valid' => $this->validateTenant()
];
}
/**
* すべてのテナントセッションをリスト
*/
public static function listTenantSessions($sessionPath) {
$sessions = [];
if (!is_dir($sessionPath)) {
return $sessions;
}
$files = scandir($sessionPath);
foreach ($files as $file) {
if ($file === '.' || $file === '..') {
continue;
}
// セッションファイル名からIDを抽出
if (preg_match('/^sess_(.+)$/', $file, $matches)) {
$sessionId = $matches[1];
$tenantInfo = self::getTenantFromSessionId($sessionId);
if ($tenantInfo) {
$sessions[] = [
'session_id' => $sessionId,
'tenant_hash' => $tenantInfo['hash'],
'file' => $file,
'size' => filesize($sessionPath . '/' . $file),
'modified' => filemtime($sessionPath . '/' . $file)
];
}
}
}
return $sessions;
}
}
// 使用例
echo "=== マルチテナントセッション管理 ===\n";
// テナント1のセッション
echo "\nテナント1:\n";
$tenant1 = new MultiTenantSessionManager('tenant-001');
$info1 = $tenant1->startSession();
echo " セッションID: {$info1['session_id']}\n";
echo " テナントID: {$info1['tenant_id']}\n";
echo " プレフィックス: {$info1['prefix']}\n";
$_SESSION['tenant1_data'] = 'テナント1のデータ';
session_write_close();
// テナント2のセッション
echo "\nテナント2:\n";
$tenant2 = new MultiTenantSessionManager('tenant-002');
$info2 = $tenant2->startSession();
echo " セッションID: {$info2['session_id']}\n";
echo " テナントID: {$info2['tenant_id']}\n";
echo " プレフィックス: {$info2['prefix']}\n";
$_SESSION['tenant2_data'] = 'テナント2のデータ';
// テナント情報を確認
$tenantInfo = $tenant2->getTenantInfo();
echo "\nテナント2の詳細情報:\n";
echo " 有効: " . ($tenantInfo['is_valid'] ? 'Yes' : 'No') . "\n";
echo " 作成日時: " . date('Y-m-d H:i:s', $tenantInfo['created_at']) . "\n";
session_write_close();
// セッションIDからテナントを識別
echo "\nセッションIDからテナント識別:\n";
$extracted = MultiTenantSessionManager::getTenantFromSessionId($info1['session_id']);
echo " テナント1のハッシュ: {$extracted['hash']}\n";
例2: セッションIDプレフィックス管理システム
class SessionPrefixManager {
private static $prefixRegistry = [];
/**
* プレフィックスを登録
*/
public static function registerPrefix($name, $prefix) {
// プレフィックスの検証
if (!self::isValidPrefix($prefix)) {
throw new Exception("Invalid prefix: {$prefix}");
}
self::$prefixRegistry[$name] = [
'prefix' => $prefix,
'registered_at' => time(),
'usage_count' => 0
];
return true;
}
/**
* プレフィックスが有効か検証
*/
private static function isValidPrefix($prefix) {
// 英数字、カンマ、ハイフンのみ許可
return preg_match('/^[a-zA-Z0-9,-]+$/', $prefix) === 1;
}
/**
* 登録済みプレフィックスでセッションIDを生成
*/
public static function createSessionId($prefixName) {
if (!isset(self::$prefixRegistry[$prefixName])) {
throw new Exception("Prefix not registered: {$prefixName}");
}
$prefix = self::$prefixRegistry[$prefixName]['prefix'];
$sessionId = session_create_id($prefix);
if ($sessionId === false) {
throw new Exception("Failed to create session ID");
}
// 使用回数をカウント
self::$prefixRegistry[$prefixName]['usage_count']++;
return [
'session_id' => $sessionId,
'prefix_name' => $prefixName,
'prefix' => $prefix,
'usage_count' => self::$prefixRegistry[$prefixName]['usage_count']
];
}
/**
* 登録済みプレフィックス一覧を取得
*/
public static function listPrefixes() {
return self::$prefixRegistry;
}
/**
* プレフィックスの使用統計
*/
public static function getStatistics() {
$stats = [
'total_prefixes' => count(self::$prefixRegistry),
'total_usage' => 0,
'most_used' => null,
'least_used' => null
];
if (empty(self::$prefixRegistry)) {
return $stats;
}
$usageCounts = array_column(self::$prefixRegistry, 'usage_count');
$stats['total_usage'] = array_sum($usageCounts);
arsort($usageCounts);
$mostUsed = array_key_first($usageCounts);
$stats['most_used'] = [
'name' => $mostUsed,
'count' => $usageCounts[$mostUsed]
];
asort($usageCounts);
$leastUsed = array_key_first($usageCounts);
$stats['least_used'] = [
'name' => $leastUsed,
'count' => $usageCounts[$leastUsed]
];
return $stats;
}
/**
* 環境別のプレフィックスを生成
*/
public static function createEnvironmentPrefix($environment) {
$prefixes = [
'production' => 'prod-',
'staging' => 'stg-',
'development' => 'dev-',
'testing' => 'test-'
];
$prefix = $prefixes[$environment] ?? 'env-';
self::registerPrefix($environment, $prefix);
return $prefix;
}
/**
* アプリケーション別のプレフィックスを生成
*/
public static function createApplicationPrefix($appName, $version = null) {
$cleanName = preg_replace('/[^a-zA-Z0-9]/', '', $appName);
$prefix = strtolower(substr($cleanName, 0, 6)) . '-';
if ($version !== null) {
$cleanVersion = preg_replace('/[^a-zA-Z0-9]/', '', $version);
$prefix .= $cleanVersion . '-';
}
self::registerPrefix($appName . ($version ? "-v{$version}" : ''), $prefix);
return $prefix;
}
}
// 使用例
echo "=== セッションIDプレフィックス管理 ===\n";
// プレフィックスを登録
echo "プレフィックス登録:\n";
SessionPrefixManager::registerPrefix('admin', 'admin-');
SessionPrefixManager::registerPrefix('user', 'user-');
SessionPrefixManager::registerPrefix('api', 'api-');
echo " 3つのプレフィックスを登録\n";
// 環境別プレフィックス
echo "\n環境別プレフィックス:\n";
$prodPrefix = SessionPrefixManager::createEnvironmentPrefix('production');
$devPrefix = SessionPrefixManager::createEnvironmentPrefix('development');
echo " 本番: {$prodPrefix}\n";
echo " 開発: {$devPrefix}\n";
// アプリケーション別プレフィックス
echo "\nアプリケーション別プレフィックス:\n";
$app1Prefix = SessionPrefixManager::createApplicationPrefix('MyShopApp', '2.0');
$app2Prefix = SessionPrefixManager::createApplicationPrefix('BlogEngine');
echo " MyShopApp v2.0: {$app1Prefix}\n";
echo " BlogEngine: {$app2Prefix}\n";
// セッションIDを生成
echo "\nセッションID生成:\n";
$adminSession = SessionPrefixManager::createSessionId('admin');
echo " 管理者: {$adminSession['session_id']}\n";
echo " プレフィックス: {$adminSession['prefix']}\n";
$userSession = SessionPrefixManager::createSessionId('user');
echo " ユーザー: {$userSession['session_id']}\n";
// 複数回使用
for ($i = 0; $i < 3; $i++) {
SessionPrefixManager::createSessionId('api');
}
// 統計情報
echo "\n=== 使用統計 ===\n";
$stats = SessionPrefixManager::getStatistics();
echo "総プレフィックス数: {$stats['total_prefixes']}\n";
echo "総使用回数: {$stats['total_usage']}\n";
echo "最多使用: {$stats['most_used']['name']} ({$stats['most_used']['count']}回)\n";
echo "最少使用: {$stats['least_used']['name']} ({$stats['least_used']['count']}回)\n";
// プレフィックス一覧
echo "\n=== 登録済みプレフィックス ===\n";
foreach (SessionPrefixManager::listPrefixes() as $name => $info) {
echo "{$name}:\n";
echo " プレフィックス: {$info['prefix']}\n";
echo " 使用回数: {$info['usage_count']}\n";
echo " 登録日時: " . date('Y-m-d H:i:s', $info['registered_at']) . "\n";
}
例3: セキュアセッションID生成システム
class SecureSessionIdGenerator {
private $entropy = 'high';
private $hashAlgorithm = 'sha256';
/**
* セキュアセッションID生成システムを初期化
*/
public function __construct($entropy = 'high', $hashAlgorithm = 'sha256') {
$this->entropy = $entropy;
$this->hashAlgorithm = $hashAlgorithm;
}
/**
* 高エントロピーのセッションIDを生成
*/
public function generateSecureId($prefix = '') {
// ベースとなるセッションIDを生成
$baseId = session_create_id($prefix);
if ($baseId === false) {
throw new Exception("Failed to create base session ID");
}
// エントロピーに応じて追加のランダム性を付与
if ($this->entropy === 'high') {
return $this->enhanceEntropy($baseId);
}
return $baseId;
}
/**
* エントロピーを強化
*/
private function enhanceEntropy($baseId) {
// 複数のランダム源を組み合わせ
$randomData = [
random_bytes(16),
microtime(true),
getmypid(),
$_SERVER['REMOTE_ADDR'] ?? 'unknown',
$_SERVER['HTTP_USER_AGENT'] ?? 'unknown'
];
$combined = $baseId . implode('', $randomData);
$hashed = hash($this->hashAlgorithm, $combined);
// プレフィックスを保持
if (strpos($baseId, '-') !== false) {
list($prefix, $id) = explode('-', $baseId, 2);
return $prefix . '-' . $hashed;
}
return $hashed;
}
/**
* タイムスタンプ付きセッションID
*/
public function generateTimestampedId($prefix = '') {
$timestamp = base_convert(time(), 10, 36);
$fullPrefix = $prefix . $timestamp . '-';
return session_create_id($fullPrefix);
}
/**
* ユーザー固有のセッションID
*/
public function generateUserSessionId($userId, $prefix = '') {
// ユーザーIDをハッシュ化
$userHash = substr(hash('sha256', $userId), 0, 8);
$fullPrefix = $prefix . 'u' . $userHash . '-';
return session_create_id($fullPrefix);
}
/**
* セッションIDの強度を評価
*/
public function evaluateStrength($sessionId) {
$strength = [
'length' => strlen($sessionId),
'has_prefix' => strpos($sessionId, '-') !== false,
'entropy_bits' => 0,
'rating' => 'weak'
];
// エントロピービットを推定
$uniqueChars = count(array_unique(str_split($sessionId)));
$strength['entropy_bits'] = $strength['length'] * log($uniqueChars, 2);
// 強度を評価
if ($strength['entropy_bits'] > 256) {
$strength['rating'] = 'very_strong';
} elseif ($strength['entropy_bits'] > 192) {
$strength['rating'] = 'strong';
} elseif ($strength['entropy_bits'] > 128) {
$strength['rating'] = 'moderate';
}
return $strength;
}
/**
* セッションIDの衝突テスト
*/
public function testCollision($count = 1000, $prefix = '') {
$ids = [];
$collisions = 0;
for ($i = 0; $i < $count; $i++) {
$id = $this->generateSecureId($prefix);
if (in_array($id, $ids)) {
$collisions++;
}
$ids[] = $id;
}
return [
'total_generated' => $count,
'unique_ids' => count(array_unique($ids)),
'collisions' => $collisions,
'collision_rate' => ($collisions / $count) * 100
];
}
/**
* 複数のアルゴリズムで比較
*/
public function compareAlgorithms($prefix = '') {
$algorithms = ['md5', 'sha1', 'sha256', 'sha512'];
$results = [];
foreach ($algorithms as $algo) {
$originalAlgo = $this->hashAlgorithm;
$this->hashAlgorithm = $algo;
$startTime = microtime(true);
$id = $this->generateSecureId($prefix);
$endTime = microtime(true);
$results[$algo] = [
'id' => $id,
'length' => strlen($id),
'generation_time' => ($endTime - $startTime) * 1000,
'strength' => $this->evaluateStrength($id)
];
$this->hashAlgorithm = $originalAlgo;
}
return $results;
}
}
// 使用例
echo "=== セキュアセッションID生成 ===\n";
$generator = new SecureSessionIdGenerator('high', 'sha256');
// 基本的なセキュアID生成
echo "セキュアID生成:\n";
$secureId = $generator->generateSecureId('secure-');
echo " ID: {$secureId}\n";
echo " 長さ: " . strlen($secureId) . "\n";
// 強度評価
$strength = $generator->evaluateStrength($secureId);
echo "\n強度評価:\n";
echo " 長さ: {$strength['length']}\n";
echo " エントロピービット: " . round($strength['entropy_bits'], 2) . "\n";
echo " 評価: {$strength['rating']}\n";
// タイムスタンプ付きID
echo "\nタイムスタンプ付きID:\n";
$timestampId = $generator->generateTimestampedId('ts-');
echo " ID: {$timestampId}\n";
// ユーザー固有ID
echo "\nユーザー固有ID:\n";
$userId1 = $generator->generateUserSessionId('user123', 'app-');
$userId2 = $generator->generateUserSessionId('user456', 'app-');
echo " ユーザー123: {$userId1}\n";
echo " ユーザー456: {$userId2}\n";
// 衝突テスト
echo "\n=== 衝突テスト ===\n";
$collisionTest = $generator->testCollision(1000, 'test-');
echo "生成数: {$collisionTest['total_generated']}\n";
echo "ユニークID数: {$collisionTest['unique_ids']}\n";
echo "衝突数: {$collisionTest['collisions']}\n";
echo "衝突率: " . round($collisionTest['collision_rate'], 4) . "%\n";
// アルゴリズム比較
echo "\n=== アルゴリズム比較 ===\n";
$comparison = $generator->compareAlgorithms('compare-');
foreach ($comparison as $algo => $result) {
echo "\n{$algo}:\n";
echo " ID長: {$result['length']}\n";
echo " 生成時間: " . round($result['generation_time'], 4) . "ms\n";
echo " 評価: {$result['strength']['rating']}\n";
}
例4: アプリケーション分離システム
class ApplicationIsolationManager {
private $applications = [];
/**
* アプリケーションを登録
*/
public function registerApplication($appName, $config = []) {
$prefix = $this->generateApplicationPrefix($appName);
$this->applications[$appName] = [
'prefix' => $prefix,
'config' => $config,
'sessions' => [],
'created_at' => time()
];
return $prefix;
}
/**
* アプリケーション固有のプレフィックスを生成
*/
private function generateApplicationPrefix($appName) {
// アプリケーション名から安全なプレフィックスを生成
$clean = preg_replace('/[^a-zA-Z0-9]/', '', $appName);
$short = strtolower(substr($clean, 0, 8));
return "{$short}-";
}
/**
* アプリケーションのセッションを開始
*/
public function startApplicationSession($appName, $userId = null) {
if (!isset($this->applications[$appName])) {
throw new Exception("Application not registered: {$appName}");
}
$app = $this->applications[$appName];
$prefix = $app['prefix'];
// ユーザーIDがある場合はプレフィックスに含める
if ($userId !== null) {
$userHash = substr(md5($userId), 0, 6);
$prefix .= "{$userHash}-";
}
// セッションID生成
$sessionId = session_create_id($prefix);
if ($sessionId === false) {
throw new Exception("Failed to create session ID");
}
// セッションを開始
session_id($sessionId);
session_start();
// アプリケーション情報を記録
$_SESSION['app_name'] = $appName;
$_SESSION['app_prefix'] = $prefix;
$_SESSION['user_id'] = $userId;
$_SESSION['session_started_at'] = time();
// アプリケーションのセッションリストに追加
$this->applications[$appName]['sessions'][] = [
'session_id' => $sessionId,
'user_id' => $userId,
'started_at' => time()
];
return [
'app_name' => $appName,
'session_id' => $sessionId,
'prefix' => $prefix,
'user_id' => $userId
];
}
/**
* セッションIDからアプリケーションを識別
*/
public function identifyApplication($sessionId) {
foreach ($this->applications as $appName => $app) {
if (strpos($sessionId, $app['prefix']) === 0) {
return [
'app_name' => $appName,
'prefix' => $app['prefix'],
'config' => $app['config']
];
}
}
return null;
}
/**
* アプリケーションのセッション一覧
*/
public function getApplicationSessions($appName) {
if (!isset($this->applications[$appName])) {
return [];
}
return $this->applications[$appName]['sessions'];
}
/**
* すべてのアプリケーション情報
*/
public function getAllApplications() {
$info = [];
foreach ($this->applications as $appName => $app) {
$info[$appName] = [
'prefix' => $app['prefix'],
'session_count' => count($app['sessions']),
'created_at' => $app['created_at']
];
}
return $info;
}
/**
* アプリケーション間のセッション移行
*/
public function migrateSession($fromApp, $toApp, $sessionData = []) {
// 現在のセッションを終了
if (session_status() === PHP_SESSION_ACTIVE) {
$oldSessionId = session_id();
$oldData = $_SESSION;
session_write_close();
}
// 新しいアプリケーションでセッション開始
$result = $this->startApplicationSession($toApp, $sessionData['user_id'] ?? null);
// データを移行
if (isset($oldData)) {
foreach ($oldData as $key => $value) {
if (!in_array($key, ['app_name', 'app_prefix'])) {
$_SESSION[$key] = $value;
}
}
}
$_SESSION['migrated_from'] = $fromApp;
$_SESSION['migrated_at'] = time();
session_write_close();
return $result;
}
/**
* アプリケーション統計
*/
public function getStatistics() {
$stats = [
'total_applications' => count($this->applications),
'total_sessions' => 0,
'applications' => []
];
foreach ($this->applications as $appName => $app) {
$sessionCount = count($app['sessions']);
$stats['total_sessions'] += $sessionCount;
$stats['applications'][$appName] = [
'prefix' => $app['prefix'],
'sessions' => $sessionCount,
'uptime' => time() - $app['created_at']
];
}
return $stats;
}
}
// 使用例
echo "=== アプリケーション分離システム ===\n";
$manager = new ApplicationIsolationManager();
// アプリケーションを登録
echo "アプリケーション登録:\n";
$manager->registerApplication('ShopApp', ['theme' => 'blue']);
$manager->registerApplication('BlogApp', ['theme' => 'green']);
$manager->registerApplication('AdminPanel', ['theme' => 'red']);
echo " 3つのアプリケーションを登録\n";
// 各アプリケーションでセッション開始
echo "\nセッション開始:\n";
$shop1 = $manager->startApplicationSession('ShopApp', 'user123');
echo " ShopApp (user123): {$shop1['session_id']}\n";
session_write_close();
$blog1 = $manager->startApplicationSession('BlogApp', 'user456');
echo " BlogApp (user456): {$blog1['session_id']}\n";
session_write_close();
$admin1 = $manager->startApplicationSession('AdminPanel', 'admin001');
echo " AdminPanel (admin001): {$admin1['session_id']}\n";
session_write_close();
// セッションIDからアプリケーションを識別
echo "\nアプリケーション識別:\n";
$identified = $manager->identifyApplication($shop1['session_id']);
echo " セッションID {$shop1['session_id']}\n";
echo " → アプリケーション: {$identified['app_name']}\n";
// 統計情報
echo "\n=== 統計情報 ===\n";
$stats = $manager->getStatistics();
echo "総アプリケーション数: {$stats['total_applications']}\n";
echo "総セッション数: {$stats['total_sessions']}\n";
echo "\nアプリケーション別:\n";
foreach ($stats['applications'] as $appName => $appStats) {
echo " {$appName}:\n";
echo " プレフィックス: {$appStats['prefix']}\n";
echo " セッション数: {$appStats['sessions']}\n";
echo " 稼働時間: {$appStats['uptime']}秒\n";
}
例5: セッションID監査システム
class SessionIdAuditSystem {
private $auditLog = [];
private $logFile;
/**
* 監査システムを初期化
*/
public function __construct($logFile = '/tmp/session_audit.log') {
$this->logFile = $logFile;
$this->loadAuditLog();
}
/**
* セッションID生成を記録
*/
public function auditSessionCreation($prefix = '', $metadata = []) {
$sessionId = session_create_id($prefix);
if ($sessionId === false) {
$this->logEvent('creation_failed', [
'prefix' => $prefix,
'error' => 'Failed to create session ID'
]);
throw new Exception("Failed to create session ID");
}
$auditEntry = [
'event' => 'session_created',
'session_id' => $sessionId,
'prefix' => $prefix,
'timestamp' => time(),
'metadata' => $metadata,
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown'
];
$this->logEvent('session_created', $auditEntry);
return $sessionId;
}
/**
* イベントをログに記録
*/
private function logEvent($eventType, $data) {
$entry = [
'event_type' => $eventType,
'timestamp' => time(),
'data' => $data
];
$this->auditLog[] = $entry;
// ファイルに追記
$logLine = json_encode($entry) . "\n";
file_put_contents($this->logFile, $logLine, FILE_APPEND);
}
/**
* 監査ログを読み込み
*/
private function loadAuditLog() {
if (!file_exists($this->logFile)) {
return;
}
$lines = file($this->logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
$entry = json_decode($line, true);
if ($entry) {
$this->auditLog[] = $entry;
}
}
}
/**
* セッションID使用パターンを分析
*/
public function analyzePatterns() {
$patterns = [
'total_created' => 0,
'by_prefix' => [],
'by_hour' => array_fill(0, 24, 0),
'by_ip' => []
];
foreach ($this->auditLog as $entry) {
if ($entry['event_type'] !== 'session_created') {
continue;
}
$patterns['total_created']++;
// プレフィックス別
$prefix = $entry['data']['prefix'] ?? 'none';
if (!isset($patterns['by_prefix'][$prefix])) {
$patterns['by_prefix'][$prefix] = 0;
}
$patterns['by_prefix'][$prefix]++;
// 時間帯別
$hour = (int)date('G', $entry['timestamp']);
$patterns['by_hour'][$hour]++;
// IP別
$ip = $entry['data']['ip_address'] ?? 'unknown';
if (!isset($patterns['by_ip'][$ip])) {
$patterns['by_ip'][$ip] = 0;
}
$patterns['by_ip'][$ip]++;
}
return $patterns;
}
/**
* 疑わしい活動を検出
*/
public function detectSuspiciousActivity() {
$suspicious = [];
$patterns = $this->analyzePatterns();
// 同一IPからの過剰なセッション生成
foreach ($patterns['by_ip'] as $ip => $count) {
if ($count > 100) {
$suspicious[] = [
'type' => 'excessive_sessions',
'ip' => $ip,
'count' => $count,
'severity' => 'high'
];
}
}
// 短時間での大量生成
$recentSessions = array_filter($this->auditLog, function($entry) {
return $entry['timestamp'] > (time() - 300) &&
$entry['event_type'] === 'session_created';
});
if (count($recentSessions) > 50) {
$suspicious[] = [
'type' => 'burst_creation',
'count' => count($recentSessions),
'timeframe' => '5 minutes',
'severity' => 'medium'
];
}
return $suspicious;
}
/**
* 監査レポートを生成
*/
public function generateReport() {
$patterns = $this->analyzePatterns();
$suspicious = $this->detectSuspiciousActivity();
$report = [
'generated_at' => time(),
'summary' => [
'total_sessions' => $patterns['total_created'],
'unique_ips' => count($patterns['by_ip']),
'unique_prefixes' => count($patterns['by_prefix']),
'suspicious_activities' => count($suspicious)
],
'patterns' => $patterns,
'suspicious_activities' => $suspicious,
'recommendations' => $this->generateRecommendations($patterns, $suspicious)
];
return $report;
}
/**
* 推奨事項を生成
*/
private function generateRecommendations($patterns, $suspicious) {
$recommendations = [];
if (count($suspicious) > 0) {
$recommendations[] = "疑わしい活動が検出されました。セキュリティを強化してください。";
}
$peakHour = array_search(max($patterns['by_hour']), $patterns['by_hour']);
$recommendations[] = "ピーク時間帯は{$peakHour}時です。この時間帯のパフォーマンスを最適化してください。";
if ($patterns['total_created'] > 1000) {
$recommendations[] = "大量のセッションが生成されています。セッションクリーンアップを検討してください。";
}
return $recommendations;
}
/**
* 監査ログをクリア
*/
public function clearAuditLog() {
$this->auditLog = [];
if (file_exists($this->logFile)) {
unlink($this->logFile);
}
}
}
// 使用例
echo "=== セッションID監査システム ===\n";
$audit = new SessionIdAuditSystem('/tmp/session_audit_test.log');
// セッションIDを生成(監査付き)
echo "セッションID生成(監査付き):\n";
for ($i = 0; $i < 20; $i++) {
$prefix = $i % 2 === 0 ? 'app1-' : 'app2-';
$sessionId = $audit->auditSessionCreation($prefix, [
'user_type' => $i % 2 === 0 ? 'regular' : 'premium',
'source' => 'web'
]);
if ($i < 3) {
echo " {$sessionId}\n";
}
}
echo " ... (合計20個生成)\n";
// パターン分析
echo "\n=== パターン分析 ===\n";
$patterns = $audit->analyzePatterns();
echo "総生成数: {$patterns['total_created']}\n";
echo "\nプレフィックス別:\n";
foreach ($patterns['by_prefix'] as $prefix => $count) {
echo " {$prefix}: {$count}個\n";
}
// 疑わしい活動の検出
echo "\n=== 疑わしい活動 ===\n";
$suspicious = $audit->detectSuspiciousActivity();
if (empty($suspicious)) {
echo "疑わしい活動は検出されませんでした\n";
} else {
foreach ($suspicious as $activity) {
echo "タイプ: {$activity['type']}\n";
echo " 深刻度: {$activity['severity']}\n";
}
}
// 監査レポート
echo "\n=== 監査レポート ===\n";
$report = $audit->generateReport();
echo "レポート生成日時: " . date('Y-m-d H:i:s', $report['generated_at']) . "\n";
echo "\nサマリー:\n";
foreach ($report['summary'] as $key => $value) {
echo " {$key}: {$value}\n";
}
echo "\n推奨事項:\n";
foreach ($report['recommendations'] as $recommendation) {
echo " - {$recommendation}\n";
}
// クリーンアップ
$audit->clearAuditLog();
例6: カスタムセッションIDバリデーション
class SessionIdValidator {
private $rules = [];
private $errors = [];
/**
* バリデーションルールを追加
*/
public function addRule($name, callable $validator, $errorMessage) {
$this->rules[$name] = [
'validator' => $validator,
'message' => $errorMessage
];
return $this;
}
/**
* セッションIDを検証
*/
public function validate($sessionId) {
$this->errors = [];
$results = [];
foreach ($this->rules as $name => $rule) {
$isValid = call_user_func($rule['validator'], $sessionId);
$results[$name] = [
'valid' => $isValid,
'message' => $isValid ? 'OK' : $rule['message']
];
if (!$isValid) {
$this->errors[$name] = $rule['message'];
}
}
return [
'valid' => empty($this->errors),
'results' => $results,
'errors' => $this->errors
];
}
/**
* 標準的なバリデーションルールをセットアップ
*/
public function setupStandardRules() {
// 最小長チェック
$this->addRule('min_length', function($id) {
return strlen($id) >= 32;
}, 'セッションIDは32文字以上である必要があります');
// 最大長チェック
$this->addRule('max_length', function($id) {
return strlen($id) <= 128;
}, 'セッションIDは128文字以下である必要があります');
// 文字種チェック
$this->addRule('valid_characters', function($id) {
return preg_match('/^[a-zA-Z0-9,-]+$/', $id) === 1;
}, 'セッションIDに不正な文字が含まれています');
// プレフィックス形式チェック
$this->addRule('prefix_format', function($id) {
if (strpos($id, '-') === false) {
return true; // プレフィックスなしはOK
}
// プレフィックスは先頭にあること
return preg_match('/^[a-zA-Z0-9,]+-/', $id) === 1;
}, 'プレフィックスの形式が不正です');
return $this;
}
/**
* セキュリティレベル別のルール
*/
public function setupSecurityRules($level = 'medium') {
$this->setupStandardRules();
switch ($level) {
case 'high':
// エントロピーチェック
$this->addRule('high_entropy', function($id) {
$uniqueChars = count(array_unique(str_split($id)));
return $uniqueChars >= 16;
}, '十分なエントロピーがありません');
// 予測可能なパターンのチェック
$this->addRule('no_patterns', function($id) {
// 連続する文字のチェック
return !preg_match('/(.)\1{3,}/', $id);
}, '予測可能なパターンが含まれています');
break;
case 'low':
// 基本的なチェックのみ
break;
case 'medium':
default:
// 適度なエントロピーチェック
$this->addRule('medium_entropy', function($id) {
$uniqueChars = count(array_unique(str_split($id)));
return $uniqueChars >= 12;
}, 'エントロピーが不十分です');
}
return $this;
}
/**
* カスタムプレフィックスルール
*/
public function requirePrefix($allowedPrefixes) {
$this->addRule('required_prefix', function($id) use ($allowedPrefixes) {
foreach ($allowedPrefixes as $prefix) {
if (strpos($id, $prefix) === 0) {
return true;
}
}
return false;
}, '許可されたプレフィックスが含まれていません: ' . implode(', ', $allowedPrefixes));
return $this;
}
/**
* セッションIDを生成して検証
*/
public function generateAndValidate($prefix = '', $maxAttempts = 10) {
for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
$sessionId = session_create_id($prefix);
if ($sessionId === false) {
continue;
}
$validation = $this->validate($sessionId);
if ($validation['valid']) {
return [
'success' => true,
'session_id' => $sessionId,
'attempts' => $attempt,
'validation' => $validation
];
}
}
return [
'success' => false,
'error' => '有効なセッションIDを生成できませんでした',
'attempts' => $maxAttempts
];
}
/**
* バッチ検証
*/
public function validateBatch($sessionIds) {
$results = [];
foreach ($sessionIds as $id) {
$results[$id] = $this->validate($id);
}
return [
'total' => count($sessionIds),
'valid' => count(array_filter($results, function($r) { return $r['valid']; })),
'invalid' => count(array_filter($results, function($r) { return !$r['valid']; })),
'results' => $results
];
}
/**
* エラーを取得
*/
public function getErrors() {
return $this->errors;
}
}
// 使用例
echo "=== カスタムセッションIDバリデーション ===\n";
$validator = new SessionIdValidator();
// 標準ルールをセットアップ
echo "標準ルールで検証:\n";
$validator->setupStandardRules();
$testId1 = session_create_id('test-');
$result1 = $validator->validate($testId1);
echo " セッションID: {$testId1}\n";
echo " 検証結果: " . ($result1['valid'] ? '有効' : '無効') . "\n";
if (!$result1['valid']) {
echo " エラー:\n";
foreach ($result1['errors'] as $rule => $message) {
echo " - {$message}\n";
}
}
// セキュリティレベル別
echo "\n高セキュリティレベルで検証:\n";
$validator2 = new SessionIdValidator();
$validator2->setupSecurityRules('high');
$testId2 = session_create_id('secure-');
$result2 = $validator2->validate($testId2);
echo " セッションID: {$testId2}\n";
echo " 検証結果: " . ($result2['valid'] ? '有効' : '無効') . "\n";
// プレフィックス要件
echo "\nプレフィックス要件付き検証:\n";
$validator3 = new SessionIdValidator();
$validator3->setupStandardRules()
->requirePrefix(['app1-', 'app2-', 'admin-']);
$testIds = [
session_create_id('app1-'),
session_create_id('app2-'),
session_create_id('invalid-')
];
foreach ($testIds as $id) {
$result = $validator3->validate($id);
echo " {$id}: " . ($result['valid'] ? '有効' : '無効') . "\n";
}
// 生成と検証
echo "\n生成と検証:\n";
$validator4 = new SessionIdValidator();
$validator4->setupSecurityRules('medium')
->requirePrefix(['myapp-']);
$generated = $validator4->generateAndValidate('myapp-');
if ($generated['success']) {
echo " 成功\n";
echo " セッションID: {$generated['session_id']}\n";
echo " 試行回数: {$generated['attempts']}\n";
} else {
echo " 失敗: {$generated['error']}\n";
}
// バッチ検証
echo "\n=== バッチ検証 ===\n";
$batchIds = [];
for ($i = 0; $i < 5; $i++) {
$batchIds[] = session_create_id('batch-');
}
$validator5 = new SessionIdValidator();
$validator5->setupStandardRules();
$batchResults = $validator5->validateBatch($batchIds);
echo "総数: {$batchResults['total']}\n";
echo "有効: {$batchResults['valid']}\n";
echo "無効: {$batchResults['invalid']}\n";
プレフィックスの制約
// 使用可能な文字
// - 英数字: a-z, A-Z, 0-9
// - カンマ: ,
// - ハイフン: -
// 正しい例
$id1 = session_create_id('abc123-'); // OK
$id2 = session_create_id('USER,001-'); // OK
$id3 = session_create_id('app-v2-'); // OK
// 不正な例(警告が発生)
// $id4 = session_create_id('app_1-'); // アンダースコア不可
// $id5 = session_create_id('app.1-'); // ドット不可
// $id6 = session_create_id('app@1-'); // 記号不可
// $id7 = session_create_id('アプリ-'); // 日本語不可
// プレフィックスの長さ制限なし(実用的には短くすべき)
$longPrefix = str_repeat('a', 100) . '-';
$id8 = session_create_id($longPrefix); // 可能だが非推奨
まとめ
session_create_id()関数の特徴をまとめると:
できること:
- 新しいセッションIDの生成
- プレフィックス付きセッションIDの作成
- アプリケーション間のセッション分離
- カスタムセッションID構造の実現
プレフィックス:
- 使用可能文字: 英数字、カンマ(,)、ハイフン(-)
- 長さ制限: なし(実用的には短く)
- 用途: アプリケーション識別、環境識別、テナント分離
推奨される使用場面:
- マルチテナントシステム
- 複数アプリケーションの共存
- セッションの論理的分離
- セキュリティ強化
- セッション監査
重要な注意点:
session_start()の前にsession_id()でIDを設定- プレフィックスに不正な文字を使用すると警告
- PHP 7.1.0以降で使用可能
- セッションIDの一意性は保証される
ベストプラクティス:
// 1. アプリケーション識別
$appId = session_create_id('myapp-');
session_id($appId);
session_start();
// 2. 環境別のプレフィックス
$env = getenv('APP_ENV'); // 'prod', 'dev', etc.
$prefix = "{$env}-";
$sessionId = session_create_id($prefix);
// 3. ユーザー識別付き
$userId = getCurrentUserId();
$userHash = substr(md5($userId), 0, 8);
$sessionId = session_create_id("u{$userHash}-");
// 4. セキュリティ強化
$timestamp = base_convert(time(), 10, 36);
$sessionId = session_create_id("sec{$timestamp}-");
関連関数:
session_id(): セッションIDの取得/設定session_start(): セッション開始session_regenerate_id(): セッションID再生成session_name(): セッション名の取得/設定
session_create_id()は、柔軟なセッション管理を実現する重要な関数です。プレフィックスを活用することで、複数のアプリケーションやテナントを効果的に分離し、セキュリティとメンテナンス性を向上できます!
