avatar

jwt最佳实践


jwt(Json Web Token) RFC文档介绍了如何去生成jwt token。但是并没有指出在登录鉴权时应该以一个什么样的交互方式来传递token。jwt描述了jwt token的生成算法,阮一峰老师的博客也言简意赅的写了一篇jwt的入门教程, 我们现在知道了怎么生成jwt,那么在设计网站登录方案时如何通过jwt鉴权呢?

简单的登录方案

在我之前做的一些小项目中,用户通过用户名密码登录,服务器拿到用户名密码后进行校验,通过则生成token,存到数据库或redis中,同时记录token的过期时间,之后用户再有请求过来时,不断更新新的过期时间,交互方式如下图所示:

sequenceDiagram
participant C as Client
participant S as Server
  C->>S: login with username and password
  S-->>C: valid ? {token, expiredTime} : 401
  C->>S: get userinfo by token
  S-->>C: response userinfo
  S-->>S: update expired time

过程简单,就是每次交互时延长过期时间,来持有用户的登录态,如果token确实过期了,就将用户重定向到登录页面再次输入用户名密码。

该方案,不限定生成token的方式,可以通过md5、uuid。生成的token需要存于服务端,服务端还要记录它的过期时间。服务使用人数不多时通过该方案没什么问题,但如果用户数很多时,每次请求服务端都需要去改数据库或者redis里面的token过期时间,可能会带来性能瓶颈,同时,由于token一直在服务器与客户端的交互中传输并延长过期时间,所以token一段被攻击者获取,攻击者也可以借助该机制反复刷新token过期时间,使自己拿到的token不过期。

基于JWT的方案

使用jwt时服务端不会存储jwt,只是通过算法来保证jwt的信息不被更改,客户端每次请求时向服务端传递jwt, 服务端校验jwt的合法性并且通过jwt里面本身包含的信息来知道来自客户端的具体访问用户。那么,如果我们按简单方案的思路,客户端请求过来之后,每次生成新的jwt token给客户端,这样肯定是不行的:

  1. 服务器需要不停的生成新的jwt,消耗性能
  2. jwt可以不停的请求来避免自身过期, 此时由于jwt存在于客户端,服务器想要封禁该用户就不可能了。

为了避免jwt引入的这些问题,服务端设计了两种token, access token(即jwt格式token)和refresh token。access token的过期时间设置的比较短,仅几分钟,用于服务端知道用户身份,refresh token过期时间较长,七天或者一个月。当access token过期时,客户端通过refresh token来获取新的access token。客户端和服务端的交互流程如下:

sequenceDiagram
participant C as Client
participant S as Server

C->>S: login with username and password
S-->>C: Access token: expired after 10 minutes<br> Refresh token: expired after 30 days
C->>S: get userinfo with access token 
S-->>C: response userinfo
C->>S: Access token expired<br>request refresh token api
S-->>C: response new access token
C->>S: request userinfo with new access token
S-->>C: response userinfo
  1. 客户端使用用户名密码进行认证
  2. 服务端生成有效时间较短的 Access Token(例如 10 分钟),和有效时间较长的 Refresh Token(例如 7 天)
  3. 客户端访问需要认证的接口时,携带 Access Token
  4. 如果 Access Token 没有过期,服务端鉴权后返回给客户端需要的数据
  5. 如果携带 Access Token 访问需要认证的接口时鉴权失败(例如返回 401 错误),则客户端使用 Refresh Token 向刷新接口申请新的 Access Token
  6. 如果 Refresh Token 没有过期,服务端向客户端下发新的 Access Token
  7. 客户端使用新的 Access Token 访问需要认证的接口

将生成的 Refresh Token 以及过期时间存储在服务端的数据库中,由于 Refresh Token 不会在客户端请求业务接口时验证,只有在申请新的 Access Token 时才会验证,所以将 Refresh Token 存储在数据库中,不会对业务接口的响应时间造成影响,也不需要像 Session 一样一直保持在内存中以应对大量的请求。

上述的架构,提供了服务端禁用用户 Token 的方式,当用户需要登出或禁用用户时,只需要将服务端的 Refresh Token 禁用或删除,用户就会在 Access Token 过期后,由于无法获取到新的 Access Token 而再也无法访问需要认证的接口。这样的方式虽然会有一定的窗口期(取决于 Access Token 的失效时间),但是结合用户登出时客户端删除 Access Token 的操作,基本上可以适应常规情况下对用户认证鉴权的精度要求。

总结

JWT 的使用,提高了开发者开发用户认证鉴权功能的效率,降低了系统架构复杂度,避免了大量的数据库和缓存查询,降低了业务接口的响应延迟。然而 JWT 的这些优点也增加了 Token 管理上的难度,通过引入 Refresh Token,既能继续使用 JWT 所带来的优势,又能使得 Token 管理的精度符合业务的需求。

Reference

  1. 基于 JWT + Refresh Token 的用户认证实践
  2. Is refreshing an expired JWT token a good strategy?

评论列表:

暂无评论 😭