【Todoアプリ】ログインページの作成で作成したログイン機能ですが、
だいぶ必要最低限の事しか考慮されていませんでした。
今回は少し、セキュリティ面にも気を配ったログイン機能を作成してみましょう。
使用するコード
使用するソースコードはこちらのzipをダウンロードして、お使いください。
パスワードのハッシュ化
セキュリティの高いログイン機能を作るために、
パスワードをハッシュ化するという技を使います。
ハッシュ化について
登録したパスワードが、DBにそのままの値で表示されるのはセキュリティ上良くないとされます。
そこで、ハッシュ化というのを使うと
値をある一定の法則に従って変換することができます。
例えば「taro」がハッシュ化されると、このように変換されます↓
「$2y$10$Hqm63LDfv5Q.GXgpcWjzhe27ZWqPdzUHQiXB.cyp3eRKgFAgUd0Ki」
これはランダムに変換されているのではなく、何度やっても必ずこうなります。
つまり「taro」と「$2y$10$H…」という文字列は対になっているはずなので、
逆に「$2y$10$H…」から「taro」を戻す事もできます。
こんなイメージ↓
PHP的には、以下の2つを使います。
- 値をランダムな値に変更する処理:password_hash()
- ハッシュ化される前と後の値が等しいのか確認できる:password_verify()
考え方としては、
password_verifyを使って、
ハッシュ化された値と入力された値が一致するか
TUREかFALSEで確認ができる。
↓
TRUEなら処理をする。という仕組みを作る
それでは実際にコードを書いてみましょう。
ユーザの新規登録ページを作成しよう
まず、ハッシュ化して登録する処理を作るために
ユーザを新規登録するための「signUp.php」を作成します。
画面を作成(HTML)
画面はこれまで同様、ログイン画面や編集画面と同じような見た目で大丈夫です。
使うCSSや画像についてはこちらを使用します。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ユーザ新規登録ページ</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="title-area">
<h1>ユーザ新規登録ページ</h1>
</div>
<form action="" method="POST">
<input type="text" class="input-area" name="name" placeholder="Your Name" required> <br>
<input type="password" class="input-area" name="password" placeholder="Your Password" required> <br>
<input type="submit" class="input-area submit" name="submit" value="新規登録">
</form>
</body>
</html>
ロジック作成(signUp.php)
ユーザの新規登録に使うSQLはINSERT文です。
まず、パスワードのハッシュ化は気にせず登録処理を作ってみます。
<?php
// DB接続のファイルを読み込む(db_connectメソッドを使いたいから)
require_once("db_connect.php");
// フォーム送信された情報を受け取る
$name = $_POST["name"];
$password = $_POST["password"];
$submit = $_POST['submit'];
// $submitが空ではない→ログインボタンが押されたと言うこと。
if (!empty($submit)) {
$pdo = db_connect();
try {
// 名前とパスワードを条件にユーザを登録
$sql = "INSERT INTO users (name, password) VALUES (:name, :password)";
$stmt = $pdo->prepare($sql);
$stmt->bindParam(":name", $name);
$stmt->bindParam(":password", $password);
$stmt->execute();
echo "登録処理が完了しました。";
echo '<font color="red">登録処理が完了しました。</font>';
} catch (PDOException $e) {
echo $e->getMessage();
}
}
?>
// この下にHTMLが続きます↓
名前:テスト太郎
パスワード:taro
で登録してみます。
実際にデータベースで登録情報を確認してみましょう。
(SELECT * from users;)
これで新規登録は完成です。
パスワードをハッシュ化してみましょう。
ハッシュ化の処理を作るのですが、実は1行で終わります。
password_hash($password, PASSWORD_DEFAULT);
「password_hash」というのを使います。
第一引数にはハッシュ化させたい値
第二引数にはオプションをつけます
今回オプションには、PASSWORD_DEFAULTを使っています。
そして、「password_hash」した結果を「$password_hash」という変数に入れておきます。
その結果をDBに登録するという流れを作るとこうなります。
// 名前とパスワードを条件にユーザを登録
$sql = "INSERT INTO users (name, password) VALUES (:name, :password)";
$stmt = $pdo->prepare($sql);
$stmt->bindParam(":name", $name);
// パスワードのハッシュ化
$password_hash = password_hash($password, PASSWORD_DEFAULT);
$stmt->bindParam(":password", $password_hash);
$stmt->execute();
echo '<font color="red">登録処理が完了しました。</font>';
処理自体はこれだけで完了です。
DBの構造を変更する必要がある…?
しかし、画面を実行するとうまくいきません。
なぜでしょうか…?
実は、画面にエラーメッセージは表示されているのです。
今回使った画像が見にくくて気づきにくいですが、黒い文字で表示されています。
「SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column ‘password’ at row 1」
このエラーの意味は、
passwordのカラムに登録するデータとして、今から登録しようとしている値は長すぎます。
という内容ですね。
実際にデータベースの登録状況を確認してみましょう。
desc users;
「varchar(8)」とあるので、8文字までしか入らない。という状態になります。
ハッシュ化するとパスワードが長い文字列に変換されるので、登録ができなかったということですね。
解決方法としては、データベースの構造を変更してあげることです。
「varchar(8)」で8文字までしか入らないのであれば、80文字くらい入るように変更します。
ALTER TABLE users MODIFY password varchar(80);
「ALTER TABLE」というのを使います。
一応「desc users;」で確認しておきましょう。
「varchar(80)」に変更されています。
それでは、ユーザの登録をしてみましょう。
今度は登録できたようなので、確認してみましょう。
無事、「taro」の文字列が変換されています!
「taro」でログインできるようにしよう
「taro」をハッシュ化すると、どうやら
「$2y$10$Hqm63LDfv5Q.GXgpcWjzhe27ZWqPdzUHQiXB.cyp3eRKgFAgUd0Ki」
となるようです。
DBには、先ほど確認した通り、この長い値で登録されています。
すると、ログインするときに入力しなければいけない値は、このハッシュ化された長い値になってしまいます。
さすがにそれは困りますね…。
password_verifyを使って対処する!
それでは「login.php」を編集します。
「password_verify」を使うと、
「taro」をハッシュ化すると「$2y$10$Hqm63LDfv5Q.GXgpcWjzhe27ZWqPdzUHQiXB.cyp3eRKgFAgUd0Ki」
に、なるのかどうかをTUREかFALSEで確認することができます。
第一引数に「ハッシュ化される前の値」
第一引数に「ハッシュ化される後の値」
これはそのままif文の条件にすることができます。
- 「taro」で登録
- 「taro」が「$2y$10$H…」に変換されてDBに登録
- password_verifyで「taro」と「$2y$10$H…」が一致するか確認
➡︎ 結果がTRUEかFALSEで返ってくる。 - 一致する(TRUE)ということは、「taro」と「$2y$10$H…」が正しいということ
「login.php」のSELECTした結果、
取得できていれば(TRUEだった場合)リダイレクトする処理をif文として追加します。
// SELECTした結果、取得できればmain.phpにリダイレクト
if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
if (password_verify($password, $row['password'])) {
header("Location: main.php");
exit;
}
} else {
echo '<font color="red">パスワードか名前に間違いがあります。</font>';
}
これで、完成…
かと思いきや、実はうまくいきません。(2回目..)
login.phpをよくみると、この処理より上の行にあるtryの中身を見てください。
try {
// 名前とパスワードを条件にユーザを取得
$sql = "SELECT * FROM users WHERE name = :name AND password = :password";
$stmt = $pdo->prepare($sql);
$stmt->bindParam(':name', $name);
$stmt->bindParam(':password', $password);
$stmt->execute();
}
SQLがパスワードも条件にしていますよね。
これだと、ハッシュ化を元に戻す前なので
入力された「taro」という文字列とDBに登録されている
「$2y$10$H…」を比べることになるので、確実にログインできません。
対策としては、ここではパスワードの判定はしないという方法でいきましょう。
つまり、以下のように書き換えます。
try {
// 名前とパスワードを条件にユーザを取得
$sql = "SELECT * FROM users WHERE name = :name";
$stmt = $pdo->prepare($sql);
$stmt->bindParam(':name', $name);
$stmt->execute();
}
SQLでは名前だけを条件に取得します。
そのため、場合によってはこの時点で複数のユーザが取得できる可能性があります。
しかし、その後の処理でハッシュ化された値と比べます。
その条件を突破したものだけがメインページに遷移できるので、結果としては
名前とパスワードの両方を見ていることになります。
動作確認してみよう
ログインページに行くリンクが無いので、
URLに直接「http://localhost/todo_hash/login.php」と入力して遷移してください。
名前:テスト太郎2
パスワード:taro
でログインしてみます。
ログインできました!
まとめ
- ハッシュ化とは、一定の法則に従ってランダムな値に変換すること
- ハッシュ化するためには「password_hash」
- ハッシュ化される前と後を比べて一致するか確認するためには「password_verify」