PHPでログイン認証

Web認証の実験。
 

Basic認証

クライアントは、ユーザ名とパスワードを":"でつないでBase64をかけたものをサーバに送って認証してもらう。Base64は簡単にデコードできるので、パスワードを生のまま送信しているのと何ら変わらないと言っても過言ではない。

<html>
<body>
<?php
if (!isset($_SERVER['PHP_AUTH_USER'])) {
header("WWW-Authenticate: Basic realm=\"Enter user-id and password\"");
header("HTTP/1.0 401 Unauthorized");
echo "ユーザーがキャンセルボタンを押した時に送信されるテキスト\n";
exit;
} else {
echo "<p>こんにちは、{$_SERVER['PHP_AUTH_USER']} さん。</p>";
echo "<p>あなたは、{$_SERVER['PHP_AUTH_PW']} をパスワードとして入力しました。</p>";
}
?>
</body>
</html>
 
 アクセスすると、401レスポンスが返り、下記の認証画面が表示された。

f:id:ittokoton:20190304235029p:plain

 ユーザー名、パスワードに、それぞれ "aa" , "bb" と入力してOKボタンを押した。
------------------------------------------------------------------------

f:id:ittokoton:20190304235302p:plain

 ------------------------------------------------------------------------

 

 

■Digest認証

Basic認証と似てる仕組みだが、ユーザ名、パスワードがほぼ生で送られるBasic認証とは違い、MD5のハッシュを送るところが改善されている。

<html>
<body>
<?php
$realm = 'Restricted area';

//user => password
$users = array('admin' => 'mypass', 'guest' => 'guest');


if (empty($_SERVER['PHP_AUTH_DIGEST'])) {
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Digest realm="'.$realm.
'",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');

die('ユーザーがキャンセルボタンを押した時に送信されるテキスト');
}


// PHP_AUTH_DIGEST 変数を精査する
if (!($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) ||
!isset($users[$data['username']]))
die('誤った証明書です!');


// 有効なレスポンスを生成する
$A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]);
$A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);
$valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);

if ($data['response'] != $valid_response)
die('誤った証明書です!');

// OK, 有効なユーザー名とパスワードだ
echo 'あなたは次のユーザーとしてログインしています: ' . $data['username'];

// http auth ヘッダをパースする関数
function http_digest_parse($txt)
{
// データが失われている場合への対応
$needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1);
$data = array();
$keys = implode('|', array_keys($needed_parts));

preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', $txt, $matches, PREG_SET_ORDER);

foreach ($matches as $m) {
$data[$m[1]] = $m[3] ? $m[3] : $m[4];
unset($needed_parts[$m[1]]);
}

return $needed_parts ? false : $data;
}
?>
</body>
</html>

 

■実際のリクエストとレスポンス

・1回目のレスポンス(応答コードが401になる)中のWWW-Authenticateヘッダ
WWW-Authenticate:
 Digest
 realm="Restricted area",
 qop=auth,
 nonce=5c7ddc287460a,
 opaque=cdce8a5c95a1427d74df7acbf41c9ce0

 

・2回目のリクエストヘッダ中のAuthorizationヘッダ
Authorization:
 Digest
 username=guest,
 realm="Restricted area",
 nonce=5c7ddc287460a,
 uri="/test/test.php",
 cnonce=4d84416b3a39fa935b1a8d3346679861,
 nc=00000001,
 response=c82457419ca13037779bfd670826d337,
 qop=auth,
 opaque=cdce8a5c95a1427d74df7acbf41c9ce0

 

 ■解説 

・nonce

 number of once。一度のみ使われる数字の意味。サーバ側でリクエスト毎に変化するランダム文字列を生成し、クライアントへ送る。クライアントはそのままサーバへ送り返す。

 

・cnonce

 クライアント側で生成するランダムな文字列。平文のままサーバに送られる。選択平文攻撃を防ぐ目的で存在する。

 

・opaque

 サーバ側で生成するランダム文字列。クライアントへ送信すると、そのまま送り返されてくる。nonceとは違い、リクエスト毎に違う値にしなくても良い。負荷分散先を入れるなどの使い道を想定されたもの。

 

・qop

 quality of protection。サポートする認証の強化方式を指定する。"auth", "auth-init"などのリスト。サーバがサポートしているものをクライアントへ提示する。クライアントはこの中から自分がサポートしているものを1つ選ぶ。

 ・auth : メソッドとURIからダイジェストを作成する
 ・auth-init : メソッドとURIとメッセージボディからダイジェストを作成する

 

・nc

 nonce-count。特定のnonceを使ってクライアントが送ったリクエスト数。00000001から始まりカウントアップされる。qopがない場合は省略する。 

 

・response

 クライアントは、A1とA2を生成し、responceを生成する。

A1 = usernamme:realm:pass
A2 = HTTPのメソッド:コンテンツのURI
response = MD5(MD5(A1):nonce:nc:cnonce:qop:MD5(A2))

 

 

 

---------------------------------------
 ■ITとことんのトップページ
 ┗■PHPのトップページ
   ┗■本ページ