セッション

入力したデータを入力画面で再利用できるように、セッションに保存します。

ファイル構成

php_sns/
├── lib/
│        ├── Database.php
│        └── Sanitize.php
├── regist/
│        ├── add.php
│        ├── index.php
│        └── input.php
├── app.php
└── env.php

セッション登録処理

セッション開始処理

app.php が実行されたら、セッション開始するようにします。

app.php
<?php
require_once "env.php";

// セッション開始
session_start();
session_regenerate_id(true);

const BASE_DIR = __DIR__;
const APP_DIR = __DIR__ . "/app/";
const LIB_DIR = __DIR__ . "/lib/";
const COMPONENT_DIR = __DIR__ . "/components/";

require_once LIB_DIR . 'Database.php';

セッション登録

POSTデータをセッションに登録します。$_SESSION のキーはenv.php で設定した APP_KEYregist にします。

regist/add.php
<?php
require_once "../app.php";

if ($_SERVER["REQUEST_METHOD"] !== "POST") {
    exit;
}

// POSTデータをセッション登録
$_SESSION[APP_KEY]['regist'] = $_POST;

$regist = $_POST;
?>

セッション取得

入力画面でセッションデータを取得してデバッグ確認します。

regist/input.php
<?php
require_once "../app.php";

$regist = $_SESSION[APP_KEY]['regist'];
// デバッグ確認
var_dump($regist);
?>
...

セッションデータ確認

確認画面でセッション登録したら、入力画面に戻ってセッションが保存されているか確認します。これで、どの画面でもセッションデータにアクセスできます。

セッション表示

入力画面のフォームに、セッションに登録した前回入力したデータを表示します。

