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
