PHP、第四章:会員登録システム。フォーム改良で使いやすさと安全性を向上

PHP

HTTPリファラー、POSTメソッドの確認

フォーム送信時に、特定のページからのリクエストかどうか、またPOSTメソッドで送信されているかを確認することで、不正なアクセスを防ぐことができます。ここでは、HTTPリファラーチェックとPOSTメソッドの確認をコードに追加する方法を紹介します。

register.htmlはregister.phpに変更してください。

// HTTPリファラーチェック
if (empty($_SERVER['HTTP_REFERER']) || strpos($_SERVER['HTTP_REFERER'], 'register.php') === false) {
    http_response_code(403);
    die("不正なアクセスです。");
}
PHP

解説

  • $_SERVER[‘HTTP_REFERER’] を確認し、リファラーが register.php を含んでいるかを確認します。
  • strpos() を使って register.php がURL内に含まれているかを判定します。
  • リファラーが空の場合や条件を満たさない場合、不正なアクセスと判断し処理を中断します。
// POSTメソッド確認
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(403);
    die("不正なアクセスです。");
}
PHP

解説

  • $_SERVER[‘REQUEST_METHOD’] を使って、リクエストが POST であることを確認します。
  • POST以外のリクエストが来た場合(例えばGETリクエスト)、処理を中断し、不正アクセスとみなします。
<?php
// HTTPリファラーチェック
if (empty($_SERVER['HTTP_REFERER']) || strpos($_SERVER['HTTP_REFERER'], 'register.php') === false) {
    http_response_code(403);
    die("不正なアクセスです。");
}

// POSTメソッド確認
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(403);
    die("不正なアクセスです。");
}

// データベース接続情報
$host = 'localhost';
$dbname = 'member_system';
$charset = 'utf8';
$username = 'root';
$password = 'pass';
PHP

CSRF対策

CSRF(クロスサイトリクエストフォージェリ)攻撃を防ぐために、フォーム送信時に CSRFトークン を導入することが効果的です。トークンを使うことで、正規のリクエストであることを確認し、不正アクセスを防止します。ここではCSRFトークンを生成し、検証する仕組みを紹介します。

フォームにトークンを埋め込む(register.php)

ファイルをhtmlからphpに変更してください。

<?php
session_start();
// CSRFトークンを生成し、セッションに保存
$token = bin2hex(random_bytes(32));
$_SESSION['csrf_token'] = $token;
?>
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>会員登録システム</title>
</head>
<body>
    <form action="insert.php" method="post">
        <div>
            <label>
                ユーザー名:
                <input type="text" name="name">
            </label>
        </div>
        <div>
            <label>
                メールアドレス:
                <input type="text" name="mail">
            </label>
        </div>
        <div>
            <label>
                パスワード:
                <input type="password" name="pass">
            </label>
        </div>
        <!-- CSRFトークンを埋め込む -->
        <input type="hidden" name="csrf_token" value="<?php echo $token; ?>">
        <input type="submit" value="新規登録">
    </form>
</body>
</html>
register.php

また、index.htmlのリンク先もregister.phpに書き換えてください。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>会員登録システム</title>
</head>
<body>
    <h1>会員登録システム</h1>
    <a href="register.php">新規登録</a>
</body>
</html>
index.html

ポイント

  1. トークンの生成
    • random_bytes() でランダムなトークンを生成し、セッションに保存します。
  2. トークンの埋め込み
    • フォームの hidden フィールドでトークンを送信。

サーバー側でトークンを検証(insert.php)

コードの先頭に追加して下さい。この仕組みによって、CSRF攻撃を防ぎ、正規のフォームからの送信のみを許可することができます。

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

// CSRFトークンの検証
if (empty($_POST['csrf_token']) || $_POST['csrf_token'] !== ($_SESSION['csrf_token'] ?? '')) {
    http_response_code(403);
    die("不正なアクセスです。");
}

// トークンを使い捨てにする(再利用防止)
unset($_SESSION['csrf_token']);

// HTTPリファラーチェック
if (empty($_SERVER['HTTP_REFERER']) || strpos($_SERVER['HTTP_REFERER'], 'register.php') === false) {
    die("不正なアクセスです。<br><a href='register.php'>戻る</a>");
}

// POSTメソッド確認
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    die("不正なリクエストです。<br><a href='register.php'>戻る</a>");
}
PHP

ポイント

  1. トークンの検証
    • サーバー側で送信されたトークンとセッションのトークンが一致するかを確認。
  2. トークンの使い捨て
    • トークンを検証後に削除し、再利用を防ぎます。

トランザクション

トランザクションとは、データベース操作中のエラーによる不整合を防ぐ仕組みです。トランザクションは一連の処理をひとまとまりとして扱い、成功時に確定(コミット)、失敗時に元に戻す(ロールバック)を実行します。例えば、複数のデータ挿入や更新が途中で失敗しても、ロールバックにより変更を無効化でき、データの整合性を保てます。これにより、システムの信頼性と安全性が向上します。

try {
    $dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";
    $pdo = new PDO($dsn, $username, $password, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    ]);

    // トランザクションを開始
    $pdo->beginTransaction();

    $checkSql = "SELECT COUNT(*) FROM users WHERE mail = ?";
    $checkStmt = $pdo->prepare($checkSql);
    $checkStmt->execute([$mail]);
    $count = $checkStmt->fetchColumn();

    if ($count > 0) {
        // 重複があればロールバックして終了
        $pdo->rollBack();
        die("このメールアドレスはすでに登録されています。<br><a href='register.html'>戻る</a>");
    }

    $sql = "INSERT INTO users (name, mail, pass) VALUES (?, ?, ?)";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$name, $mail, $pass]);

    // トランザクションをコミット
    $pdo->commit();

    echo "登録が完了しました!";
    echo '<br><a href="index.html">トップに戻る</a>';
} catch (PDOException $e) {
    // エラー発生時はロールバック
    if (isset($pdo) && $pdo instanceof PDO && $pdo->inTransaction()) {
        $pdo->rollBack();
    }
    echo "エラーが発生しました: " . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8');
}
?>
PHP

コードが長くなったので、トランザクション挿入部分のみのコードになります。

ポイント

  1. トランザクションの開始: beginTransaction() を呼び出して、処理の一連の開始を明示します。
  2. 成功時のコミット: データベース操作がすべて正常に完了した場合に、commit() を呼び出して変更を確定します。
  3. 失敗時のロールバック: 途中でエラーが発生した場合に、rollBack() を呼び出して、データを元の状態に戻します。

これらの改良を行うことで、フォームの安全性とユーザーの利便性が大幅に向上します。特にCSRFトークンの導入は、セキュリティの観点で欠かせない要素です。これらを組み込むことで、より実用的な会員登録フォームが完成します。

コメント

タイトルとURLをコピーしました