regist/input.php
...
<main id="regist" class="flex justify-center">
    <div class="w-1/2 mt-3 p-5">
        <h2 class="text-2xl mb-3 font-normal text-center">Sign Up</h2>
        <form action="regist/confirm.php" method="post">
            <div class="relative mb-4">
                <input class="block
                        px-2.5 pb-2.5 pt-6 mb-3
                        w-full rounded-lg
                        text-sm 
                        text-gray-900 
                        ring-1 ring-gray-300 
                        focus:outline-none 
                        focus:ring-1 
                        focus:ring-blue-600 
                        peer" 
                        oninput="checkRegisterInputs()" type="text" 
                        name="account_name" id="account_name" 
                        value="<?= @$regist['account_name'] ?>" placeholder=" " required>
                <label for="account_name" class="absolute 
                        text-sm text-gray-400 
                        duration-300 
                        transform -translate-y-4 scale-75 
                        top-4 
                        origin-[0] start-2.5 
                        peer-focus:px-0
                        peer-focus:text-blue-600 
                        peer-focus:dark:text-blue-500 
                        peer-placeholder-shown:scale-100 
                        peer-placeholder-shown:translate-y-0 
                        peer-focus:scale-75 
                        peer-focus:-translate-y-4">
                    アカウント名
                </label>
                <p class="text-sm text-red-600"><?= @$errors['email'] ?></p>
            </div>
            <div class="relative mb-4">
                <input class="block
                        px-2.5 pb-2.5 pt-6 mb-3
                        w-full rounded-lg
                        text-sm 
                        text-gray-900 
                        ring-1 ring-gray-300 
                        focus:outline-none 
                        focus:ring-1 
                        focus:ring-blue-600 
                        peer" 
                        oninput="checkRegisterInputs()" type="email" 
                        name="email" id="email" value="<?= @$regist['email'] ?>" 
                        placeholder=" " required>
                <label for="email" class="absolute 
                        text-sm text-gray-400 
                        duration-300 
                        transform -translate-y-4 scale-75 
                        top-4 
                        origin-[0] start-2.5 
                        peer-focus:px-0
                        peer-focus:text-blue-600 
                        peer-focus:dark:text-blue-500 
                        peer-placeholder-shown:scale-100 
                        peer-placeholder-shown:translate-y-0 
                        peer-focus:scale-75 
                        peer-focus:-translate-y-4">
                    Email
                </label>
                <p class="text-sm text-red-600"><?= @$errors['email'] ?></p>
            </div>
            <div class="relative mb-4">
                <input class="block
                        px-2.5 pb-2.5 pt-6 mb-3 
                        w-full rounded-lg
                        text-sm 
                        text-gray-900 
                        ring-1 ring-gray-300 
                        focus:outline-none 
                        focus:ring-1 
                        focus:ring-blue-600 
                        peer" 
                        oninput="checkRegisterInputs()" 
                        type="password"  name="password"  id="password"  value="" 
                        placeholder=" " required>
                <label for="password" class="absolute 
                        text-sm text-gray-400 
                        duration-300 
                        transform -translate-y-4 scale-75 
                        top-4 
                        origin-[0] start-2.5 
                        peer-focus:px-0
                        peer-focus:text-blue-600 
                        peer-focus:dark:text-blue-500 
                        peer-placeholder-shown:scale-100 
                        peer-placeholder-shown:translate-y-0 
                        peer-focus:scale-75 
                        peer-focus:-translate-y-4">
                    パスワード
                </label>
                <div class="text-sm text-red-600"><?= @$errors['password'] ?></div>
            </div>

            <div class="relative mb-4">
                <input class="block
                        px-2.5 pb-2.5 pt-6 mb-3
                        w-full rounded-lg
                        text-sm 
                        text-gray-900 
                        ring-1 ring-gray-300 
                        focus:outline-none 
                        focus:ring-1 
                        focus:ring-blue-600 
                        peer" 
                        oninput="checkRegisterInputs()" type="text" 
                        name="name" id="name" value="<?= @$regist['name'] ?>" 
                        placeholder=" " required>
                <label for="name" class="absolute 
                        text-sm text-gray-400 
                        duration-300 
                        transform -translate-y-4 scale-75 
                        top-4 
                        origin-[0] start-2.5 
                        peer-focus:px-0
                        peer-focus:text-blue-600 
                        peer-focus:dark:text-blue-500 
                        peer-placeholder-shown:scale-100 
                        peer-placeholder-shown:translate-y-0 
                        peer-focus:scale-75 
                        peer-focus:-translate-y-4">
                    名前
                </label>
                <p class="text-sm text-red-600"><?= @$errors['name'] ?></p>
            </div>

            <div>
                <button id="submit_button" class="w-full
                        mb-2 py-2 px-4 bg-sky-500 
                        hover:bg-sky-700 
                        text-white 
                        rounded-lg
                        disabled:bg-blue-300" disabled>
                    次へ
                </button>
            </div>

            <div class="text-center">
                <ul>
                    <li class="pt-3">
                        <a href="login/" class="text-blue-700 hover:text-blue-600">Sign in はこちら</a>
                    </li>
                    <li class="pt-3">
                        <a href="forget_password/" class="text-blue-700 hover:text-blue-600">
                        パスワードを忘れた方
                        </a>
                    </li>
                </ul>
            </div>
        </form>
    </div>
</main>
...

ユーザ登録処理

セッション取得

regist/add.php
<?php 
// app.php を読み込み
require_once '../app.php';

// POSTリクエストでなければ何も表示しない
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    exit;
}

// セッションデータ取得
$regist = $_SESSION[APP_KEY]['regist'];

パスワードハッシュ

ハッシュデータはデータを復号(元に戻す)できないので、パスワードを保存するのに有効です。

password_hash()

password_hash() は、パスワードハッシュするためのメソッドです。

password_hash(パスワード, オプション);

パスワードがハッシュ化されると解読不可能な文字列に変換されます。

$2y$10$S9n.vg7s8T4lRIOiggVlMOuWgR1tZSBKLwToX5S.nbZ2iRcZvK8w6

password_verify()

password_verify() で生のパスワードと、ハッシュが一致しているかの検証(True/False)できます。

