はじめに
お目汚し失礼いたします。ど素人の勉強ノートです。
使っているレンタルサーバーのPHPバージョンが5.4だったのでpassword_hashが使えず。
徳丸浩さんの『安全なwebアプリケーションの作り方』のサンプルを参照しています。
ご指摘いただけるととても嬉しいです。よろしくお願いします。
・登録内容は、「名前」「パスワード」「登録日」「プロフ画像(リサイズ)」など。
・idがユニークかどうかのチェック
・パスワード確認
自分メモ:もし、画像を登録しなかった時にその他もインサートできていなかったら、mysqlのnullのチェック欄を確認。__チェックを入れて__おくこと。
登録ページ
register.php
<?php
session_start();
require_once 'dbmanager.php';//データベース接続
define('FIXEDSALT', '123456789abcdef');
define('STRETCHCOUNT', 1000);
// 初期化
$name = '';
$password = '';
$errors = array();
if(!empty($_POST)) {
if (!isset($_POST['name']) || $_POST['name'] === '') {
$errors['name'] = 'ニックネームが入力されていません。';
} else {
$name = htmlspecialchars($_POST['name'], ENT_QUOTES);
$pdo = getDb();
$stmt = $pdo->prepare('select name from members where name = :name');
$stmt->bindParam(':name', $name, PDO::PARAM_STR);
$stmt->execute();
$row = $stmt->fetchAll();
$count = count($row);
if ($count > 0) {
$errors['name2'] = $name.'はすでに使用されているニックネームです。';
}
}
if (!isset($_POST['password']) || $_POST['password'] === '') {
$errors['password'] = 'パスワードが入力されていません。';
} else {
if (strlen($_POST['password']) < 4){
$errors['passwordlength'] = 'パスワードは4文字以上で入力してください。';
} else {
$password = htmlspecialchars($_POST['password'], ENT_QUOTES);
}
}
if (!isset($_POST['password2']) || $_POST['password2'] === '') {
$errors['password2'] = '確認用パスワードを入力してください。';
} else {
$password2 = htmlspecialchars($_POST['password2'], ENT_QUOTES);
if ($password != $password2){
$errors['password12'] = 'パスワードが一致していません。';
} else {
// パスワードをハッシュする
function get_salt($id){
return $id . pack('H*', FIXEDSALT);
}
function get_password_hash($id, $pwd){
$salt = get_salt($id);
$hash = '';
for ($i = 0; $i < STRETCHCOUNT; $i++){
$hash = hash('sha256', $hash . $pwd . $salt);
}
return $hash;
}
$password = get_password_hash($name, $password);
}
}
}
if (isset($_FILES['upfile']['error']) && is_int($_FILES['upfile']['error'])) {
try {
// $_FILES['upfile']['error'] の値を確認
switch ($_FILES['upfile']['error']) {
case UPLOAD_ERR_OK: // OK
break;
case UPLOAD_ERR_NO_FILE: // ファイル未選択
throw new RuntimeException('ファイルが選択されていません');
case UPLOAD_ERR_INI_SIZE: // php.ini定義の最大サイズ超過
case UPLOAD_ERR_FORM_SIZE: // フォーム定義の最大サイズ超過
throw new RuntimeException('ファイルサイズが大きすぎます');
default:
throw new RuntimeException('その他のエラーが発生しました');
}
// $_FILES['upfile']['mime']の値はブラウザ側で偽装可能なので、MIMEタイプを自前でチェックする
$filename = $_FILES['upfile']['tmp_name'];
if (!$info = getimagesize($filename)){
throw new RuntimeException('有効な画像ファイルを指定してください', 400);
}
if(!in_array($info[2], [IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG], true)){
throw new RuntimeException('未対応の画像形式です', 400);
}
list($width, $height) = $info;
// 縦横比を維持したまま 120 * 120 以下に収まるサイズを求める
if ($info[0] >= $info[1]) {
$new_width = 120;
$new_height = ceil(120 * $info[1] / max($info[0], 1));
} else {
$new_width = ceil(120 * $info[0] / max($info[1], 1));
$new_height = 120;
}
$image_p = imagecreatetruecolor($new_width, $new_height);
// 画像処理に使う関数名を決定する
$create = str_replace('/', 'createfrom', $info['mime']);
$output = str_replace('/', '', $info['mime']);
$image = $create($filename);
imagecopyresampled($image_p, $image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
ob_start();
$output($image_p);
$img = ob_get_contents();
$_SESSION['img'] = $img;
$_SESSION['type'] = $info['mime'];
ob_end_clean();
$msg = ['green', 'ファイルは正常に読み込まれています。'];
} catch (RuntimeException $e) {
$msg = ['red', $e->getMessage()];
}
}
if(isset($_GET['action']) && $_GET['action'] === 'edit'){
$name = $_SESSION['name'];
}
if(count($errors) === 0){
$_SESSION['name'] = $name;
$_SESSION['password'] = $password;
}
$date = new DateTime();
$date = $date->format('Y-m-d H:i:s');
if(!empty($_POST['register'])){
require_once 'dbmanager.php';
try{
$pdo = getDb();
$sql = 'INSERT INTO members(name,password, picture, type, created) VALUES(:name, :password, :picture, :type, :created)';
$stmt = $pdo->prepare($sql);
$stmt->bindParam(':name', $_SESSION['name'], PDO::PARAM_STR);
$stmt->bindParam(':password', $_SESSION['password'], PDO::PARAM_STR);
$stmt->bindParam(':picture', $_SESSION['img'], PDO::PARAM_STR);
$stmt->bindParam(':type', $_SESSION['type'], PDO::PARAM_STR);
$stmt->bindValue(':created', $date, PDO::PARAM_STR);
$stmt->execute();
header('Location: thanks.php');
} catch (PDOException $e) {
echo $e->getMessage();
}
}
?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>登録画面</title>
</head>
<body>
<?php
echo "<ul>";
foreach($errors as $value){
echo"<li>";
echo $value;
echo"</li>";
}
echo "</ul>";
?>
<?php if (!isset($_POST['submit']) || $_POST['submit'] === '') : ?>
<form enctype="multipart/form-data" method="post" action="">
<dl>
<dt>ニックネーム<span class="required">必須</span></dt>
<dd><input type="text" name="name" size="35" maxlength="255" value="<?php if(isset($name)){ echo $name; } ?>">
</dd>
<dt>ログインパスワード<span class="required">必須</span></dt>
<dd><input type="password" name="password" size="10" maxlength="20" value=""></dd>
<dt>確認用パスワード<span class="required">必須</span></dt>
<dd><input type="password" name="password2" size="10" maxlength="20" value=""></dd>
<dt>プロフィール画像など(GIF, JPEG, PNGのみ対応)</dt>
<dd><input type="file" name="upfile" /></dd>
<p><input type="submit" value="submit" name="submit"/></p>
</dl>
</form>
<?php elseif( count($errors) > 0) : ?>
<form enctype="multipart/form-data" method="post" action="">
<dl>
<dt>ニックネーム<span class="required">必須</span></dt>
<dd><input type="text" name="name" size="35" maxlength="255" value="<?php if(isset($name)){ echo $name; } ?>">
</dd>
<dt>ログインパスワード<span class="required">必須</span></dt>
<dd><input type="password" name="password" size="10" maxlength="20" value=""></dd>
<dt>確認用パスワード<span class="required">必須</span></dt>
<dd><input type="password" name="password2" size="10" maxlength="20" value=""></dd>
<dt>プロフィール画像など(GIF, JPEG, PNGのみ対応)</dt>
<dd><input type="file" name="upfile" /></dd>
<p><input type="submit" value="submit" name="submit"/></p>
</dl>
</form>
<?php else: ?>
<form action="" method="post" enctype="multipart/form-data">
<dl>
<dt>ニックネーム<span class="required">必須</span></dt>
<dd><?php if(isset($name)){ echo $name; } ?></dd>
<dt>ログインパスワード<span class="required">必須</span></dt>
<dd>【表示されません】</dd>
<dt>プロフィール画像など</dt>
<dd>
<?php if (isset($info)): ?>
<img src="UseImg.php" ><br>
<?php endif; ?>
<?php if (isset($msg)): ?>
<span style="color:<?=$msg[0]?>;"><?=$msg[1]?></span>
<?php endif; ?>
</dd></dl>
<div><a href="?action=edit">« 書き直す</a>|<input type="submit" name="register" value="登録する" /></div>
</form>
<?php endif; ?>
<?php
print $password;
?>
</body>
</html>
ログインページ
login.php
<?php
session_start();
require_once 'dbmanager.php';// データベース接続
$pdo = getDb();
// エラーメッセージの初期化
$errorMessage = "";
// ログインボタンが押された場合
if (isset($_POST["login"])) {
// 1.ユーザIDの入力チェック
if (empty($_POST["name"])) {
$errorMessage = "ユーザIDが未入力です。";
} else if (empty($_POST["password"])) {
$errorMessage = "パスワードが未入力です。";
}
// 2.ユーザIDとパスワードが入力されていたら認証する
if (!empty($_POST["name"]) && !empty($_POST["password"])) {
// mysqlへの接続
$pdo = getDb();
// クエリの実行
$stmt = $pdo->prepare('SELECT * FROM members WHERE name = :name');
$stmt->bindParam(':name', $name, PDO::PARAM_STR);
$stmt->execute();
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
// パスワード(暗号化済み)の取り出し
var_dump($row['password']);
}
// データベースの切断
$db= NULL;
// 3.画面から入力されたパスワードとデータベースから取得したパスワードのハッシュを比較
$name = htmlspecialchars($_POST['name'], ENT_QUOTES);
$pdo = getDb();
$stmt = $pdo->prepare('select password from members where name = :name');
$stmt->bindParam(':name', $name);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$hash = $row['password'];
function get_salt($id){
return $id . pack('H*', FIXEDSALT);
}
function get_password_hash($id, $pwd){
$salt = get_salt($id);
$hash = '';
for ($i = 0; $i < STRETCHCOUNT; $i++){
$hash = hash('sha256', $hash . $pwd . $salt);
}
return $hash;
}
$password = htmlspecialchars($_POST['password'], ENT_QUOTES);
$password = get_password_hash($name, $password);
if ( $password != $hash){
// 認証失敗
$errorMessage = "ユーザIDあるいはパスワードに誤りがあります。";
} else {
// 4.認証成功なら、セッションIDを新規に発行する
session_regenerate_id(true);
$_SESSION["name"] = $_POST["name"];
if(!empty($_SESSION['return'])){
$url = $_SESSION['return'];
header("Location: $url");
exit;
} else{
header("Location: main.php");
exit;
}
}
} else {
// 未入力なら何もしない
}
}
?>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>ログインページ</title>
</head>
<body>
<h1>ログイン機能 サンプルアプリケーション</h1>
<!-- $_SERVER['PHP_SELF']はXSSの危険性があるので、actionは空にしておく -->
<!--<form id="loginForm" name="loginForm" action="<?php print($_SERVER['PHP_SELF']) ?>" method="POST">-->
<form id="loginForm" name="loginForm" action="" method="POST">
<fieldset>
<legend>ログインフォーム</legend>
<div><?php echo $errorMessage ?></div>
<label for="name">ニックネーム</label><input type="text" id="name" name="name" value="
<?php
if(isset($_POST["name"])){
$name = htmlspecialchars($_POST["name"], ENT_QUOTES);
echo $name;
}
?>
">
<br>
<label for="password">パスワード</label><input type="password" id="password" name="password" value="">
<br>
<input type="submit" id="login" name="login" value="ログイン">
</fieldset>
</form>
</body>
</html>
参考サイト
・【初心者向け】PHP5とMySQLでつくるログイン機能のサンプルアプリケーション
・PHP+MySQLで簡易画像アップローダ
・徳丸浩『安全なWebアプリケーションの作り方』(2014年, SBクリエイティブ)