jwt的token如何刷新?

1、为什么要刷新Token的过期时间?

Token都有过期时间。那么问题来了,假设Token过期时间为15天,用户在第14天的时候,还可以免登录正常访问系统。但是到了第15天,用户的Token过期,于是用户需要重新登录系统。

HttpSession的过期时间比较优雅,默认为15分钟。如果用户连续使用系统,只要间隔时间不超过15分钟,系统就不会销毁HttpSession对象。JWT的令牌过期时间能不能做成HttpSession那样超时时间,只要用户间隔操作时间不超过15天,系统就不需要用户重新登录系统。实现这种效果的方案有两种:双TokenToken缓存,这里重点讲一下Token缓存方案。
在这里插入图片描述
Token缓存方案是把Token缓存到Redis,然后设置Redis里面缓存的Token过期时间为正常Token的1倍,然后根据情况刷新Token的过期时间。

1、Token失效,缓存也不存在的情况

当第15天,用户的Token失效以后,我们让Shiro程序到Redis查看是否存在缓存的Token,如果这个Token不存在于Redis里面,就说明用户的操作间隔了15天,需要重新登录。

2、Token失效,但是缓存还存在的情况

如果Redis中存在缓存的Token,说明当前Token失效后,间隔时间还没有超过15天,不应该让用户重新登录。所以要生成新的Token返回给客户端,并且把这个Token缓存到Redis里面,这种操作成为刷新Token过期时间。

2、客户端如何更新令牌?

在我们的方案中,服务端刷新Token过期时间,其实就是生成一个新的Token给客户端。那么客户端怎么知道这次响应带回来的Token是更新过的呢?这个问题很容易解决。
在这里插入图片描述

只要用户成功登陆系统,当后端服务器更新Token的时候,就在响应中添加Token。客户端那边判断每次Ajax响应里面是否包含Token,如果包含,就把Token保存起来就可以了。

3、如何在响应中添加令牌?

img
我们定义OAuth2Filter类拦截所有的HTTP请求,一方面它会把请求中的Token字符串提取出来,封装成对象交给Shiro框架;另一方面,它会检查Token的有效性。如果Token过期,那么会生成新的Token,分别存储在ThreadLocalTokenRedis中。

之所以要把新令牌保存到ThreadLocalToken里面,是因为要向AOP切面类传递这个新令牌。虽然OAuth2Filter中有doFilterInternal()方法,我们可以得到响应并且写入新令牌。但是这个做非常麻烦,首先我们要通过IO流读取响应中的数据,然后还要把数据解析成JSON对象,最后再放入这个新令牌。如果我们定义了AOP切面类,拦截所有Web方法返回的R对象,然后在R对象里面添加新令牌,这多简单啊。但是OAuth2FilterAOP切面类之间没有调用关系,所以我们很难把新令牌传给AOP切面类

这里我想到了ThreadLocal,只要是同一个线程,往ThreadLocal里面写入数据和读取数据是完全相同的。在Web项目中,从OAuth2FilterAOP切面类,都是由同一个线程来执行的,中途不会更换线程。所以我们可以放心的把新令牌保存都在ThreadLocal里面,AOP切面类可以成功的取出新令牌,然后往R对象里面添加新令牌即可。

ThreadLocalToken是我自定义的类,里面包含了ThreadLocal类型的变量,可以用来保存线程安全的数据,而且避免了使用线程锁。

如果以上有疑问,可以在评论区进行交流沟通。
如果以上有帮助,点个关注,相互交流,共同进步。

