在日常开发中,咱们进行用户登录的时候,大部分状况下都会应用 session 来保留用户登录信息,并以此为根据判断用户是否已登录。但其实 HTTP 也提供了这种登录验证机制,咱们明天就来学习对于 HTTP 验证相干的常识。
HTTP Basic
if (!isset($_SERVER['PHP_AUTH_USER'])) {header('WWW-Authenticate: Basic realm="My Realm"');
header('HTTP/1.0 401 Unauthorized');
echo 'Text to send if user hits Cancel button';
exit;
} else {echo "<p>Hello {$_SERVER['PHP_AUTH_USER']}.</p>";
echo "<p>You entered {$_SERVER['PHP_AUTH_PW']} as your password.</p>";
}
// Authorization: Basic YWFhOmFhYQ==
echo base64_decode('YWFhOmFhYQ==');
// aaa:aaa 等于明文
还是间接就从代码动手,下面的代码就是最简略的一种 HTTP 认证形式,如果 $_SERVER[‘PHP_AUTH_USER’] 不存在,那么咱们就向浏览器发送一个 401 响应头,就是通知浏览器咱们须要登录验证。当浏览器收到这个响应头时,就会弹出一个浏览器自带的验证框并要求输出用户名和明码。
当咱们填写了用户名和明码后,浏览器会在申请头中带上 Authorization 字段,并且将 base64 之后的用户名和密码发送过去。同时,PHP 将会别离把用户名和明码解析到 \$_SERVER[‘PHP_AUTH_USER’] 和 $_SERVER[‘PHP_AUTH_PW’] 中。
上述这种认证形式就是最简略的 HTTP Basic 认证,能够看出,这种形式进行验证的用户名和明码其实是相当于明文传输的,因为 base64 很容易就能够反向解析进去。所以这种形式是十分不平安的。那么有没有更简单一些的形式呢?
HTTP Digest
既然这么写了,那必定是有更好的形式啦,那就是 HTTP Digest 形式的 HTTP 认证。
$realm = 'Restricted area';
//user => password
$users = array('admin' => 'mypass', 'guest' => 'guest');
// 指定 Digest 验证形式
if (empty($_SERVER['PHP_AUTH_DIGEST']) || !$_COOKIE['login']) {setcookie('login', 1); // 退出登录条件判断
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Digest realm="' . $realm .
'",qop="auth",nonce="' . uniqid() . '",opaque="' . md5($realm) . '"');
// 如果用户不输出明码点了勾销
die('您点了勾销,无奈登录');
}
// 验证用户登录信息
if (!($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) ||
!isset($users[$data['username']])) {die('Wrong Credentials!');
}
// 验证登录信息
$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);
// $data['response'] 是浏览器客户端的加密内容
if ($data['response'] != $valid_response) {die('Wrong Credentials!');
}
// 用户名明码验证胜利
echo '您的登录用户为:' . $data['username'];
setcookie("login", 2);
// Authorization: Digest username="guest", realm="Restricted area", nonce="5e815bcbb4eba", uri="/", response="9286ea8d0fac79d3a95fff3e442d6d79", opaque="cdce8a5c95a1427d74df7acbf41c9ce0", qop=auth, nc=00000002, cnonce="a42e137359673851"
// 服务器回复报文中的 nonce 值,加上 username,password, http method, http uri 利用 MD5(或者服务器指定的其余算法)计算出 request-digest,作为 repsonse 头域的值
// 获取登录信息
function http_digest_parse($txt)
{
// echo $txt;
// protect against missing data
$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,]+))@', $txt, $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;
}
if($_GET['logout']){setcookie("login", 0);
header("Location: /");
}
从代码量就能够看出这种形式简单了很多。首先是咱们一样须要在未登录的状况下返回 401 响应头,通知浏览器咱们要进行 Digest 认证。这里 header 信息就有不一样的中央了,格局是 Digest,内容也比 Basic 多了许多,这些多进去的内容都是咱们在验证认证内容的时候须要用到的。
接着,浏览器一样是会弹出输出用户名和明码的弹窗。而后将加密后的用户名和明码信息提交上来。咱们能够看到返回值里有明文的 username,然而没有明文的明码了。其实明码是通过 username、明码、nonce、nc、cnoce、cop、$_SERVER[‘REQUEST_METHOD’]、uri 等这些内容进行 md5 加密后生成的,放在了 response 字段中提交了上来。咱们也须要依照同样的规定取得加密后的明码进行比对就能够断定用户名和明码正确从而让用户实现失常的登录流程。
在这段代码中,咱们退出了一个 cookie,是为了做退出登录的判断应用的。因为 HTTP 认证这种模式的过期工夫是以浏览器为基准的。也就是如果客户端敞开了浏览器,则客户端浏览器内存中保留的用户名和明码才会隐没。这种状况下咱们只能通过 cookie 来进行退出登录的操作,如果用户退出登录了就扭转这个 cookie 的内容并从新发送 401 响应头给浏览要求从新登录。
总结
HTTP 验证的这种操作个别不会做为咱们日常开发中的失常登录性能,大部分状况下,咱们会给后盾或者一些非凡的管理工具加上一层这种 HTTP 认证来实现双重的认证,也就是为了保障后盾的数据安全。比方,我会在我的 phpMyAdmin 上减少一层这个认证。另外,HTTP 认证也能够间接在 Nginx 或 Apache 中间接配置,不须要走到 PHP 这一层来,这个咱们未来学习 Nginx 的时候会再做阐明。
测试代码:
https://github.com/zhangyue0503/dev-blog/blob/master/php/202003/source/PHP%E7%9A%84HTTP%E9%AA%8C%E8%AF%81.php
参考文档:
https://www.php.net/manual/zh/features.http-auth.php
各自媒体平台均可搜寻【硬核项目经理】