PHPとMySQLを使っていちから掲示板を作ってみる講座、6回目になりました。
今回はログイン後の投稿画面を作ります。次のような画面を作ります。
CSRF対策となるワンタイムトークンの設定方法や、投稿一覧画面の表示方法など解説していきます。
「どんな掲示板ができるのかな。」と思ったら、下記の紹介動画をクリックしてくださいね。
↓↓↓
なお記事では掲示板に必要なコードはご紹介しますが、デザイン部分は省略しています。
デザイン部分も含めた形で、GitHubにコード公開しています。全体のコードを見たい時に、参考にしてください。
いいなと思ったら、GitHub右上のStarボタン、クリックしてもらえると、嬉しいです。
前回までの内容
掲示板作成の7ステップの目次と概要はこちらをご覧ください。
- 概要説明
- ① 設計図を作って、データベースの準備をしよう
- ②会員登録画面をつくる 前編
- ③会員登録画面をつくる 後編
- ④会員登録確認画面をつくる
- ⑤ログイン画面をつくる
- ⑥投稿画面をつくる⇒今ここ
- ⑦削除画面をつくる
PHPで投稿画面を作る
では投稿画面を作っていきます。
まずはC:\xampp\htdocs\forumの中に、post.phpファイルを作成します。この中に、下記コードを追加します。
【C:\xampp\htdocs\forum\post.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 67 68 69 70 71 72 73 74 75 76 77 78 79 |
<?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: login.php'); exit(); } // ★ポイント2★ if (!empty($_POST)) { if (isset($_POST['token']) && $_POST['token'] === $_SESSION['token']) { $post=$db->prepare('INSERT INTO posts SET created_by=?, post=?, created=NOW()'); $post->execute(array($member['id'] , $_POST['post'])); header('Location: post.php'); exit(); } else { header('Location: login.php'); exit(); } } // ★ポイント3★ $posts=$db->query('SELECT m.name, p.* FROM members m JOIN posts p ON 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> <!-- ★ログアウト★ --> <header> <div class="head"> <h1>週末プラン投稿画面</h1> <span class="logout"><a href="login.php">ログアウト</a></span> </div> </header> <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="post" cols='50' rows='10'><?php echo htmlspecialchars($post??"", ENT_QUOTES); ?></textarea> </div> <input type="submit" value="投稿する" class="button02"> </form> <?php foreach($posts as $post): ?> <div class="post"> <?php echo htmlspecialchars($post['post'], ENT_QUOTES); ?> | <span class="name"> <?php echo htmlspecialchars($post['name'], ENT_QUOTES); ?> | <?php echo htmlspecialchars($post['created'], ENT_QUOTES); ?> | </span> </div> <?php endforeach; ?> </body> </html> |
★ポイント★ごとに解説していきます。
ポイント① ログイン状態をチェック
まず最初は、ログイン状態をチェックします。
1 2 3 4 5 6 7 8 9 10 11 |
// ★ポイント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: login.php'); exit(); } |
「$_SESSION[‘id’]があって、さらにセッション時間が3600秒以内なら データベースのmembersテーブルメンバーから、$_SESSION[‘id’] と一致するmember情報を取ってきてね。」としています。
前回のポイント①でも、同じような形で、データベースからデータを取得しましたね。
なお3600秒とは、1時間です。time()は現在の時間です。
条件に合わない場合は、きちんとログインしていない、あるいは操作しないまま放置している状態です。この場合は、login.phpに追い出されてしまうことになります。
ポイント② CSRF対策のトークンを設定する
今回はCSRF(シーサーフ)対策のためにトークンを設定します。
CSRFというのは、ユーザーになりすまして投稿したり、物を買ったりといった詐欺のこと。
詳しくはPHPのセキュリティに関する記事で解説しているので、お読みください。
悪用されないようにする方法としてトークンを発行し、セッションに保存するという方法があります。
ユーザーが何か操作する時、セッションに保存したトークンと、送信したトークンが同じでなければ処理を実行しない、という形にするのです。
こうしておけば、本人ではない何者かがユーザーになりすまして操作を行うのを防げます。
コードでは、次のように指定しています。
1 2 3 4 5 6 7 8 9 10 11 12 |
// ★ポイント2★ if (!empty($_POST)) { if (isset($_POST['token']) && $_POST['token'] === $_SESSION['token']) { $post=$db->prepare('INSERT INTO posts SET created_by=?, post=?, created=NOW()'); $post->execute(array($member['id'] , $_POST['post'])); header('Location: post.php'); exit(); } else { header('Location: login.php'); exit(); } } |
まず「これは「$_POSTが空ではなく、何か投稿されていたら」という条件式。
そのあとに
- 「トークンがあって」
- 「セッションに保存してあるトークンと、今回送信するときのトークンがあっていたら」
と条件を指定しています。
条件が合えば、今回投稿したデータがデータベースに保存されます。
メンバーの登録時と同様、【INSERT INTO】を使っています。
ふたつのトークンが合わないと、login.php というログイン画面に追い出されます。
なおセッションのトークンと、今回送信するときのトークンの発行は次のように設定します。
セッションのトークン発行
セッションのトークンはどこで発行しているかというと、この少し後の部分です。
1 2 3 4 |
$TOKEN_LENGTH = 16; $tokenByte = openssl_random_pseudo_bytes($TOKEN_LENGTH); $token = bin2hex($tokenByte); $_SESSION['token'] = $token; |
今回送信するときのトークン発行
今回送信するときのトークンは、フォーム送信時に発行されるようにします。
htmlのフォームタグの下に下記のように設定してます。
hiddenタイプにしてあるので、ユーザーには見えません。
1 |
<input type="hidden" name="token" value="<?=$token?>"> |
ポイント③ データベースに保存された投稿データ表示
データベースに登録された投稿を一覧にして表示します。
そのために、下記コードで、データベースからデータを取得しています。
1 |
$posts=$db->query('SELECT m.name, p.* FROM members m JOIN posts p ON m.id=p.created_by ORDER BY p.created DESC'); |
membersテーブルとpostsテーブルを結合しています。結合条件は、membersのidカラムと、postsのcreated_byが等しいことです。
これにより、各投稿について、作成者の情報も取得できるようになります。
また、’m.name’、’p.*’という指定があります。これは、’members’テーブルの’name’カラムと、’posts’テーブルの全てのカラムを取得することを意味しています。
最後に、ORDER BYを使用して、投稿の作成日時に基づいて結果を降順(新しい順)に並べ替えます。
取得したデータを表示するため、bodyタグの下のHTMLコードの中で、次のように入力します。
1 2 3 4 5 6 7 8 9 10 |
<?php foreach($posts as $post): ?> <div class="post"> <?php echo htmlspecialchars($post['post'], ENT_QUOTES); ?> | <span class="name"> <?php echo htmlspecialchars($post['name'], ENT_QUOTES); ?> | <?php echo htmlspecialchars($post['created'], ENT_QUOTES); ?> | </span> </div> <?php endforeach; ?> |
foreachを使って投稿データをひとつずつ表示するよう設定しています。
各投稿データには、投稿者の名前($post[‘name’])、投稿時間($post[‘created’])を表示させています。
テスト
今の段階で一度、テストしてみましょう。
XAMPPを起動した状態で、下記のURLを開きます。
データベースに登録済みのmember情報をいれてログインした後、登録をしてみましょう。
さいごに
今回の山場はワンタイムトークンでした。
他にも、これまでご紹介したデータベースからのデータ取得もありました。取得したデータをforeachを使って一覧表示したりして、盛沢山の内容となりました。
掲示板、大分、形になってきましたね。
ただ今のままだと、投稿を削除できません。うっかり間違えて投稿すると、困ったことになります。そこで、次回は今回作った投稿画面に削除機能を追加して使いやすくしていきます。
次回が最終回の予定です。掲示板、仕上げていきましょう。