Ajaxでパスワード認証の続き-公開スクリプト
2015/11/05
この記事は過去のセキュリティ的に低い観点で書かれたものです。前回のAjax認証サンプルはコード分量が多いので、この記事で公開することにしました。制作環境は以下の通りでJavaScriptとPHP、MySQLになります。 利用環境 クライアントサイド: JavaScript jQuery jQuery UI サーバーサイド: PHP5 PEAR MySQL
こちらが万全というわけではないですが、Ajaxのパスワード認証についてよりセキュアな視点で書いた記事をアップロードしました(2015/11/05)ので、こちらを参考にされますようお願いします。
⇒セキュアなAjaxパスワード認証とアプリケーションの構築方法
ajax_auth/
|
―――――――――――――
| | |
index.php php js
phpフォルダは以下の通り
php
|
―――――――――――――――――――――――――――――――――――――――
| | | | | | | |
auth-init auth regi logout member-auth member-main member-regi pager
jsフォルダは以下の通り
js
|
―――――――
| |
member sha1
index.php(PHP部分)
<?php header('Content-Type: text/html; charset=utf-8'); /* ログイン認証済みかどうか */ $unauthorized = false; /* 現在時刻をチャレンジIDとして取得 */ $salt = time(); /* ログイン期間を5分間に設定 */ $expire = $salt + (60 * 5); /* クッキーにチャレンジIDを期限付きで保存 */ setcookie("expire", $salt, $expire, "/"); /* 認証済みかどうかで特定の要素を入れ替える */ session_name("member"); session_start(); if (isset($_SESSION["user"])) { /* 認証済みならセッションを再開 */ $user = $_SESSION["user"]; session_regenerate_id(); require_once "php/member-main.php"; } else { $unauthorized = true; require_once "php/member-auth.php"; } ?>
index.php(続くHTML部分)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html lang="ja" xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>メンバー専用ページ - ウィザード・コード | WIZARD-CODE</title> <link rel="shortcut icon" href="http://wiz-code.digick.jp/image/icon/favicon.ico" /> <link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/start/jquery-ui.css" type="text/css" media="screen" /> </head> <body> <!-- 認証がまだなら認証画面を、済んでいればユーザー画面のHTMLをコンテナ要素に挿入する --> <div id="container"><?= $unauthorized === true ? $auth_html : $main_html ?></div> <!-- ダイアログ用の要素 --> <div id="dialog"> </div> <script src="https://www.google.com/jsapi"></script> <script type="text/javascript"> // <![CDATA[ google.load('jquery', '1.5'); google.load('jqueryui', '1.8'); google.setOnLoadCallback(function () { /* 認証済みかどうかで初期化する関数を変更 */ <?= $unauthorized === true ? "authorize();" : "logout();\r\n" ?> }); // ]]> </script> <script type="text/javascript" src="js/member.js" charset="utf-8"></script> <script type="text/javascript" src="js/sha1.js"></script> </body> </html>PHPファイル データベース接続設定
auth-init.php
<?php require_once "DB.php"; $dsn = array( 'phptype' => 'mysql', 'dbsyntax' => false, 'username' => 'wizard-code', 'password' => '12345678', 'protocol' => false, 'hostspec' => 'localhost', 'port' => false, 'socket' => false, 'database' => 'my_database' ); $db = DB::connect( "${dsn['phptype']}://${dsn['username']}:${dsn['password']}@${dsn['protocol']}${dsn['hostspec']}/${dsn['database']}" ); if (DB::isError($db)) { die($db->getMessage()); } $db->query("SET NAMES SJIS"); ?>パスワード認証処理
auth.php
<?php /* HTML出力フォーマット */ header('Content-Type: text/html; charset=utf-8'); /* データベース接続 */ require_once "auth-init.php"; $user = $_GET["username"]; $pass = $_GET["password"]; $salt = $_GET["salt"]; $user = htmlSpecialChars($user); /* メインページのHTML断片 */ require_once "member-main.php"; /* データベースのエンコードに変換 */ mb_convert_variables("SJIS", "UTF-8", $user); mb_convert_variables("SJIS", "UTF-8", $pass); mb_convert_variables("SJIS", "UTF-8", $salt); /* 結果を入れる変数 */ $result = "failed"; /* ログイン画面(index.php)からのみアクセス可に */ if ($referer = $_SERVER["HTTP_REFERER"]) { if ($referer != "http://localhost/ajax_auth/" && $referer != "http://localhost/ajax_auth/index.php") { die("不正なアクセスです。"); } } else { die("リファラーが取得できませんでした。"); } /* クッキーが期限切れかどうかチェック */ if (!isset($_COOKIE["expire"])) { die("expired"); } /* パスワード照合 */ $data = $db->query("SELECT username, password FROM user_auth"); if (!empty($user)) { while ($row = $data->fetchRow()) { $sha1 = sha1($salt . $row[1]); if ($row[0] === $user && $sha1 === $pass) { $result = "successful"; break; } } } else { die($result); } /* 認証が成功したらユーザー画面のHTMLを返す */ if ($result === "successful") { session_name("member"); session_start(); session_regenerate_id(); $_SESSION = array(); $_SESSION["user"] = mb_convert_encoding($user, "UTF-8", "SJIS"); setCookie("expire", "", time() - (60 * 60 * 24), "/"); echo $main_html; } else { die($result); } ?>ユーザー登録処理
regi.php
<?php header('Content-Type: text/html; charset=utf-8'); require_once "auth-init.php"; $user = $_GET["username"]; $pass = $_GET["password"]; $user = htmlSpecialChars($user); require_once "member-main.php"; $result = "failed"; /* ログイン画面(regi.php)からのみアクセス可に */ if ($referer = $_SERVER["HTTP_REFERER"]) { if ($referer != "http://localhost/ajax_auth/" && $referer != "http://localhost/ajax_auth/index.php") { die("不正なアクセスです。"); } } else { die("リファラーが取得できませんでした。"); } mb_convert_variables("SJIS", "UTF-8", $user); mb_convert_variables("SJIS", "UTF-8", $pass); /* ユーザー名の重複確認 */ $duplicated = false; $data = $db->query("SELECT username FROM user_auth"); if (!empty($user)) { while ($row = $data->fetchRow()) { if ($row[0] === $user) { $duplicated = true; break; } } } else { die($result); } /* 認証が成功したらユーザー画面のHTMLを返す */ if (!$duplicated) { $sql = "INSERT INTO user_auth (username, password) VALUES (?, ?)"; $sth = $db->prepare($sql); if (PEAR::isError($sth)) { die($sth->getMessage()); } $res = $db->execute($sth, array(${user}, ${pass})); if (PEAR::isError($res)) { die($res->getMessage()); } session_name("member"); session_start(); session_regenerate_id(); $_SESSION = array(); $_SESSION["user"] = mb_convert_encoding($user, "UTF-8"); setCookie("expire", "", time() - (60 * 60 * 24), "/"); echo $main_html; } else { $result = "duplicated"; die($result); } ?>ログアウト処理
logout.php
<?php header('Content-Type: text/html; charset=utf-8'); $param = $_GET["param"]; /* 現在時刻をチャレンジIDとして取得 */ $salt = time(); /* ログイン期間を5分間に設定 */ $expire = $salt + (60 * 5); /* クッキーにチャレンジIDを期限付きで保存 */ setcookie("expire", $salt, $expire, "/"); /* 認証ページのHTMLを読み込む */ require_once "member-auth.php"; /* ログアウト送信が行われたときの処理 */ if (isSet($param) && $param == "logout") { session_name("member"); session_start(); if (isSet($_COOKIE[session_name()])) { setcookie(session_name(), "", time() - (60 * 60 * 24), "/"); } session_destroy(); echo $auth_html; } ?>メイン画面(入口ページ)HTML
member-auth.php
<?php $auth_html = <<<AUTHAjaxで認証
AUTH; ?> メンバー専用画面HTML
member-main.php
<?php $main_html = <<<MAINMAIN; ?> メンバー登録画面ホームページ
こんにちは${user}さん
メニュー
コンテンツ
member-regi.php
<?php $regi_html = <<<REGIREGI; ?> ページ管理メンバー登録
pager.php
<?php header('Content-Type: text/html; charset=utf-8'); $page = $_GET["page"]; $page = htmlSpecialChars($page); mb_convert_variables("SJIS", "UTF-8", $page); $result = "failed"; switch($page) { case "register": require_once "member-regi.php"; $result = $regi_html; echo $result; break; } function isAuth($name) { $authorized = false; session_name($name); session_start(); if (isset($_SESSION["user"])) { $authorized = true; } return $authorized; } ?>JavaScriptファイル Ajax処理
member.js
if (typeof jQuery !== 'undefined') { /* ダイアログ(jQuery UI)初期化 */ var dialog = jQuery('#dialog'); dialog.dialog({ autoOpen: false, modal: true, title: 'メッセージ確認', position: 'center', minWidth: 400, width: 200 }); /* 認証開始メソッド初期化 */ function authorize() { var form = jQuery('#ajax_auth'), user = form.find('input[type=text]'), salt = form.find('input[type=hidden]'), pass = form.find('input[type=password]'), regi = jQuery('#ajax_regi'), url = 'http://localhost/ajax_auth/php/auth.php'; /* 認証フォーム */ form.submit(function (event) { var query; event.preventDefault(); if (user.val() === '' || pass.val() === '') { dialog.dialog('open').html('ユーザー名かパスワードが入力されていません。'); pass.val(''); return; } pass.val(CybozuLabs.SHA1.calc(salt.val() + pass.val())); query = url + '?' + jQuery(this).serialize(); pass.val(''); jQuery.get( query, null, function (data) { if (/^failed$/.test(data)) { dialog.dialog('open').html('IDかパスワードが間違っています。'); } else if (/^expired$/.test(data)) { dialog.dialog('open').html('ログイン有効期限が過ぎました。リロードしてやり直してください。'); } else if (/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi.test(data)) { dialog.dialog('open').html('ログインに成功しました。'); /* イベントハンドラを削除して要素を入れ替える */ form.unbind('submit'); jQuery('#container').html(data); logout(); } }, 'html' ); }); /* 登録ボタン */ regi.submit(function (event) { var url = 'http://localhost/ajax_auth/php/pager.php'; event.preventDefault(); jQuery.get( url, { page: 'register' }, function (data) { if (/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi.test(data)) { regi.unbind('submit'); jQuery('#container').html(data); register(); } }, 'html' ); }); } /* ログアウト開始メソッド初期化 */ function logout() { var form = jQuery('#member'), url = 'http://localhost/ajax_auth/php/logout.php'; form.submit(function (event) { var query = url + '?' + jQuery(this).serialize(); event.preventDefault(); jQuery.get( query, null, function (data) { dialog.dialog('open').html('ログアウトしました。'); form.unbind('submit'); jQuery('#container').html(data); authorize(); }, 'html' ); }); } /* 登録開始メソッド初期化 */ function register() { var form = jQuery('#register'), user = form.find('input[type=text]'), pass = form.find('input#password'), conf = form.find('input#confirm'), url = 'http://localhost/ajax_auth/php/regi.php'; form.submit(function (event) { var query; event.preventDefault(); if (user.val() === '') { dialog.dialog('open').html('ユーザー名を入力してください。'); return; } else if (pass.val() !== conf.val()) { dialog.dialog('open').html('パスワードが一致しません。再入力をお願いします。'); pass.val(''); conf.val(''); return; } query = url + '?' + jQuery(this).serialize(); pass.val(''); conf.val(''); jQuery.get( query, null, function (data) { if (/^failed$/.test(data)) { dialog.dialog('open').html('登録に失敗しました。'); } else if (/^duplicated$/.test(data)) { dialog.dialog('open').html('そのユーザーIDはすでに使用されています。'); } else if (/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi.test(data)) { dialog.dialog('open').html('登録が完了しました。ユーザーIDとパスワードを忘れないようにしてください。'); form.unbind('submit'); jQuery('#container').html(data); logout(); } }, 'html' ); }); } }暗号化処理
sha1.js こちらのサイトで配布されています。 mitsunari@cybozu labs