JWT(json web token)
简介
JWT,全称是 Json Web Token, 是 JSON 风格轻量级的授权和身份认证规范,可实现无状态、
分布式的 Web 应用授权;官网:https://jwt.iohttps://jwt.io
GitHub 上 jwt 的 java 客户端:https://github.com/jwtk/jjwthttps://github.com/jwtk/jjwt
我们最终可以利用 jwt 实现无状态登录
数据格式
JWT 包含三部分数据:
Header:头部,通常头部有两部分信息:
token 类型:JWT
加密方式:base64(HS256)
Payload:载荷,就是有效数据,一般包含下面信息:
用户身份信息(注意,这里因为采用 base64 编码,可解码,因此不要存放敏感信息)
注册声明:如 token 的签发时间,过期时间,签发人等,这部分也会采用 base64 编码,得到第二部分数据。
Signature:签名
是整个数据的认证信息。根据前两步的数据,再加上指定的密钥(secret)(不要泄漏,最好周期性更换),通过 base64 编码生成。用于验证整个数据完整和可靠性
交互流程
步骤:
- 1、用户登录
- 2、服务的认证,通过后根据 secret 生成 token
- 3、将生成的 token 返回给浏览器
- 4、用户每次请求携带 token
- 5、服务端利用秘钥解读 jwt 签名,判断签名有效后,从 Payload 中获取用户信息
- 6、处理请求,返回响应结果
因为 JWT 签发的 token 中已经包含了用户的身份信息,并且每次请求都会携带,这样服务的
就无需保存用户信息,甚至无需去数据库查询,完全符合了 Rest 的无状态规范
授权中心流程
优势
易于水平扩展
在 cookie-session 方案中,cookie 内仅包含一个 session 标识符,而诸如用户信息、授权列表等都保存在服务端的 session 中。如果把 session 中的认证信息都保存在JWT 中,在服务端就没有 session 存在的必要了。当服务端水平扩展的时候,就不用处理 session 复制(session replication)/ session 黏连(sticky session)或是引入外部 session 存储了[实际上 spring-session 和 hazelcast 能完美解决这个问题]。
防护 CSRF(跨站请求伪造)攻击
访问某个网站会携带这个域名下的 cookie。所以可能导致攻击。但是我们可以把 jwt放在请求头中发送。
Jwt 放在请求头中,就必须把 jwt 保存在 cookie 或者 localStorage 中。保存这里 js就会读写,又会导致 xss 攻击。可以设置 cookie,httponly=true 来防止 xss
安全
只是 base64 编码了,cookie+session 直接将数据保存在服务端,看都看不见,请问哪个更安全?
问题
我们不建议使用 jwt+cookie 代替 session+cookie 机制,jwt 更适合 restful api
jwt token 泄露了怎么办?
这个问题可以不考虑,因为 session+cookie 同样泄露了 cookie 的 jsessionid 也会有这个问题
我们可以遵循以下规范减少风险
使用 https 加密应用
返 回 jwt 给 客 户 端 时 设 置 httpOnly=true 并 且 使 用 cookie 而 不 是LocalStorage 存储 jwt,防止 XSS 攻击和 CSRF 攻击
secret 如果泄露会导致大面积风险
定期更新
Secret 设计可以和用户关联起来,每个用户不一样。防止全用一个 secret
注销和修改密码
传统的 session+cookie 方案用户点击注销,服务端清空 session 即可,因为状态保存在服务端。我们不害怕注销后的假登录
Jwt 会有问题。用户如果注销了或者修改密码了。恶意用户还使用之前非法盗取来的 token,可以在不重新登录的情况下继续使用
可以按程度使用如下设计,减少一定的风险
清空客户端的 cookie,这样用户访问时就不会携带 jwt,服务端就认为用户需要重新登录。这是一个典型的假注销,对于用户表现出退出的行为,实际上这个时候携带对应的 jwt 依旧可以访问系统。
清空或修改服务端的用户对应的 secret,这样在用户注销后,jwt 本身不变,但是由于 secret 不存在或改变,则无法完成校验。这也是为什么将secret 设计成和用户相关的原因
借助第三方存储,管理 jwt 的状态,可以以 jwt 为 key,去 redis 校验存在性。但这样,就把无状态的 jwt 硬生生变成了有状态了,违背了 jwt的初衷。实际上这个方案和 session 都差不多了。
修改密码则略微有些不同,假设号被到了,修改密码(是用户密码,不是jwt 的 secret)之后,盗号者在原 jwt 有效期之内依旧可以继续访问系统,所以仅仅清空 cookie 自然是不够的,这时,需要强制性的修改 secret
续签问题
传统的 cookie 续签方案一般都是框架自带的,session 有效期 30 分钟,30分钟内如果有访问,session 有效期被刷新至 30 分钟。而 jwt 本身的 payload之中也有一个 exp 过期时间参数,来代表一个 jwt 的时效性,而 jwt 想延期这个 exp 就有点身不由己了,因为 payload 是参与签名的,一旦过期时间被修改,整个 jwt 串就变了,jwt 的特性天然不支持续签!
可如下解决,但都不是完美方案
每次请求刷新 jwt:简单暴力,性能低下,浪费资源。
只要快要过期的时候刷新 jwt:jwt 最后的几分钟,换新一下。但是如果用户连续操作了 27 分钟,只有最后的 3 分钟没有操作,导致未刷新jwt,就很难受。
完 善 refreshToken : 借 鉴 oauth2 的 设 计 , 返 回 给 客 户 端 一 个refreshToken,允许客户端主动刷新 jwt。这样做,还不如用 oauth2
使用 redis 记录独立的过期时间:jwt 作为 key,在 redis 中保存过期时间,每次使用在 redis 中续期,如果 redis 没有就认为过期。但是这样做,还不如用 session+cookie
总结
- 在 Web 应用中,别再把 JWT 当做 session 使用,绝大多数情况下,传统的cookie-session 机制工作得更好
- JWT 适合一次性的命令认证,颁发一个有效期极短的 JWT,即使暴露了危险也很小,由于每次操作都会生成新的 JWT,因此也没必要保存 JWT,真正实现无状态
作者声明
1 | 如有问题,欢迎指正! |