注:本文并不适合初学者
简单说下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?而让客户端去储存呢?
我们首先要考虑几个问题:
- cookie在csrf和xss攻击下极不安全,不能单靠cookie来做事情。
- localStorage在某些情况下也是不安全的。
- 还是要考虑之前的问题,我们要保证用户的token只能在他自己电脑上用,可不能让他女朋友看到并藏起来偷偷登录。
我们试着这样解决:
服务端:
- 服务器设置并更新secret为http-only cookie
- 服务器发送token交由前端储存至localStorage
客户端:
- 客户端将token设置为header头发送
- 带上cookie(跨域设置access-control-allow-credentials)
这下基本解决所有问题了,注销和更改密码,服务器更新新的secret交由客户端即可。
当然,你女朋友还是能偷偷把token和secret拿来登录用。如果非要防止这种情况,只能让前端做密码学的运算,非常消耗性能。