本文目录导读:

在 PHP 中实现 HTTP 摘要认证(Digest Authentication) 比基本认证更安全,因为它不会明文传输密码。
PHP 本身提供了清晰的实现方式,主要通过检查 $_SERVER['PHP_AUTH_DIGEST'] 变量,并结合 MD5 哈希计算来完成验证。
以下是一个完整的、可直接运行的实现步骤和代码示例。
核心步骤
- 发送 401 状态码:当用户未认证或认证失败时,服务器返回
401 Unauthorized。 - 生成 Nonce(一次性随机数):防止重放攻击的数字。
- 发送
WWW-Authenticate头:告诉浏览器使用摘要认证。 - 接收客户端响应:浏览器会发送
Authorization头,PHP 将其存入$_SERVER['PHP_AUTH_DIGEST']。 - 解析并验证:解析该字符串,比对服务器端计算的 hash 值与客户端发来的 hash 值。
完整代码示例
<?php
// 1. 模拟的用户数据(实际应查数据库)
$users = [
'admin' => '123456', // 明文密码,实际应存哈希,但摘要认证需要原始密码计算
'user1' => 'password'
];
// 2. 检查是否收到了Authorization头(摘要认证的响应)
if (empty($_SERVER['PHP_AUTH_DIGEST'])) {
// 未认证 -> 发送挑战(Challenge)
$realm = 'Restricted Area'; // 领域名称,浏览器会显示
$nonce = md5(uniqid(mt_rand(), true)); // 生成nonce
$opaque = md5($realm); // opaque可选,用于客户端计算
// 发送401状态码和WWW-Authenticate头
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Digest realm="' . $realm .
'", qop="auth", nonce="' . $nonce .
'", opaque="' . $opaque . '"');
echo '认证被取消或失败。'; // 用户点击取消时显示
exit;
} else {
// 3. 有认证信息,开始验证
// 解析浏览器发来的 Authorization 头
$authData = parseDigestAuth($_SERVER['PHP_AUTH_DIGEST']);
if (!$authData) {
header('HTTP/1.1 401 Unauthorized');
exit('Invalid authorization header.');
}
// 检查用户名是否存在
if (!isset($users[$authData['username']])) {
header('HTTP/1.1 401 Unauthorized');
exit('Username not found.');
}
// 获取该用户的密码
$password = $users[$authData['username']];
$realm = 'Restricted Area';
// 4. 服务器端计算应有的 response 值
// 公式:HA1 = MD5(username:realm:password)
$A1 = md5($authData['username'] . ':' . $realm . ':' . $password);
// HA2 = MD5(method:uri)
$method = $_SERVER['REQUEST_METHOD'];
$A2 = md5($method . ':' . $authData['uri']);
// response = MD5(HA1:nonce:nc:cnonce:qop:HA2)
$validResponse = md5($A1 . ':' . $authData['nonce'] . ':' .
$authData['nc'] . ':' .
$authData['cnonce'] . ':' .
$authData['qop'] . ':' . $A2);
// 5. 比较客户端发来的 response 和我们计算的 validResponse
if ($authData['response'] === $validResponse) {
// 认证成功
echo '欢迎, ' . htmlspecialchars($authData['username']) . '!你已通过摘要认证。';
// 这里可以开始你的业务逻辑
} else {
// 密码错误
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Digest realm="' . $realm .
'", qop="auth", nonce="' . md5(uniqid()) .
'", opaque="' . md5($realm) . '"');
exit('认证失败,密码错误。');
}
}
/**
* 解析 HTTP Digest 认证的 Authorization 头字符串
* @param string $header Authorization 头的值
* @return array|false 解析后的关联数组,失败返回 false
*/
function parseDigestAuth($header) {
// 确保是 Digest 认证
if (strpos($header, 'Digest ') !== 0) {
return false;
}
$header = substr($header, 7); // 去掉 "Digest "
$needed_parts = [
'username', 'realm', 'nonce', 'uri',
'qop', 'response', 'nc', 'cnonce'
];
$data = [];
// 使用正则提取键值对(处理引号和逗号)
preg_match_all('/(\w+)=["\']?([^"\',\s]+)["\']?,?\s*/', $header, $matches, PREG_SET_ORDER);
foreach ($matches as $m) {
$data[$m[1]] = $m[2];
}
// 检查必要的字段是否都存在
foreach ($needed_parts as $part) {
if (!isset($data[$part])) {
return false;
}
}
return $data;
}
?>
关键点解释
-
$_SERVER['PHP_AUTH_DIGEST']- 这是 PHP 自动解析
Authorization: Digest ...头后提供的变量,注意不是PHP_AUTH_USER(那是基本认证用的)。 - 如果用户没有输入或点击取消,该变量为空。
- 这是 PHP 自动解析
-
parseDigestAuth()函数- 浏览器发来的认证字符串是类似
username="admin", realm="...", nonce="...", response="..."这样的格式。 - 该函数使用正则将其解析为关联数组。
- 浏览器发来的认证字符串是类似
-
response的计算(最核心的安全逻辑)- 客户端发来
response字段,服务器需要验证它是否正确。 - 计算公式(RFC 2617):
HA1 = MD5(username:realm:password) HA2 = MD5(method:uri) response = MD5(HA1:nonce:nc:cnonce:qop:HA2) nc(nonce count):请求计数器,防重放。cnonce(client nonce):客户端生成的随机数。
- 客户端发来
-
密码存储
- 这是关键:摘要认证需要密码的 原始明文 或
HA1的值(MD5(username:realm:password))才能完成计算。 - 如果你库中存的是普通 MD5 或 bcrypt 哈希,无法 直接用于摘要认证(除非你同时存储了 HA1),通常为了提高安全性,建议使用
openssl、sodium或在 HTTPS 下使用 Token/Bearer 认证 替代。
- 这是关键:摘要认证需要密码的 原始明文 或
如何测试
- 将上述代码保存为
digest.php。 - 放到支持 PHP 的 Web 服务器(如 Apache, Nginx 或 PHP内置服务器
php -S localhost:8000)。 - 用浏览器访问
http://localhost:8000/digest.php。 - 浏览器会弹出一个登录框:
- 用户名:
admin - 密码:
123456
- 用户名:
- 输入正确则看到欢迎信息,错误则再次弹出登录框。
注意事项
- HTTPS 是必须的:摘要认证防重放但仍有中间人攻击风险,始终使用 HTTPS。
realm字段:应该与服务器配置一致,否则客户端可能不发送认证头。qop="auth":是常用选项,支持完整性保护。- 默认情况下 Apache/Nginx 可能干扰:使用 FastCGI (PHP-FPM) 时,有些配置可能导致
PHP_AUTH_DIGEST未设置,如果遇到问题,检查.htaccess或 Nginx 配置是否传递了认证头。
Nginx 配置示例(如果需要):
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_param PHP_AUTH_DIGEST $http_authorization;
# ... 其他配置
}
认证的核心在于:
- 正确处理 WWW-Authenticate 和
$_SERVER['PHP_AUTH_DIGEST']。 - 严格按照 MD5 组合公式 验证客户端发来的
response。 - 注意密码存储方式与摘要认证的兼容性。
如果希望更简单、更现代的方式,推荐使用 Bearer Token (JWT) 结合 HTTPS,比摘要认证更灵活且易于扩展。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播