场景描述
使用 Nginx 部署一下静态网页时,出于安全性案例,需要对用户进行访问限制。而 Nginx 恰好提供了访问认证的功能Restricting Access with HTTP Basic Authentication, 主要是配置简单,只需要如下简单的几步就可以实现一个认证:
// 1. 在linux上面使用httpd-tools生成密钥
htpasswd -c /etc/nginx/htpasswd hello // 回车 并 输入避免即可
// 2. 配置 Nginx
location /api {
auth_basic "Login";
auth_basic_user_file /etc/nginx/htpasswd;
...
}
配置后,就可以看见一个登录框了
使用输入正确的账号、密码,Nginx 才会响应正常的内容,点击取消就是 401 未授权页面了:
场景分析
浏览器是怎么知道登录?
可以简单对比一下 Nginx 配置了 auth_basic
前后的Nginx响应的变化。
配置 auth_basic
后,初次访问 statusCode 会变成 401,且reponse header中会新增响应头:
WWW-Authenticate: Basic realm="Login"
401 比较好理解,就是没有访问权限。但 WWW-Authenticate
作用是啥?
在HTTP 身份验证中有介绍到:
RFC 7235 定义了一个 HTTP 身份验证框架,服务器可以用来针对客户端的请求发送 challenge (质询信息),客户端则可以用来提供身份验证凭证。质询与应答的工作流程如下:服务器端向客户端返回 401(Unauthorized,未被授权的) 状态码,并在 WWW-Authenticate 首部提供如何进行验证的信息,其中至少包含有一种质询方式。之后有意向证明自己身份的客户端可以在新的请求中添加 Authorization 首部字段进行验证,字段值为身份验证凭证信息。通常客户端会弹出一个密码框让用户填写,然后发送包含有恰当的 Authorization 首部的请求。
WWW-Authenticate
其实就是标准的响应头,目的就是告诉浏览器需要请求需要登录校验。实际测试过程中,普通浏览器可用,微信中不可用,可以通过代码控制 Webview 是否开启Http的认证。
WWW-Authenticate
的值由两部分组成:
WWW-Authenticate: <type> realm=<realm>
type一般为 Basic,还有些不常用,在Hypertext Transfer Protocol (HTTP) Authentication Scheme Registry 规范中有维护,realm就是一个安全区域的描述,简单理解就是一个命名空间的作用,我需要用的认证场景没有体现其价值,具体可以看What is the “realm” in basic authentication
验证过程是怎样的?
在浏览器输入用户名、密码后,浏览器将用户名、密码以下述格式转化:
base64(<userName>:<password>)
然后放在请求头进行传递
再浏览器可以通过
atob
解码 base64, 上面的密钥解码数据为:
Nginx难道这个头部信息后,和 /etc/nginx/htpasswd
中的保存的用户信息进行比对验证即可。
使用Node实现一个简单认证服务
使用Node实现也比较简单,核心就是 statusCode + 请求头信息设置, 简易的demo为:
const http = require('http')
http
.createServer((req, res) => {
if (req.headers.authorization) {
// 可以解析里面内容,判断userId&password是否正确
res.write('hello world')
return res.end()
}
res.statusCode = 401
res.setHeader('WWW-Authenticate', 'Basic realm="Login"')
res.end()
})
.listen(8132)
效果为:
总结
通过使用 Http 提供的认证规范 WWW-Authenticate
,结合 Nginx 提供的认证功能,可以很简单的实现一个简易的登录认证过程,而且浏览器认证都是会话级别的,也就是说,只要不关闭浏览器,就无需重新认证,有一定的安全性,同时体验也挺好的。