说起鉴权大家应该都很熟悉,不过作为前端开发来讲,鉴权的流程大头都在后端小哥那边,本文的目的就是为了让大家了解一下常见的鉴权的方式和原理。
认知:HTTP 是一个无状态协议,所以客户端每次发出请求时,下一次请求无法得知上一次请求所包含的状态数据。
HTTP 提供一个用于权限控制和认证的通用框架。最常用的HTTP认证方案是HTTP Basic Authentication
// Authorization 加密过程
let email = "postmail@test.com"
let password = "12345678"
let auth = `${email}:${password}`
const buf = Buffer.from(auth, 'ascii');
console.info(buf.toString('base64')); // cG9zdG1haWxAdGVzdC5jb206MTIzNDU2Nzg=
// Authorization 解密过程
const buf = Buffer.from(authorization.split(' ')[1] || ''), 'base64');
const user = buf.toString('ascii').split(':');
通用 HTTP 身份验证框架有多个验证方案使用。不同的验证方案会在安全强度上有所不同。
IANA 维护了一系列的验证方案[1],除此之外还有其他类型的验证方案由虚拟主机服务提供,例如 Amazon AWS ,常见的验证方案包括:
思考:为什么要在密码里加点“盐”?[9]
最常用的 Session 存储方式是 KV 存储,如Redis,在分布式、API 支持、性能方面都是比较好的,除此之外还有 mysql、file 存储。
如果服务是分布式的,使用 file 存储,多个服务间存在同步 session 的问题;高并发情况下错误读写锁的控制。
我们上面提到的流程中,缺少 Session 的刷新的环节,我们不能在用户登录之后经过一个 expires 时间就把用户踢出去,如果在 Session 有效期间用户一直在操作,这时候 expires 时间就应该刷新。
以 Koa 为例,刷新 Session 的机制也比较简单:
开发一个 middleware(默认情况下所有请求都会经过该 middleware),如果校验 Session 有效,就更新 Session 的 expires: 当前时间+过期时间。
优化:
有些情况下,只允许一个帐号在一个端下登录,如果换了一个端,需要把之前登录的端踢下线(默认情况下,同一个帐号可以在不同的端下同时登录的)。
这时候可以借助一个服务保存用户唯一标识和 sessionId 值的对应关系,如果同一个用户,但 sessionId 不一样,则不允许登录或者把之前的踢下线(删除旧 session )。
JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
JWT 由三部分组成,分别是 header(头部),payload(载荷),signature(签证) 这三部分以小数点连接起来。
例如使用名为 jwt-token 的cookie来存储 JWT 例如:
jwt-token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoibHVzaGlqaWUiLCJpYXQiOjE1MzI1OTUyNTUsImV4cCI6MTUzMjU5NTI3MH0.WZ9_poToN9llFFUfkswcpTljRDjF4JfZcmqYS0JcKO8;
使用.分割值可以得到三部分组成元素,按照顺序分别为:
header:
payload:
{
"name": "lushijie",
"iat": 1532595255, // 发布时间
"exp": 1532595270 // 过期时间
}
signature:
const headerEncode = base64Encode(header);
const payloadEncode = base64Encode(payload);
let signature = HMACSHA256(headerEncode + '.' + payloadEncode, '密钥');
对于验证一个 JWT 是否有效也是比较简单的,服务端根据前面介绍的计算方法计算出 signature,和要校验的JWT中的 signature 部分进行对比就可以了,如果 signature 部分相等则是一个有效的 JWT。
为了减少 JWT Token 泄露风险,一般有效期会设置的比较短。这样就会存在 JWT Token 过期的情况,我们不可能让用户频繁去登录获取新的 JWT Token。
解决方案:
可以同时生成 JWT Token 与 Refresh Token,其中 Refresh Roken 的有效时间长于 JWT Token,这样当 JWT Token 过期之后,使用 Refresh Token 获取新的 JWT Token 与 Refresh Token,其中 Refresh Token 只能使用一次。
有时候,我们登录某个网站,但我们又不想注册该网站的账号,这时我们可以使用第三方账号登录,比如 github、微博、微信、QQ等。
开放授权(OAuth)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的网站(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容。OAuth是OpenID的一个补充,但是完全不同的服务。 —— 摘自 维基百科
名词解释:
授权码模式(authorization code)是功能最完整、流程最严密的授权模式。除了我们上面所说的授权码模式,其实还有其他授权模式:
有些 Web 应用是纯前端应用,没有后端。这时就不能用上面的方式了,必须将令牌储存在前端。RFC 6749 就规定了第二种方式,允许直接向前端颁发令牌。这种方式没有授权码这个中间步骤
如果你高度信任某个应用,RFC 6749 也允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌
适用于没有前端的命令行应用,即在命令行下请求令牌
关于这些模式详细请见:OAuth2.0 的四种方式[10]
单点登录(Single Sign On, SSO),即:单一标记(单点)登录。例如:QQ,我在QQ空间登录一次,我可以去访问QQ产品的其他服务:QQ邮箱、腾讯新闻等,都能保证你的账户保持登录状态。
延伸阅读:
没有最好,只有最合适!!!
HTTP Auth Authentication:
梳理总结:
适用场景:
问题:
Cookie + Session:
梳理总结:
使用场景:
JWT:
梳理总结:
适用场景:
问题:
OAuth:
梳理总结:
适用场景:
OAuth 分为下面四种模式:
文内链接:
1.一系列的验证方案:
http://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml
2.RFC 7617:
https://tools.ietf.org/html/rfc7617
3.RFC 6750:
https://tools.ietf.org/html/rfc6750
4.RFC 6716:
https://link.zhihu.com/?target=https%3A//tools.ietf.org/html/rfc6750
5.bug 472823:
https://bugzilla.mozilla.org/show_bug.cgi?id=472823
6.RFC 7486:
https://tools.ietf.org/html/rfc7486
7.draft-ietf-httpauth-mutual:
https://tools.ietf.org/html/draft-ietf-httpauth-mutual-11
8.AWS docs:
https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-auth-using-authorization-header.html
9.为什么要在密码里加点“盐”?
https://www.cnblogs.com/apolloren/p/11985083.html
10.OAuth2.0 的四种方式:
http://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html
11.如何实现单点登录?:
https://blog.csdn.net/Faker_Wang/article/details/80877654
11.手机扫码登录内网怎么实现的?:
https://blog.csdn.net/maxchenBug/article/details/88791514