Basic 认证简介
在网络活动中, 身份认证是非常重要的一环 Basic 身份认证, 是 HTTP 1.0 中引入的认证方案之一虽然方案比较古老, 同时存在安全缺陷, 但由于实现简单, 至今仍有不少网站在使用它
本文通过实例, 介绍 Basic 认证协议是如何实现的同时, 探讨 Basic 认证存在的安全缺陷最后, 附上 Basic 认证的服务端代码
核心概念
Basic 认证通过核对用户名密码的方式, 来实现用户身份的验证
Basic 认证中, 最关键的 4 个要素:
userid: 用户的 id 也就是我们常说的用户名
password: 用户密码
realm: 领域, 其实就是指当前认证的保护范围
同一个 server, 访问受限的资源多种多样, 比如资金信息机密文档等可以针对不同的资源定义不同的 realm, 并且只允许特定的用户访问
跟 Linux 下的账户分组体系很像, 如下例子所示
Basic 认证实例
下面通过实例来讲解 Basic 认证是如何实现的, 一共分 4 个步骤假设:
用户访问的资源:/protected_docs
用户名密码: chyingp123456
步骤 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 认证还存在无法吊销认证的情况
服务端代码示例
服务端代码如下, 比较简单, 这里不展开, 有问题可留言交流完整代码可 点击这里 https://github.com/chyingp/blog/tree/master/demo/2018.03.31-web-auth/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 https://tools.ietf.org/html/rfc2617
- Security Considerations https://tools.ietf.org/html/rfc2617#section-4
来源: https://juejin.im/entry/5ac175baf265da239e4e3999