bool = password_verify(文字列, ハッシュ)
regist/add.php
<?php 
require_once '../app.php';

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    exit;
}

$regist = $_SESSION[APP_KEY]['regist'];

// パスワードのハッシュ化
$regist['password'] = password_hash($regist['password'], PASSWORD_DEFAULT);

ユーザデータ登録

usersテーブルにデータ登録するSQLを作成し、実行します。データ登録後は「complete.php」にリダイレクトします。

regist/add.php
<?php 
require_once '../app.php';

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    exit;
}

$regist = $_SESSION[APP_KEY]['regist'];

$regist['password'] = password_hash($regist['password'], PASSWORD_DEFAULT);

// データベースに接続
$model = new Model();

// users テーブルにレコードを挿入するSQL
$sql = "INSERT INTO users (account_name, name, email, password)
        VALUES (:account_name, :name, :email, :password);
        ";

// データベースに登録
$stmt = $model->pdo->prepare($sql);
$stmt->execute($regist);

// ログインページにリダイレクト
header('Location: ../login/');

データ確認

確認画面から【登録】ボタンをクリックし、usersテーブルにデータ登録されるか確認してみましょう。

セキュリティ処理

SQLインジェクション

フォームから悪意のあるデータ送信して、SQLを書き換えて実行できる可能性があります。これをSQLインジェクション攻撃といいます。

query() の危険性

送信データをそのまま query() で実行すると、SQLインジェクションの対象となります。 ログインフォームの Email欄に以下の文字を入力してログインしてみましょう。

' or 1 = '1

SQLインジェクションの結果

正しい Email を入力しなくてもユーザデータを取得できました。

array(7) {
  ["id"]=>
  int(1)
  ["name"]=>
  string(13) "三宅 直子"
  ["email"]=>
  string(19) "[email protected]"
  ["password"]=>
...
}

実行されたSQL

SQLの条件は「Email を空欄」または「すべて」となり、すべてのユーザを取得するSQLに変換されました。

SELECT * FROM users WHERE email = '' or 1 = '1'

サニタイズ

サニタイズとは

フォーム送信で悪意のあるデータを変換して無力化することを、サニタイズ(Sanitize) といいます。

htmlspecialchars() でサニタイズ

HTMLやURLなどはSQLインジェクションの文字列を含んでいるため、htmlspecialchars() で、特殊文字をHTMLエンティティに変換します。

htmlspecialchars(値, ENT_QUOTES, "UTF-8")

例えば '(シングルクォーテーション)&#039; に変換されるので、SQLインジェクション対策に有効です。

変換前
' or 1 = '1
変換後
&#039; or 1 = &#039;1

POSTデータのサニタイズ

lib/Sanitize.php を作成し、サニタイズの関数を追加します。

lib/Sanitize.php
<?php
/**
 * サニタイズ関数1
 * @param string $str サニタイズする文字列
 * @return string サニタイズ後の文字列
 */
function h($str)
{
    return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}

/**
 * サニタイズ関数2
 * @param mixed $data サニタイズするデータ
 * @return mixed サニタイズ後のデータ
 */
function sanitize($data)
{
    // 配列の場合、再帰的にサニタイズ
    if (is_array($data)) {
        return array_map('sanitize', $data);
    }
    // サニタイズ処理
    // htmlspecialchars() を使用して、HTMLエスケープを行う
    // trim() を使用して、前後の空白を削除
    // ENT_QUOTES を指定して、シングルクォートとダブルクォートをエスケープ
    return trim(h($data, ENT_QUOTES, 'UTF-8'));
}

sanitize() を追加して、サニタイズ処理の実装します。

regist/add.php
<?php
require_once "../app.php";

if ($_SERVER["REQUEST_METHOD"] !== "POST") {
    exit;
}

$_SESSION[APP_KEY]['regist'] = $_POST;
// サニタイズ
$regist = sanitize($_POST);
?>

PHP + MySQL Webサーバプログラミング