Basic认证简介
在网络活动中,身份认证是非常重要的一环。Basic身份认证,是HTTP 1.0中引入的认证方案之一。虽然方案比较古老,同时存在安全缺陷,但由于实现简单,至今仍有不少网站在使用它。
本文通过实例,介绍Basic认证协议是如何实现的。同时,探讨Basic认证存在的安全缺陷。最后,附上Basic认证的服务端代码。
核心概念
Basic认证通过核对用户名、密码的方式,来实现用户身份的验证。
Basic认证中,最关键的4个要素:
- userid:用户的id。也就是我们常说的用户名。
- password:用户密码。
- realm:“领域”,其实就是指当前认证的保护范围。
同一个server,访问受限的资源多种多样,比如资金信息、机密文档等。可以针对不同的资源定义不同的 realm,并且只允许特定的用户访问。
跟Linux下的账户、分组体系很像,如下例子所示。
Basic认证实例
下面通过实例来讲解Basic认证是如何实现的,一共分4个步骤。假设:
- 用户访问的资源:/protected_docs
- 用户名、密码:chyingp、123456
步骤1:用户访问受限资源
如下,用户访问受限资源 /protected_docs
。请求报文如下:
GET /protected_docs HTTP/1.1
Host: 127.0.0.1:3000
步骤2:服务端返回401要求身份认证
服务端发现 /protected_docs 为受限资源,于是向用户发送401状态码,要求进行身份认证。
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm=protected_docs
响应首部中,通过WWW-Authenticate
告知客户端,认证的方案是basic
。同时以realm
告知认证的范围。
WWW-Authenticate: Basic realm=<需要保护资源的范围>
步骤3:用户发送认证请求
用户收到服务端响应后,填写用户名、密码,然后向服务端发送认证请求。
以下为请求报文。Authorization
请求首部中,包含了用户填写的用户名、密码。
GET /protected_docs HTTP/1.1
Authorization: Basic Y2h5aW5ncDoxMjM0NTY=
Authorization
首部的格式为Basic base64(userid:password)
。实际代码如下:
Buffer.from('chyingp:123456').toString('base64'); // Y2h5aW5ncDoxMjM0NTY=
步骤4:服务端验证请求
服务端收到用户的认证请求后,对请求进行验证。验证包含如下步骤:
- 根据用户请求资源的地址,确定资源对应的realm。
- 解析 Authorization 请求首部,获得用户名、密码。
- 判断用户是否有访问该realm的权限。
- 验证用户名、密码是否匹配。
一旦上述验证通过,则返回请求资源。如果验证失败,则返回401要求重新认证,或者返回403(Forbidden)。
安全缺陷
Basic认证的安全缺陷比较明显,它通过明文传输用户的密码,这会导致严重的安全问题。
- 在传输层未加密的情况下,用户明文密码可被中间人截获。
- 明文密码一旦泄露,如果用户其他站点也用了同样的明文密码(大概率),那么用户其他站点的安全防线也告破。
关于上述问题的建议:
- 传输层未加密的情况下,不要使用Basic认证。
- 如果使用Basic认证,登录密码由服务端生成。
- 如果可能,不要使用Basic认证。
除了安全缺陷,Basic认证还存在无法吊销认证的情况。
服务端代码示例
服务端代码如下,比较简单,这里不展开,有问题可留言交流。完整代码可 点击这里。
const express = require('express');
const app = express();
const realms = [
{ realm: 'protected_docs', path: '/protected_docs', users: ['chyingp'] }
];
const users = [
{ usrname: 'chyingp', passwd: '123456' }
];
// 检查资源路径对应的realm,比如 path:'/protected_docs' => realm:'protected_docs'
function findRealm (path) {
return realms.find(item => path.indexOf(item.path) !== -1);
}
// 根据用户名、密码,查找用户
function findUser (usrname, passwd) {
return users.find(user => user.usrname === usrname && user.passwd === passwd);
}
// 判断用户是否在realm里
function isUserInRealm (realmItem, usrname) {
return realmItem.users.indexOf(usrname) !== -1;
}
function notAuthorized (res) {
res.status = 403;
res.end();
}
const protectedPath = '/protected_docs';
app.get(protectedPath, (req, res, next) => {
const realmItem = findRealm(protectedPath);
const realm = realmItem.realm; // 这里是 protected_docs
const authorization = req.get('authorization');
if (authorization) { // 身份认证
const usernamePasswd = authorization.split(' ')[1]; // Basic Y2h5aW5ncDoxMjM0NTY
const [usrname, passwd] = Buffer.from(usernamePasswd, 'base64').toString().split(':');
if (isUserInRealm(realmItem, usrname) === false) { // 用户不在realm里
return notAuthorized(res);
}
const user = findUser(usrname, passwd);
if (!user) { // 用户账号、密码验证不通过
return notAuthorized(res);
}
res.end(`welecom ${usrname}`);
} else { // 告知用户需要身份认证
res.statusCode = 401;
res.set('WWW-Authenticate', 'Basic realm=' + encodeURIComponent(realm));
res.end();
}
});
app.listen(3000);
参考链接
HTTP Authentication: Basic and Digest Access Authentication
关于作者:程序猿小卡,前腾讯高级工程师,现任前海云汉金融科技前端技术负责人。专注技术架构、技术分享、项目管理。