読者です 読者をやめる 読者になる 読者になる

PHPでDigest認証

お手軽にDigest認証出来るコードが
http://techblog.ecstudio.jp/tech-tips/digestauth.html
で公開されていたので使わせてもらおうと思うけど、気になった点を変更してみた。

  1. ソースコードのインデント
  2. サーバー側で生パスワードは持たない場合にも使えるようにインターフェース変更
  3. 配列インデックシングのエラー対処

しかしDigest認証のユーザー名とパスワードを入力するインターフェースをHTMLで用意出来ないのが残念。何か方法はあるんだろうか?

<?php

/*
	ダイジェスト認証をかける
	
	original : http://techblog.ecstudio.jp/tech-tips/digestauth.html
	
	@param array $auth_list ユーザー情報(複数ユーザー可)
		array(
			"ユーザ名" => md5("ユーザ名" . ':' . $realm . ':' . "ユーザーのパスワード")
		) の形式
	@param string $realm レルム文字列
	@param string $failed_text 認証失敗時のエラーメッセージ
*/
function digest_auth($auth_list, $realm, $failed_text="authentication failed.")
{
//	print_r($_SERVER);
	if (!array_key_exists('PHP_AUTH_DIGEST', $_SERVER)) {
		$headers = getallheaders();
		if (array_key_exists('Authorization', $headers)) {
			$_SERVER['PHP_AUTH_DIGEST'] = $headers['Authorization'];
		}
	}
	
	if (array_key_exists('PHP_AUTH_DIGEST', $_SERVER)) {
		// PHP_AUTH_DIGEST 変数を精査する
		// データが失われている場合への対応
		$needed_parts = array(
					'nonce' => true,
					'nc' => true,
					'cnonce' => true,
					'qop' => true,
					'username' => true,
					'uri' => true,
					'response' => true
				);
		$data = array();
		
		$matches = array();
		preg_match_all('/(\w+)=("([^"]+)"|([a-zA-Z0-9=.\/\_-]+))/', $_SERVER['PHP_AUTH_DIGEST'], $matches, PREG_SET_ORDER);
		
		foreach ($matches as $m) {
			if ($m[3]) {
				$data[$m[1]] = $m[3];
			}else {
				$data[$m[1]] = $m[4];
			}
			unset($needed_parts[$m[1]]);
		}
		
		if ($needed_parts) {
			$data = array();
		}
		
		if (array_key_exists($data['username'], $auth_list)) { // $auth_list[$data['username']]
			// 有効なレスポンスを生成する
			$A1 = $auth_list[$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) {
				unset($_SERVER['PHP_AUTH_DIGEST']);
			}else {
				return $data['username'];
			}
		}
	}
	
	//認証データが送信されているか
	header('HTTP/1.1 401 Authorization Required');
	header('WWW-Authenticate: Digest realm="'.$realm.'", nonce="'.uniqid(rand(),true).'", algorithm=MD5, qop="auth"');
	header('Content-type: text/html; charset='.mb_internal_encoding());
	
	die($failed_text);
}

?>