PHP 怎么PHP 摘要认证

wen PHP项目 1

本文目录导读:

PHP 怎么PHP 摘要认证

  1. 核心步骤
  2. 完整代码示例
  3. 关键点解释
  4. 如何测试
  5. 注意事项

在 PHP 中实现 HTTP 摘要认证(Digest Authentication) 比基本认证更安全,因为它不会明文传输密码。

PHP 本身提供了清晰的实现方式,主要通过检查 $_SERVER['PHP_AUTH_DIGEST'] 变量,并结合 MD5 哈希计算来完成验证。

以下是一个完整的、可直接运行的实现步骤和代码示例。

核心步骤

  1. 发送 401 状态码:当用户未认证或认证失败时,服务器返回 401 Unauthorized
  2. 生成 Nonce(一次性随机数):防止重放攻击的数字。
  3. 发送 WWW-Authenticate:告诉浏览器使用摘要认证。
  4. 接收客户端响应:浏览器会发送 Authorization 头,PHP 将其存入 $_SERVER['PHP_AUTH_DIGEST']
  5. 解析并验证:解析该字符串,比对服务器端计算的 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;
}
?>

关键点解释

  1. $_SERVER['PHP_AUTH_DIGEST']

    • 这是 PHP 自动解析 Authorization: Digest ... 头后提供的变量,注意不是 PHP_AUTH_USER(那是基本认证用的)。
    • 如果用户没有输入或点击取消,该变量为空。
  2. parseDigestAuth() 函数

    • 浏览器发来的认证字符串是类似 username="admin", realm="...", nonce="...", response="..." 这样的格式。
    • 该函数使用正则将其解析为关联数组。
  3. 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):客户端生成的随机数。
  4. 密码存储

    • 这是关键:摘要认证需要密码的 原始明文HA1 的值(MD5(username:realm:password))才能完成计算。
    • 如果你库中存的是普通 MD5 或 bcrypt 哈希,无法 直接用于摘要认证(除非你同时存储了 HA1),通常为了提高安全性,建议使用 opensslsodium 或在 HTTPS 下使用 Token/Bearer 认证 替代。

如何测试

  1. 将上述代码保存为 digest.php
  2. 放到支持 PHP 的 Web 服务器(如 Apache, Nginx 或 PHP内置服务器 php -S localhost:8000)。
  3. 用浏览器访问 http://localhost:8000/digest.php
  4. 浏览器会弹出一个登录框:
    • 用户名admin
    • 密码123456
  5. 输入正确则看到欢迎信息,错误则再次弹出登录框。

注意事项

  • 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;
      # ... 其他配置
  }

认证的核心在于:

  1. 正确处理 WWW-Authenticate$_SERVER['PHP_AUTH_DIGEST']
  2. 严格按照 MD5 组合公式 验证客户端发来的 response
  3. 注意密码存储方式与摘要认证的兼容性。

如果希望更简单、更现代的方式,推荐使用 Bearer Token (JWT) 结合 HTTPS,比摘要认证更灵活且易于扩展。

抱歉,评论功能暂时关闭!