java实现token无感刷新+处理并发的后端方案
作者:编程就是n踢r
在Web应用中,Token用于身份验证和会话管理,但当Token过期时,可能会导致用户在提交表单或进行操作时突然被重定向到登录页面,本文就来介绍一下java实现token无感刷新+处理并发的后端方案,感兴趣的可以了解一下
问题描述:
当用户通过登陆后进入一个web网站,会把token保存到localStorage。假设token过期时间30min。
那么当用户在网站快乐地玩耍了30min后,这时进行了一次提交表单,它会被重定向到登陆页面。
作为用户:我表单填了这么久,点击提交时让我重新登陆?我的体验很不好。
jwt的好处和局限
jwt的好处是:服务器无需存储这些登陆的状态
而它的局限是:服务器无法主动地通知用户“你的token过期了,我重新给你一个”。
如何解决
方案一:双token
在登陆时我们会生成俩个token
- token:表示正常情况下登陆凭证
- refresh-token:表示需要刷新情况下登陆凭证
假设前者(token)设置过期时间为30min,后者为1天。
流程
- time=0min,用户成功登陆,后端返回俩个token(token和refresh-token),前端把它俩保存到localStorage
- time=10min,用户进行了一次查询,前端将俩个token都发给后端,后端检验token,有效,返回结果,用户很开心!
- time=35min,用户提交了表单,前端还是将俩个token发给后端;后端检验token,过期;检验refresh-token,有效,后端认为这是要刷新token,生成新的token和refresh-token,成功返回数据和双token。前端把数据渲染,把双token保存。
token正常过期的情况:
当然,token可以正常过期,如果在检验时refresh-token也过期,那就说明正常过期
假设time=0min时用户登陆,time=2天时用户提交了表单,那么后端认为这是正常过期,用户需要重新登陆。
流程图如下:
token刷新并发问题
现在很多登陆是可以多端的,当多端并发都去尝试刷新token时,会导致token被重复刷新
方案二:刷新时用分布式锁
可以引入redis缓存中间件,使用缓存加速并处理并发刷新问题。
将双token生成后存入redis。当需要刷新token时,采用double-check+获取关于refresh-token的分布式锁来实现。
伪代码如下:
// 验证和刷新Token的方法 public String validateAndRefreshToken(String userId, String accessToken, String refreshToken) { String storedAccessToken = redisTemplate.opsForValue().get(ACCESS_TOKEN_PREFIX + userId); String storedRefreshToken = redisTemplate.opsForValue().get(REFRESH_TOKEN_PREFIX + userId); // 检查Access Token是否有效 if (storedAccessToken != null && storedAccessToken.equals(accessToken)) { return accessToken; // 直接返回原始的Access Token } // Access Token无效,检查Refresh Token if (storedRefreshToken != null && storedRefreshToken.equals(refreshToken)) { // 使用双层检查和分布式锁控制高并发刷新 String lockKey = "refresh_lock:" + userId; boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 5, TimeUnit.SECONDS); if (lockAcquired) { try { // 再次双层检查(避免重复刷新) storedAccessToken = redisTemplate.opsForValue().get(ACCESS_TOKEN_PREFIX + userId); if (storedAccessToken == null) { // 生成新的Access Token String newAccessToken = generateToken(); redisTemplate.opsForValue().set(ACCESS_TOKEN_PREFIX + userId, newAccessToken, 15, TimeUnit.MINUTES); return newAccessToken; } else { return storedAccessToken; // 直接返回已刷新过的Token } } finally { redisTemplate.delete(lockKey); // 释放锁 } } else { // 未获取到锁,等待其他线程刷新完成,再次获取已刷新的Access Token return redisTemplate.opsForValue().get(ACCESS_TOKEN_PREFIX + userId); } } // 若Refresh Token也无效,返回null或抛出异常,需重新登录 throw new TokenInvalidException("Access and Refresh Token both expired."); }
方案三:过渡时间(没看懂)
到此这篇关于java实现token无感刷新+处理并发的后端方案的文章就介绍到这了,更多相关java token无感刷新和并发内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!