MENU

PHPの基本から応用まで – 初心者向けチュートリアルシリーズ(第6回:セッション管理とセキュリティ)

目次

はじめに

前回のデータベース操作に続き、今回はPHPにおけるセッション管理とWebアプリケーションのセキュリティについて解説します。ユーザー認証、セッション管理、そしてよくある脆弱性とその対策について学んでいきましょう。

セッション管理の基本

セッションの開始と使用

<?php
// セッションの開始
session_start();

// セッションへのデータ保存
$_SESSION['user_id'] = 123;
$_SESSION['username'] = 'yamada';

// セッションからのデータ取得
if (isset($_SESSION['user_id'])) {
    echo "ログインユーザーID: " . $_SESSION['user_id'];
}

// セッションの破棄
session_destroy();

セッションセキュリティの設定

<?php
// セッションの設定
ini_set('session.cookie_httponly', 1);  // JavaScriptからのアクセスを防ぐ
ini_set('session.use_only_cookies', 1); // URLからのセッションIDを無効化
ini_set('session.cookie_secure', 1);    // HTTPSのみでセッションを有効化

// セッションIDの再生成
session_regenerate_id(true);

ユーザー認証の実装

安全なパスワード管理

class UserAuthentication {
    private $pdo;
    
    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }
    
    // ユーザー登録
    public function register(string $username, string $password, string $email): bool {
        $hashedPassword = password_hash($password, PASSWORD_DEFAULT);
        
        $sql = "INSERT INTO users (username, password, email) 
                VALUES (:username, :password, :email)";
                
        try {
            $stmt = $this->pdo->prepare($sql);
            return $stmt->execute([
                ':username' => $username,
                ':password' => $hashedPassword,
                ':email' => $email
            ]);
        } catch (PDOException $e) {
            error_log($e->getMessage());
            return false;
        }
    }
    
    // ログイン認証
    public function login(string $username, string $password): ?array {
        $sql = "SELECT * FROM users WHERE username = :username";
        
        try {
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute([':username' => $username]);
            $user = $stmt->fetch();
            
            if ($user && password_verify($password, $user['password'])) {
                // セッションIDを再生成
                session_regenerate_id(true);
                
                // セッションにユーザー情報を保存
                $_SESSION['user_id'] = $user['id'];
                $_SESSION['username'] = $user['username'];
                
                return $user;
            }
            
            return null;
        } catch (PDOException $e) {
            error_log($e->getMessage());
            return null;
        }
    }
}

クロスサイトスクリプティング(XSS)対策

出力のエスケープ処理

class HTMLPurifier {
    public static function escape(string $str): string {
        return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
    }
    
    public static function escapeArray(array $array): array {
        return array_map([self::class, 'escape'], $array);
    }
}

// 使用例
$userInput = "<script>alert('XSS');</script>";
echo HTMLPurifier::escape($userInput); // 安全な出力

CSRFトークンの実装

class CSRFProtection {
    public static function generateToken(): string {
        if (empty($_SESSION['csrf_token'])) {
            $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
        }
        return $_SESSION['csrf_token'];
    }
    
    public static function validateToken(?string $token): bool {
        if (empty($_SESSION['csrf_token']) || empty($token)) {
            return false;
        }
        return hash_equals($_SESSION['csrf_token'], $token);
    }
}

// フォームでの使用例
?>
<form method="POST" action="/submit">
    <input type="hidden" name="csrf_token" 
           value="<?php echo CSRFProtection::generateToken(); ?>">
    <!-- フォームの内容 -->
</form>

実践的な例:セキュアなログインシステム

<?php
class SecureLoginSystem {
    private $pdo;
    private $maxLoginAttempts = 5;
    private $lockoutTime = 900; // 15分
    
    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }
    
    public function attemptLogin(string $username, string $password): array {
        if ($this->isIPBlocked($_SERVER['REMOTE_ADDR'])) {
            return [
                'success' => false,
                'message' => 'Too many failed attempts. Please try again later.'
            ];
        }
        
        try {
            $user = $this->validateCredentials($username, $password);
            
            if ($user) {
                $this->resetLoginAttempts($_SERVER['REMOTE_ADDR']);
                $this->createLoginSession($user);
                
                return [
                    'success' => true,
                    'message' => 'Login successful'
                ];
            }
            
            $this->incrementLoginAttempts($_SERVER['REMOTE_ADDR']);
            return [
                'success' => false,
                'message' => 'Invalid credentials'
            ];
            
        } catch (Exception $e) {
            error_log($e->getMessage());
            return [
                'success' => false,
                'message' => 'An error occurred'
            ];
        }
    }
    
    private function createLoginSession(array $user): void {
        $_SESSION['user_id'] = $user['id'];
        $_SESSION['username'] = $user['username'];
        $_SESSION['login_time'] = time();
        $_SESSION['ip_address'] = $_SERVER['REMOTE_ADDR'];
        
        // セッションIDを再生成
        session_regenerate_id(true);
    }
    
    private function validateCredentials(string $username, string $password): ?array {
        $sql = "SELECT * FROM users WHERE username = :username";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([':username' => $username]);
        $user = $stmt->fetch();
        
        if ($user && password_verify($password, $user['password'])) {
            return $user;
        }
        
        return null;
    }
    
    // 以下、レート制限関連のメソッド
    private function isIPBlocked(string $ip): bool {
        $sql = "SELECT COUNT(*) as attempts, MAX(attempt_time) as last_attempt 
                FROM login_attempts 
                WHERE ip_address = :ip 
                AND attempt_time > DATE_SUB(NOW(), INTERVAL 15 MINUTE)";
                
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([':ip' => $ip]);
        $result = $stmt->fetch();
        
        return ($result['attempts'] >= $this->maxLoginAttempts);
    }
    
    private function incrementLoginAttempts(string $ip): void {
        $sql = "INSERT INTO login_attempts (ip_address) VALUES (:ip)";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([':ip' => $ip]);
    }
    
    private function resetLoginAttempts(string $ip): void {
        $sql = "DELETE FROM login_attempts WHERE ip_address = :ip";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([':ip' => $ip]);
    }
}

セキュリティチェックリスト

  1. 入力検証
    • すべてのユーザー入力を検証
    • 適切なデータ型とフォーマットの強制
    • 不正な入力の拒否
  2. 出力エスケープ
    • HTMLエスケープの実施
    • JavaScriptエスケープの実施
    • SQLエスケープ(プリペアドステートメント使用)
  3. セッション管理
    • セッションIDの定期的な再生成
    • セキュアなセッション設定
    • セッションタイムアウトの実装
  4. パスワードセキュリティ
    • 強力なハッシュアルゴリズムの使用
    • ソルトの適用
    • パスワード強度の要件設定

まとめ

本日は、PHPでのセッション管理とセキュリティ対策について学びました。適切なセキュリティ対策は、Webアプリケーション開発において非常に重要です。

練習問題

  1. 以下の機能を持つログインシステムを作成してください:
    • ユーザー登録(パスワードのハッシュ化)
    • ログイン認証
    • パスワードリセット機能
    • ログイン試行回数の制限
  2. CSRFトークンを実装したフォーム送信システムを作成してください:
    • トークンの生成と検証
    • フォームの自動保護
    • エラー処理

次回は、PHPでのファイル操作とアップロード機能について解説していきます。お楽しみに!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次