- 本文地址: https://www.yangdx.com/2019/03/19.html
- 转载请注明出处
如果你有一个简单的页面需要做登录认证,也就是需要一个登录框,但是自己又懒得写一堆HTML和CSS,那么你可以借用浏览器的HTTP认证。
我们可以用PHP的 header() 函数来向客户端浏览器发送401认证信息,使浏览器弹出一个用户名/密码输入窗口。当用户输入用户名和密码提交后,浏览器将用户名和密码转码后添加到HTTP请求头发送给服务器,而PHP脚本将接收到用户名和密码存储到两个变量里:$_SERVER['PHP_AUTH_USER']、$_SERVER['PHP_AUTH_PW'] 。
浏览器支持两种常见的认证方法,分别是“Basic”和“Digest”。
Basic认证
Basic认证代码举例:
<?php
if (!isset($_SERVER['PHP_AUTH_USER'])) {
header('WWW-Authenticate: Basic realm="My Realm"');
header('HTTP/1.0 401 Unauthorized');
echo '如果用户点击了“取消”按钮将会看到这段文字';
exit;
} else {
echo "<p>{$_SERVER['PHP_AUTH_USER']} 您好。</p>";
echo "<p>您输入的密码是 {$_SERVER['PHP_AUTH_PW']} 。</p>";
}
?>
各浏览器登录框样式预览:
输入用户名和密码提交,监听浏览器的网络请求,我们可以看到HTTP请求头多了一行 “Authorization” :
其中Basic指明了使用认证方式(首字母必须大写),接着1个空格,后面这一串字符“YWRtaW46YWExMjM0NTY=”则是我们输入的“用户名:密码”经过base64转码后的字符串(中间有个冒号)。
一旦认证成功,后续的每个HTTP请求都会携带这个“Authorization”请求头。
如何注销登录呢?方法一:关闭浏览器后自动销毁(类似于session);方法二:重新向浏览器发送401状态(上面那两行header代码),浏览器会立即清除认证缓存。
Basic认证方法的认证过程简单明了,适合于对安全性要求不高的系统或设备中,如大家所用路由器的配置页面的认证,几乎都采取了这种方式。其缺点是没有灵活可靠的认证策略,如无法提供域(domain或realm)认证功能,另外,BASE64的随便解密,安全性无保障。当然,Basic认证系统也可以与SSL或者Kerberos结合,实现安全性能较高(相对)的认证系统。
Digest认证
Digest认证代码举例:
<?php
$realm = 'Restricted area';
// 用户名 => 密码
$users = [
'admin' => 'mypass',
'jack' => '123456'
];
if (empty($_SERVER['PHP_AUTH_DIGEST'])) {
$nonce = uniqid();
$opaque = md5($realm);
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Digest realm="'.$realm.'", qop="auth", nonce="'.$nonce.'", opaque="'.$opaque.'"');
die('如果用户点击了“取消”按钮将会看到这段文字');
}
// 分析PHP_AUTH_DIGEST变量
if (!($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) || !isset($users[$data['username']])) {
die('认证失败');
}
// 验证请求是否有效
$A1 = md5($data['username'] . ':' . $realm . ':' . $users[$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) {
die('认证失败!');
}
// 到这里表示认证成功
echo '您登录的账号是:'.$data['username'];
// 用于解析 http auth 请求头的函数
function http_digest_parse($txt) {
// 防止丢失数据
$needed_parts = [
'nonce' => 1,
'nc' => 1,
'cnonce' => 1,
'qop' => 1,
'username' => 1,
'uri' => 1,
'response' => 1
];
$data = [];
preg_match_all('@(\w+)=([\'"]?)([a-zA-Z0-9=./\_-]+)\2@', $txt, $matches, PREG_SET_ORDER);
foreach ($matches as $m) {
$data[$m[1]] = $m[3];
unset($needed_parts[$m[1]]);
}
return $needed_parts ? false : $data;
}
?>
这次 header() 函数发送的数据多了几个参数,具体含义解释如下:
- Digest:认证方法;
- realm:领域,领域参数是强制的,在所有的盘问中都必须有,它的目的是鉴别SIP消息中的机密,在SIP实际应用中,它通常设置为SIP代理服务器所负责的域名;
- qop:保护的质量,这个参数规定服务器支持哪种保护方案,客户端可以从列表中选择一个。值 “auth”表示只进行身份查验, “auth-int”表示进行查验外,还有一些完整性保护。需要看更详细的描述,请参阅RFC2617;
- nonce:为一串随机值,在下面的请求中会一直使用到,当过了存活期后服务端将刷新生成一个新的nonce值;
- opaque:一个不透明的(不让外人知道其意义)数据字符串,在盘问中发送给用户。
登录框还是一个样子,但这次的HTTP请求头不太一样:
太长了,我贴出来:
Authorization: Digest username="admin", realm="Restricted area", nonce="5c829a8c92474", uri="/auth2.php", response="b570c5fdd02e997de9a5f5582f447447", opaque="cdce8a5c95a1427d74df7acbf41c9ce0", qop=auth, nc=00000002, cnonce="52c83b15aa2d298b"
其中,参数realm、nonce、opaque、qop是由上一个请求服务器返回给客户端的数据。客户端新生成的参数含义如下:
- username:登录用户名(明文);
- uri:客户端想要访问的URI;
- response:这是由用户代理软件计算出的一个字符串,以证明用户知道口令;
- nc:“现时”计数器,这是一个16进制的数值,即客户端发送出请求的数量(包括当前这个请求),这些请求都使用了当前请求中这个“现时”值。例如,对一个给定的“现时”值,在响应的第一个请求中,客户端将发送“nc=00000001”。这个指示值的目的,是让服务器保持这个计数器的一个副本,以便检测重复的请求。如果这个相同的值看到了两次,则这个请求是重复的;
- cnonce:这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护。
PHP参考手册:http://php.net/manual/zh/features.http-auth.php
HTTP认证机制:https://www.cnblogs.com/attilax/p/6226004.html
快来评论一下吧!
发表评论