我们相信:世界是美好的,你是我也是。平行空间的世界里面,不同版本的生活也在继续...

401认证就是从服务器端发出一个401状态码,然后由客户端浏览器来处理登录框的问题。但是,服务器端能够自定义登录的认证方式为realm,还是digest realm。整体上来说,两者的区别就是数据加密方式的不同,而这件事情对于用户来说,无感。

苏南大叔:php如何处理401状态码,完成Digest realm授权认证闭环? - 完成Digest realm授权认证闭环
php如何处理401状态码,完成Digest realm授权认证闭环? (图3-1)

苏南大叔的“程序如此灵动”博客,记录苏南大叔的编程经验文章。本文讨论以php的方式,如何完成“digest realm”的认证闭环?测试环境:win10nginx@1.15.11php@8.2.9ntschrome@121.0.6167.140

发出401状态[digest]

nginx/apache等,可以发出401状态,php/java等服务器端语言也可以发出401状态码。本文以php方法发出digest认证方式请求。代码如下:

$realm = "My Realm Name";
header('WWW-Authenticate: Digest realm="'.$realm.'",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
header('HTTP/1.0 401 Unauthorized');

苏南大叔:php如何处理401状态码,完成Digest realm授权认证闭环? - 401登录框
php如何处理401状态码,完成Digest realm授权认证闭环? (图3-2)

发送的数据

客户端浏览器发生的认证数据,就是客户端传递的header里面的Authorization。它可以使用php$_SERVER['PHP_AUTH_DIGEST']进行获取。

本文的实际传递的header值是:

Digest username="admin", realm="My Realm Name", nonce="65cc9dbb4059e", uri="/1.php", response="1413e1b163f3e2a8b2d374cfd110b14f", opaque="b598c5a20fb87712f3eb9c8f269e00b1", qop=auth, nc=00000002, cnonce="fed529ce0017ee1e"

苏南大叔:php如何处理401状态码,完成Digest realm授权认证闭环? - header头
php如何处理401状态码,完成Digest realm授权认证闭环? (图3-3)

分解认证数据

digest realm方式比realm方式先进的地方就是:无法分解处理密码明文。只能拿到:

  • username,用户名。
  • uri,发出认证的页面地址。
  • response,各种组合后的md5密文。
  • 其它未知参数,nonce/nc/cnonce/qop
// 只是一种非常严格的写法,实际上就是字符串的正则匹配过程
function http_digest_parse($auth_digest)
{
    if ($auth_digest == "") {return false;}
    $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,]+))@', $auth_digest, $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;
}

这里的参数$auth_digest,经过函数处理后,得到的值是:

array(7) {
  ["username"]=>
  string(5) "admin"
  ["nonce"]=>
  string(13) "65cc9dbb4059e"
  ["uri"]=>
  string(6) "/1.php"
  ["response"]=>
  string(32) "1413e1b163f3e2a8b2d374cfd110b14f"
  ["qop"]=>
  string(4) "auth"
  ["nc"]=>
  string(8) "00000002"
  ["cnonce"]=>
  string(16) "fed529ce0017ee1e"
}

鉴权认证

由于这个Digest realmrealm先进的地方,就是拿不到明文的密码。而是有个算法获得了个特殊组装的md5值,来替代了密码的作用。所以,这个替代密码的response的值,就显得很重要。

$realm = "My Realm Name";
$data = @http_digest_parse($_SERVER['PHP_AUTH_DIGEST']);
$username = @$data['username'];
if ($username != "") {
    $ok_auth = array('admin' => 'admin', 'guest' => 'guest');
    if (key_exists($username, $ok_auth)) {
        $password = $ok_auth[$username];
        $A1 = md5($username . ':' . $realm . ':' . $password);
        $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) {
           //验证成功
        }
    }
}

完整代码

这里是苏南大叔修改的官方例子,欢迎指正:

这是付费可看内容,收费5元。

相关文章

总结

更多php相关经验文章,请点击苏南大叔的博客:

如果本文对您有帮助,或者节约了您的时间,欢迎打赏瓶饮料,建立下友谊关系。
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。

 【福利】 腾讯云最新爆款活动!1核2G云服务器首年50元!

 【源码】本文代码片段及相关软件,请点此获取更多信息

 【绝密】秘籍文章入口,仅传授于有缘之人   php