关于token的总结和思考——并发访问刷新策略,jwt过期及作废问题

前后端分离与Token认证
探讨前后端分离项目中Token认证机制,包括Token刷新策略、JWT优缺点及常见问题,对比Cookie-Session机制。

前言:

  最近在尝试做前后端分离的项目,由于之前一直听过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问题主要参考了如下文章(侵删)

https://learnku.com/articles/22616

总结

这样看来,其实token本身也是有很多麻烦的,当然也有它的用武之地。但是传统的cookie-session机制其实也挺好的,有些人觉得对于前后端分离,app程序这些,cookie没法带上。其实倒不至于,前后端分离项目也是可以让前端请求带上cookie的,而app中也有相应的实现。至于谈到分布式,也有spring session等技术可以实现session共享。session由于存在比较久远,相应的技术还是挺全面的,这样以来,其实session机制也是挺好的(我是这么想的)。当然,具体使用情况还是看具体业务吧,各有各的用处。

本文只是我这段时间对token学习的理解和记录(因为实在有太多疑惑。。),如果有不对的地方或疑惑的地方,欢迎提出共同讨论,彼此学习!!!

### JWT Token过期问题处理 JWT(JSON Web Token)是一种用于身份验证的开放标准,广泛应用于现代 Web 应用中。由于 JWT 的无状态特性,Token 一旦签发,无法像传统 Session 一样被直接撤销。因此,Token过期新机制成为保障系统安全性的关键。 在 JWT 中,可以通过在 Payload 中设置 `exp` 字段来指定 Token过期时间。为了实现 Token新机制,可以在用户每次访问时更新 Token过期时间,并签发一个新的 Token。由于 JWT 的签名机制依赖于整个 Payload 的内容,因此只要 `exp` 发生变化,Token 的签名也会随之改变,相当于重新颁发了一个新的 Token [^1]。 为了进一步增强安全性,可以在后端引入 Redis 缓存机制,将用户的 JWT 存储在 Redis 中,并设置一个比 Token 本身有效期更长的缓存时间。这样,即使 Token 本身已经过期,只要它仍然存在于 Redis 中,就可以认为该 Token 处于“可新”状态,允许用户在一定时间内自动获取新的 Token,而无需重新登录 [^3]。 在实际的处理流程中,前端可以首先发起请求,后端通过 JWT 工具类(如 `isTokenExpired` 函数)验证 Token 是否过期。如果 Token过期,前端可以捕获到 401 错误,并发起Token 的请求。后端在收到新请求后,验证缓存中的 Token 是否有效,并为前端返回一个新的 Token [^2]。 在Token 的过程中,可以将新的 Token 存储在 Redis 中,同时更新Token(refresh token)的有效期。如果Token 也已过期,则需要用户重新登录。新的 Token 通常不会直接存储在 Redis 中,而是将Token 存储其中,以确保安全性 [^5]。 此外,可以将 Token 分为几种状态:正常 Token(未过期且未达到建议更换时间)、濒死 Token(未过期但已达到建议更换时间)、正常过期 Token(已过期但存在于缓存中)非正常过期 Token(已过期且不存在于缓存中)。通过这种分类,可以更精细地控制 Token 的生命周期新策略 [^4]。 以下是一个简单的 Token 新逻辑示例代码: ```python import jwt from datetime import datetime, timedelta import redis # 初始化 Redis 客户端 redis_client = redis.StrictRedis(host='localhost', port=6379, db=0) def generate_token(user_id, refresh=False): payload = { 'user_id': user_id, 'exp': datetime.utcnow() + timedelta(minutes=15) # Token 有效期为15分钟 } token = jwt.encode(payload, 'secret_key', algorithm='HS256') if refresh: # 将Token 存入 Redis,并设置更长的过期时间 redis_client.setex(token, timedelta(hours=24), 'refresh_token') return token def refresh_token(old_token): try: # 解析 Token payload = jwt.decode(old_token, 'secret_key', algorithms=['HS256']) user_id = payload['user_id'] # 检查 Token 是否存在于 Redis 中 if redis_client.exists(old_token): # 删除旧 Token redis_client.delete(old_token) # 生成新的 Token 并设置新标志 return generate_token(user_id, refresh=True) else: raise Exception("Token过期且不在缓存中") except jwt.ExpiredSignatureError: raise Exception("Token过期") except jwt.InvalidTokenError: raise Exception("无效的 Token") ``` 通过上述机制,可以有效管理 JWT Token 的生命周期,确保系统的安全性与用户体验的平衡。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值