PHPとMySQLを使っていちから掲示板を作ってみる講座、6回目になりました。
今回はログイン後の投稿画面を作ります。
CSRF対策となるワンタイムトークンの設定方法や、投稿一覧画面の表示方法など解説していきます。
実際のサイトは、下記をクリックすると別ウィンドウで確認できます。
↓↓↓
前回までの作り方は、下記ページの目次からご覧ください。
PHPで投稿画面を作る
では投稿画面を作っていきます。
前回と同様、CSSは省き、簡易版を作っていきます。
作成するページの外見は次のようになります。
コードは次のとおり。
【test5.php】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
<?php session_start(); require('dbconnect.php'); // ★ポイント1★ if (isset($_SESSION['id']) && ($_SESSION['time'] + 3600 > time())) { $_SESSION['time'] = time(); $members=$db->prepare('SELECT * FROM members WHERE id=?'); $members->execute(array($_SESSION['id'])); $member=$members->fetch(); } else { header('Location: test4.php'); exit(); } // ★ポイント2★ if (!empty($_POST)){ if (isset($_POST['token']) && $_POST['token'] === $_SESSION['token']) { $message=$db->prepare('INSERT INTO posts SET created_by=?, message=?, created=NOW()'); $message->execute(array($member['id'] , $_POST['message'])); header('Location: test5.php'); exit(); }else { header('Location: test4.php'); exit(); } } // ★ポイント3★ $posts=$db->query('SELECT m.name, p.* FROM members m, posts p WHERE m.id=p.created_by ORDER BY p.created DESC'); $TOKEN_LENGTH = 16; $tokenByte = openssl_random_pseudo_bytes($TOKEN_LENGTH); $token = bin2hex($tokenByte); $_SESSION['token'] = $token; ?> <!DOCTYPE html> <html lang="ja"> <body> <form action='' method="post"> <input type="hidden" name="token" value="<?=$token?>"> <?php if (isset($error['login']) && ($error['login'] =='token')): ?> <p class="error">不正なアクセスです。</p> <?php endif; ?> <div class="edit"> <p><?php echo htmlspecialchars($member['name'], ENT_QUOTES); ?>さん、ようこそ</p> <textarea name="message" cols='50' rows='10'><?php echo htmlspecialchars($message??"", ENT_QUOTES); ?></textarea> </div> <input type="submit" value="投稿する" class="button02"> </form> <?php foreach($posts as $post): ?> <div class="message"> <?php echo htmlspecialchars($post['message'], ENT_QUOTES); ?> <span class="name"><?php echo htmlspecialchars($post['name'], ENT_QUOTES); ?> | <?php echo htmlspecialchars($post['created'], ENT_QUOTES); ?> | <?php endforeach; ?> </body> </html> |
★ポイント★ごとに解説していきます。
ポイント① ログイン状態をチェック
まず最初は、ログイン状態をチェックします。
1 2 3 4 5 6 7 8 9 |
if (isset($_SESSION['id']) && ($_SESSION['time'] + 3600 > time())) { $_SESSION['time'] = time(); $members=$db->prepare('SELECT * FROM members WHERE id=?'); $members->execute(array($_SESSION['id'])); $member=$members->fetch(); } else { header('Location: test4.php'); exit(); } |
「セッションIDがあって、さらにセッション時間が3600秒以内なら データベースのメンバーから、会員情報を取ってきてね。」
とあります。3600秒とは、1時間です。time()は現在の時間です。
条件に合わない場合は、きちんとログインしていない、あるいは操作しないまま放置している状態です。
この場合は、test4.phpに追い出されてしまうことになります。
ポイント② CSRF対策のトークンを設定する
今回はCSRF(シーサーフ)対策のためにトークンを設定します。
CSRFというのは、ユーザーになりすまして投稿したり、物を買ったりといった詐欺のこと。
詳しくはPHPのセキュリティに関する記事で解説しているので、お読みください。
悪用されないようにする方法としてトークンを発行し、セッションに保存するという方法があります。
ユーザーが何か操作する時、セッションに保存したトークンと、送信したトークンが同じでなければ処理を実行しない、という形にするのです。
こうしておけば、本人ではない何者かがユーザーになりすまして操作を行うのを防げます。
コードでは、まず最初の条件式で次のように指定しています。
1 2 3 4 5 6 7 8 9 10 11 |
if (!empty($_POST)){ if (isset($_POST['token']) && $_POST['token'] === $_SESSION['token']) { $message=$db->prepare('INSERT INTO posts SET created_by=?, message=?, created=NOW()'); $message->execute(array($member['id'] , $_POST['message'])); header('Location: test5.php'); exit(); }else { header('Location: test4.php'); exit(); } } |
ここはまず「これは「【$_POST】がカラでなく、何か投稿されていたら」という条件式。
そのあとに
- 「トークンがあって」
- 「セッションに保存してあるトークンと、今回送信するときのトークンがあっていたら」
と条件を指定しています。
条件が合えば、今回投稿したデータがMySQLに保存されます。
ユーザー登録のときと同じように、【INSERT INTO】を使っています。
ふたつのトークンが合わないと、test4.php というログイン前の画面に追い出されます。
なおセッションのトークンと、今回送信するときのトークンの発行は次のように設定します。
セッションのトークン発行
セッションのトークンはどこで発行しているかというと、この少し後の部分です。
1 2 3 |
$toke_byte = openssl_random_pseudo_bytes(16); $token = bin2hex($toke_byte); $_SESSION['token'] = $token; |
今回送信するときのトークン発行
今回送信するときのトークンは、フォーム送信時に発行されるようにします。
htmlのフォームタグの下に下記のように設定してます。
hiddenタイプにしてあるので、ユーザーには見えません。
1 |
<input type="hidden" name="token" value="<?=$token?>"> |
ポイント③ MySQLに保存された投稿データ表示
MySQLに登録された投稿を一覧にして表示します。
1 |
$posts=$db->query('SELECT m.name, p.* FROM members m, posts p WHERE m.id=p.created_by ORDER BY p.created DESC'); |
「MySQLのmembersテーブルから【name】データを取得してね。postsテーブルからはすべてのデータを取得してね。」
という意味になります。
なお、テーブル名は毎回入力するのが面倒。そこでFromの箇所で【members m】と書くことで、membersテーブルはmと省略可能になります。同様に、【posts p】と書くことで、postsテーブルはpと省略可能になります。
さいごに【DESC】とありますが、これは【作成順から逆】という意味。つまり、「古い順ではなく、新しい順にしてね」という意味です。

descend は英語で「下りる」という意味。
英語の知識は、プログラミングでもわりと役立ちます。
取得したデータを表示するため、bodyタグの下のHTMLコードの中で、次のように入力します。
1 2 3 4 5 |
<?php foreach($posts as $post): ?> <div class="message"> <?php echo htmlspecialchars($post['message'], ENT_QUOTES); ?> <span class="name"><?php echo htmlspecialchars($post['name'], ENT_QUOTES); ?> | <?php echo htmlspecialchars($post['created'], ENT_QUOTES); ?> | <?php endforeach; ?> |
foreachを使って投稿データをひとつずつ表示するよう設定しています。
各投稿データの下には、投稿者の名前($post[‘name’])、投稿時間($post[‘created’])を表示させています。
さいごに
今回の山場はワンタイムトークンでした。
他にも、これまでご紹介したMySQLとの連携や、データ挿入もありました。
またForeachを使った投稿一覧の作成など、PHPの知識が必要な部分もあり、盛沢山の内容となりました。
次回は今回作った投稿画面に削除機能を追加して使いやすくしていきます。
コメント