前端应该知道的jwt揭秘与实践

527 阅读3分钟

注:本文并不适合初学者

简单说下jwt原理

基于ha256的签名

ha256是一个可以加上秘钥的签名方式(密码学上,很多签名都是不带密码的hash),jwt对payload和headers进行签名,作为第三部分放在末尾

基于rsa的签名

这个很简单,私钥加密,公钥验证即可

注意!!!

是签名不是加密,在非https的情况下慎用jwt,否则payloads完全无安全保障

jwt怎么验证的?

服务端接收到token,拿出payload和headers,通过headers的alo得到签名算法,拿出储存的secret(这是服务器与客户端唯一的羁绊),再次签名payload和headers并对比签名即可。

为什么防串改?

对不熟悉密码学的小伙伴讲一下,密码学意义上的任何签名方式完全不可逆,如果更改payload无法更改签名(没有秘钥或私钥),服务端验证不通过。

为什么要一户一密?

相信很多同学都是一个服务端一个秘钥,但是这其实是有一些潜在问题的。

更改密码

用户更改密码后,以前的token在exp过期之前还可以使用,你不可能为了一个用户更改密码去更改全局的secret。此时如果是一户一密,更改secret为最新的即可。

注销

相信大家都是在注销的时候清除cookie/localStorage,但是如果用户女朋友想要看看他做了什么,偷偷从他电脑上复制token,即使用户注销了,他女朋友还是能达到登录的目的。这有点类似于中间人攻击,而server端完全无法防范。如果一户一密,注销后delete掉secret即可。

单点登录

ps:真不建议使用基于token的sso

真的很难想象,你的多个应用全部配置相同的secret...

一户一密储存在哪?

token不仅会作为登录凭证,也会用来传递payload,secret最好存入redis

myAccount::tokenSecret mamama

当然,你一定需要设置过期时间,直接setex比较安全。

是否需要存入数据库?

  • 缓存一致性问题:因为expired跟jwt的过期时间设置为一致,如果缓存里secret不存在,那么jwt一定失效,所以并不存在缓存一致性问题。
  • 过期雪崩问题:用户登录的时间并非一致,不会出现同时大量用户token过期的情况,也不用考虑雪崩问题。
  • 丢失代价:secret意外丢失对用户也没有多大影响,再登录一次即可。
  • 综上,没必要存入数据库。如果非要写入,用MQ写也可以。

ps:有些用户登录一次后就不会再玩了,redis过期缓存删除策略不要设置为惰性删除

为什么不存session?

那我还要token干嘛?

完全解放server?

有没有办法让服务端不存secret?而让客户端去储存呢?

我们首先要考虑几个问题:

  1. cookie在csrf和xss攻击下极不安全,不能单靠cookie来做事情。
  2. localStorage在某些情况下也是不安全的。
  3. 还是要考虑之前的问题,我们要保证用户的token只能在他自己电脑上用,可不能让他女朋友看到并藏起来偷偷登录。

我们试着这样解决:

服务端:

  1. 服务器设置并更新secret为http-only cookie
  2. 服务器发送token交由前端储存至localStorage

客户端:

  1. 客户端将token设置为header头发送
  2. 带上cookie(跨域设置access-control-allow-credentials)

这下基本解决所有问题了,注销和更改密码,服务器更新新的secret交由客户端即可。

当然,你女朋友还是能偷偷把token和secret拿来登录用。如果非要防止这种情况,只能让前端做密码学的运算,非常消耗性能。