前言:
最近在尝试做前后端分离的项目,由于之前一直听过token和jwt,似乎在前后端分离中也很常用。于是便了解了一番,结果扯出一大堆问题,折腾了好久,所以想把一些问题和思考记录一下。另外,其实前后端分离若无特殊需求,也不必执着于使用token认证,毕竟这会引发一些问题,未必就比传统cookie-session好。下面逐步讲述。
token刷新策略
传统的token认证,即以token为令牌(可以是一串uuid),服务端缓存下来,发给客户端。请求时根据请求携带的token来认证用户是否登陆以及取数据等。
但是token得设置有效期,不然用户就可以一直访问了。特别是如果token被窃取后,不会过期就很麻烦。所以需要设置过期时间。
而设置token有效期后又引发一个问题,一旦token过期,一般会要求用户重新登陆认证,用户体验差,于是人们想出了隐式地刷新token,让用户感觉不到。而隐式地刷新token,需要考虑一些问题:如果在过期的时间点,一个页面同时发起多个ajax请求过来。那么第一个请求服务端校验后发现token过期。便会刷新token。而后续的几个请求携带的仍然是旧token,就会被拒绝。如果这样,那页面上就请求不到资源了,会让人觉得很奇怪。解决办法可以参考如下几种
- 后台解决方案:在服务端缓存或数据库中,保存两份token,一份是当前有效的token,一份是上次失效的preToken。当在过期时间点有多个请求并发访问时,检查到请求头中的token已经失效,把preToken设置为当前失效的token,把token更新为新的token。之后的其他并发访问的请求,发现它们的token和缓存中已经刷新的token不一致,就去检查是否和preToken一致,如果一致,也可以允许访问。这样一来,并发访问就没问题了。
- 前端解决方案:前端使用axios拦截器,每次返回结果时,检查是否token过期。如果token已经过期,那么发起刷新token请求。当然,如果只是这样,上面的问题并没解决,并发访问的那几个请求由于因为token过期,会同时发起多个token刷新请求,这样会造成重复刷新token。所以,可以这样设计,前端保存一个变量isfreshing,表示当前是否正在请求刷新token。为0表示没有,则可以发起刷新token请求;为1则表示已经在请求新token,利用promise保存当前请求。如果发现token失效,就把isfreshing置为1,并发送刷新请求。这时候,如果其他并发请求也由于token失效要发起刷新token请求时,检查到isfreshing已经是1了,就不会发起请求,并且该token失效的请求被保存起来,直到新token返回后重新发起。这样的实现应该是没问题的,思路参考该文章 https://segmentfault.com/a/1190000016946316 (侵删)具体代码我还没怎么研究(Promise比较烂,所以没怎么看懂),但是应该是这个思路没错。
以上就是两种可行的解决方案,其实后台解决方案还有其他类似的方法,比如设置一个允许过期的误差时间等。
无论是什么方法,要注意的是该方法在刷新token的同时,要解决两个问题:1.多个请求在过期点并发访问,后面的请求不会失败。2. 不会造成多次重复刷新token问题
jwt过期及作废问题
jwt是一种token的实现规范,一大特点就是通过非对称加密,使得可以做到无状态token(服务端不需保存任何信息)。但是jwt的使用还是需要慎重。这里主要讲一下一个困惑我很久的问题
作废问题:由于服务端不存储任何jwt信息,所以使得无法作废已经颁布的jwt token(比如修改密码,必须作废jwt)。这一点很致命,网上有的办法是服务端缓存一个黑名单,存储已经作废的token,其实这样就变得有状态了,需要服务端的存储。或者干脆把Jwt缓存到服务端,无论哪种方法,都破坏了jwt的无状态特性,那和普通的token,session也没有区别了其实
过期时间问题:Jwt一般也都要设置过期时间,其实就是在payload中加一个过期时间戳,但是这样其实很不安全,payload里面的内容只是经过简单的base64加密,是可以很简单破解修改的。对于过期的token,可以简单的修改日期就可以重用了。而如果把jwt的信息和过期时间缓存在服务端,倒是可以解决问题,但是又回到了老问题——有状态jwt。
-----综上,其实使用Jwt还是又很多问题有待思考的,并不是说它不好,jwt也有它自己适用的场景,只是用的时候要考虑好问题。虽然服务器无需缓存jwt任何信息是一大亮点,但并不是任何场景下这一点都能用好。
关于jwt问题主要参考了如下文章(侵删)
总结
这样看来,其实token本身也是有很多麻烦的,当然也有它的用武之地。但是传统的cookie-session机制其实也挺好的,有些人觉得对于前后端分离,app程序这些,cookie没法带上。其实倒不至于,前后端分离项目也是可以让前端请求带上cookie的,而app中也有相应的实现。至于谈到分布式,也有spring session等技术可以实现session共享。session由于存在比较久远,相应的技术还是挺全面的,这样以来,其实session机制也是挺好的(我是这么想的)。当然,具体使用情况还是看具体业务吧,各有各的用处。
本文只是我这段时间对token学习的理解和记录(因为实在有太多疑惑。。),如果有不对的地方或疑惑的地方,欢迎提出共同讨论,彼此学习!!!