实践: jwt如何实现主动token无效化 | 青训营

95 阅读3分钟

本人的玩具项目用到了Gin+GORM+MySQL+Redis+OSS, 可以调动以上这些框架进行业务实现. 用户的注册和登录当然是所有业务中最基本的一环, 它的实现说简单确实很简单, 但是如果从用户的安全性角度出发, 这一部分的设计同样需要一番仔细斟酌.

因为http是无状态的协议, 因此用户每次访问受限内容的请求都需要验证自己的身份. 每次请求都要上传用户名和密码是不现实的, 比较浅显的原因至少有两点: 每次上传用户名和密码都需要从SQL数据库中比对(对于这种要求实时且准确的东西, Redis缓存不太适用), 速度很慢, 性能很低; 这种实现要求前端记住用户名和密码(总不能让用户点击一下就输入一遍用户名和密码吧), 很不安全. 常用的实现方式多为cookie, session, token. cookie存储在用户端, 一般不可跨域(现代浏览器都默认不会允许的, 存在隐私风险), 可以从浏览器读取, 不太安全; session存放在服务端, 经常与cookie一起用, 单论session比cookie安全, 但是session会给服务端带来更大压力; token则与cookie类似, 但是它简短且不与域名强绑定, 更灵活. 现在很常用jwt(json web token), 利用json对象在各方间安全传递信息, 包括登录认证信息. 但这种方法还是有一些弊端, 最典型的莫过于jwt可以设置过期时间, 但是单独使用jwt, 在token自己过期前是无法主动撤销它的. 这给予了多个有效token同时存在以可乘之机, 当token泄露时将会带来很大的安全风险. 本人的玩具项目就遇到了这样的问题.

其实这个问题也比较好解决. 最容易想到的方案便是在服务端记住目前那些token是有效的. 上述表述中提到, 单独使用jwt时会存在问题. 那么, 可以将token与Redis结合使用, 在服务端收到token后访问Redis进行比对, 检查该token是否有效. 具体来说, 每次登录签发token时, 服务端会向Redis存放新签发的token, 若每个用户同时只有一个token可以被存放, 还可以实现单终端登陆的限制(类似微信的机制). 在用户每个请求发来后, 会先访问Redis比对该token是否有效, 无论其本身是否未过期. 在确定某token已经泄露后, 可以从Redis中删除该token, 就实现了token的主动无效化. Redis的高性能可以保证这一方案的速度, 若Redis崩溃也不会导致用户可以随意登录(但是会影响用户的正常登录, 只是安全性未受损害而已).

Redis缓存的token可以设置有效期, 实现token自动过期. 但包括jwt本身设置的有效期也存在这个问题: 若用户登录后token自动过期, 就只能再次登陆了. 其实可以将jwt自带的有效期(同一token不修改无法续期)关闭, 每次请求时若token比对成功则延长该token在Redis中的有效期, 就可以实现无感续期.