WIZ-CODE.blog

JavaScriptやAjaxをテーマとしたブログです。

*

Ajaxでパスワード認証の続き-公開スクリプト

      2015/11/05

この記事は過去のセキュリティ的に低い観点で書かれたものです。
こちらが万全というわけではないですが、Ajaxのパスワード認証についてよりセキュアな視点で書いた記事をアップロードしました(2015/11/05)ので、こちらを参考にされますようお願いします。
⇒セキュアなAjaxパスワード認証とアプリケーションの構築方法
前回のAjax認証サンプルはコード分量が多いので、この記事で公開することにしました。制作環境は以下の通りでJavaScriptPHPMySQLになります。 利用環境 クライアントサイド: JavaScript jQuery jQuery UI サーバーサイド: PHP5 PEAR MySQL

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 = <<<AUTH

Ajaxで認証


 
ID: PASSWORD:
AUTH; ?>  

メンバー専用画面HTML

member-main.php

 
<?php
$main_html = <<<MAIN

ホームページ

こんにちは${user}さん

メニュー

コンテンツ

MAIN; ?>  

メンバー登録画面

member-regi.php

 
<?php
$regi_html = <<<REGI

メンバー登録

ID入力:

パスワード入力:

パスワード確認:

REGI; ?>  

ページ管理

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

 - JavaScript/Ajax , , , , , ,