php如何处理401状态码,完成Digest realm授权认证闭环?
发布于 作者:苏南大叔 来源:程序如此灵动~ 我们相信:世界是美好的,你是我也是。平行空间的世界里面,不同版本的生活也在继续...
401
认证就是从服务器端发出一个401
状态码,然后由客户端浏览器来处理登录框的问题。但是,服务器端能够自定义登录的认证方式为realm
,还是digest realm
。整体上来说,两者的区别就是数据加密方式的不同,而这件事情对于用户来说,无感。
苏南大叔的“程序如此灵动”博客,记录苏南大叔的编程经验文章。本文讨论以php
的方式,如何完成“digest realm”的认证闭环?测试环境:win10
,nginx@1.15.11
,php@8.2.9nts
,chrome@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');
发送的数据
客户端浏览器发生的认证数据,就是客户端传递的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"
分解认证数据
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 realm
比realm
先进的地方,就是拿不到明文的密码。而是有个算法获得了个特殊组装的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元。
相关文章
- https://newsn.net/say/electron-401.html
- https://newsn.net/say/nginx-401.html
- https://newsn.net/say/php-401.html
总结
更多php
相关经验文章,请点击苏南大叔的博客:
如果本文对您有帮助,或者节约了您的时间,欢迎打赏瓶饮料,建立下友谊关系。
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。