最近逛 V 站又看见有人在讨论 JWT,感觉很多人讲的很乱,我想简单记录下我印象中的理解

Session

最开始应该是 session 方案,就是用户登陆后服务器返回客户端一个存根(token)来标识当前的会话

服务器在缓存中保存这个 token,用户请求时需要传递这个 token(不管你是使用 cookie header 还是 query)

每次请求服务器都在缓存中查找这个 token,并且找到当前会话的相关信息(比如说是哪个用户)

使用这种方案的优点:

  • 强控制性:可以手动过期,如果想让某个用户下线就直接在缓存删除对应的键值对就行
  • 可观察性:可以很清楚地看见当前的在线用户数量

使用这种方案的缺点:

  • 性能需求高:因为每次请求都要查缓存,如果用户规模越大的问题越明显
  • 对分布式不友好:他是有状态的,你必须共用一个缓存空间来存储数据

原始 JWT

为了克服上面的缺点, JWT 出现了

JWT (JSON Web Token ) 使用的是一种很巧妙的方法,他本身也是一个 string token,分为三部分

Header.Payload.Signature

  1. Header,标识加密算法
  2. Payload,你要存储的数据,比如说用户 ID 、权限 Level 与过期时间等,请注意这是外部可读的
  3. Signature,加密算法签名

你需要注意的是,Payload 是前后端都可读的,但是可以使用 Signature 保证这个 Payload 是未更改的

工作流程是这样的:

用户登陆后服务器通过本地密钥计算并返回 JWT 给用户,用户请求时需要传递这个 JWT(不管你是使用 cookie header 还是 query)

服务器解析 JWT,通过本地密钥 + Signature 验证是否是当初颁发的 JWT,如果是的话就完全相信 Payload 的内容(你是谁,你的权限是什么等等)

到这里你应该能看出一些问题了,但暂时按下不表,先谈优点

使用这种方案的优点:

  • 性能要求低:不需要查缓存或者查库,如果收到的是有效的 JWT,就直接信赖 Payload 的内容
  • 无状态&分布式友好:如果你有两个毫不相干的服务 A 和 B,那么只需要你的本地密钥是相同的,那么生成的 JWT 看上去就是从一个服务生成的(也就是说能够相互承认)

使用这种方案的优点:

不安全哇!即使你的 JWT 可以过期,那么在过期前用户权限更改或者用户注销了怎么办?它是无法撤销的

  • 如果你选择完全相信 Payload 的内容

    当权限更改时(严重点用户注销时)过期之前 JWT 仍然有效,这时用户仍然可以使用之前的身份活动(严重点你可以看见注销的用户在活动)

  • 如果你选择每次查库验证

    你选择每次接收请求时都查一遍用户的权限,或者这个用户存不存在

    但是这样就违背了 JWT 的初衷了,你还是倒退回「每次请求都要查一遍缓存」,JWT 就退化成类似 Session 的模型了

长短令牌的 JWT

为了缓解这个问题,就有了长短 JWT

  • 短期 JWT(Access Token):用于日常访问,但是有效期短(比如 10-30min)

    这样,即使权限更改或者用户注销,或者令牌被盗,也只能在短时间能使用

  • 长期 JWT(Refresh Token):用于在短 JWT 过期时查库并生成新的短 JWT,不能用于日常请求,有效期很长

这种策略能很好的缓解上面的问题,比如长 JWT 只有用户 ID,每次刷新都会查库确认用户存在并拿到当前权限

即使用户身份更改了,旧身份的有效期也只有短 JWT 的长度(实际上前端应该立即销毁旧身份并刷新令牌)

当然,你可能会说这样还是不够安全哇!如果你问出这个问题的话,那你可以使用 Session 模式,它的确是更加安全可控

但对于一般项目来说也没什么所谓了,JWT 也够了,毕竟方便