在使用 JWT(JSON Web Token)进行身份验证时,token 通常是有时效性的(如 1 小时),为了避免用户频繁登录,需要实现 token 的**刷新机制**。通常的做法是引入一个**refresh token**,它具有更长的有效期,并用于获取新的 access token。 --- ## 刷新 JWT Token 的流程 ### 步骤如下: 1. **用户登录成功**后,服务端返回: - `access_token`(短时效) - `refresh_token`(长时效) 2. 前端将这两个 token 存储起来(例如:`localStorage` 或 `httpOnly cookie`)。 3. 当 `access_token` 过期后,前端携带 `refresh_token` 请求刷新接口。 4. 后端验证 `refresh_token` 是否有效: - 如果有效,生成新的 `access_token` 返回。 - 如果无效(如过期、被篡改),要求用户重新登录。 5. 用户继续访问受保护资源。 --- ## 示例代码(Node.js + Express) ### 安装依赖 ```bash npm install express jsonwebtoken body-parser cookie-parser ``` ### 后端代码 ```javascript const express = require('express'); const jwt = require('jsonwebtoken'); const bodyParser = require('body-parser'); const cookieParser = require('cookie-parser'); const app = express(); app.use(bodyParser.json()); app.use(cookieParser()); const ACCESS_SECRET = 'access-secret-key'; const REFRESH_SECRET = 'refresh-secret-key'; // 模拟数据库存储 refresh tokens let refreshTokens = []; // 登录接口:生成 access token 和 refresh token app.post('/login', (req, res) => { const { username, password } = req.body; // 实际应查库验证用户名密码 if (username !== 'user' || password !== 'pass') { return res.status(400).json({ message: 'Invalid credentials' }); } const accessToken = jwt.sign({ username }, ACCESS_SECRET, { expiresIn: '15s' }); // 短时效 const refreshToken = jwt.sign({ username }, REFRESH_SECRET, { expiresIn: '7d' }); refreshTokens.push(refreshToken); res.cookie('refreshToken', refreshToken, { httpOnly: true, maxAge: 7 * 24 * 60 * 60 * 1000 }); // 7天 res.json({ accessToken }); }); // 刷新 token 接口 app.post('/refresh-token', (req, res) => { const refreshToken = req.cookies.refreshToken; if (!refreshToken) return res.sendStatus(401); if (!refreshTokens.includes(refreshToken)) return res.sendStatus(403); jwt.verify(refreshToken, REFRESH_SECRET, (err, user) => { if (err) return res.sendStatus(403); const accessToken = jwt.sign({ username: user.username }, ACCESS_SECRET, { expiresIn: '15s' }); res.json({ accessToken }); }); }); // 注销接口:移除 refresh token app.post('/logout', (req, res) => { const refreshToken = req.cookies.refreshToken; refreshTokens = refreshTokens.filter(t => t !== refreshToken); res.clearCookie('refreshToken'); res.json({ message: 'Logged out' }); }); app.listen(3000, () => console.log('Server running on port 3000')); ``` ### 前端调用示例(JavaScript fetch) ```javascript async function login() { const res = await fetch('http://localhost:3000/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'user', password: 'pass' }) }); const data = await res.json(); localStorage.setItem('accessToken', data.accessToken); } async function getProtectedResource() { let token = localStorage.getItem('accessToken'); try { const res = await fetch('http://localhost:3000/protected', { headers: { Authorization: `Bearer ${token}` } }); if (res.status === 403) { // access token 失效,尝试刷新 const refreshRes = await fetch('http://localhost:3000/refresh-token', { method: 'POST', credentials: 'include' }); if (!refreshRes.ok) throw new Error('Refresh failed'); const newToken = await refreshRes.json(); localStorage.setItem('accessToken', newToken.accessToken); token = newToken.accessToken; // 再次尝试请求 const retryRes = await fetch('http://localhost:3000/protected', { headers: { Authorization: `Bearer ${token}` } }); const retryData = await retryRes.json(); console.log(retryData); } else { const data = await res.json(); console.log(data); } } catch (error) { console.error(error); } } ``` --- ## 解释说明: - `refresh token` 一般通过 `httpOnly cookie` 存储,防止 XSS 攻击。 - `access token` 是短期的,避免长时间暴露。 - 使用数组 `refreshTokens` 来管理有效的 refresh token,实际应用中建议使用 Redis 等持久化方式。 - 登出时需从列表中删除 refresh token,以保证安全性。 --- ##
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

loongloongz

相互鼓励,相互帮助,共同进步。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值