一、核心概念先理清
在做前后端分离的登录鉴权时,我们通常会用到两种 Token:
表格
| Token 类型 | 作用 | 有效期 | 安全性 | 典型场景 |
|---|---|---|---|---|
| Access Token | 用于接口鉴权,携带在请求头中,验证用户身份 | 短(如 15 分钟~2 小时) | 高,泄露后影响范围小 | 每次请求接口时携带 |
| Refresh Token | 用于在 Access Token 过期时,换取新的 Access Token | 长(如 7 天~30 天) | 较低,泄露后可长期续登 | 仅在刷新 Token 时使用 |
为什么要分两种 Token?
- 如果只用一个 Token,且有效期很长:一旦泄露,攻击者可以长期冒用身份,风险极高。
- 如果只用一个 Token,且有效期很短:用户需要频繁登录,体验极差。
- 双 Token 方案:短有效期 Access Token 保证安全,长有效期 Refresh Token 保证体验,兼顾安全与便捷。
二、完整生命周期流程
我们把用户从登录到最终重新登录的完整流程拆解为 4 个阶段:
阶段 1:首次登录,获取双 Token
- 用户输入账号密码,前端调用
/user/login接口。 - 后端验证账号密码合法后,生成:
accessToken:短有效期,用于接口鉴权。refreshToken:长有效期,用于刷新 Access Token。
- 后端将双 Token 及过期时间返回给前端(通常放在响应体
data中)。 - 前端将双 Token 存储到本地(如
localStorage、sessionStorage或安全的 Cookie)。
// 登录接口返回示例
{
"code": 200,
"msg": "登录成功",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"accessExpire": "1720000000000",
"refreshExpire": "1720604800000",
"userId": 1,
"username": "admin"
}
}
阶段 2:正常使用,Access Token 未过期
- 前端每次请求接口时,在请求头
Authorization中携带accessToken:http
Authorization: Brear eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... - 后端过滤器 / 拦截器验证
accessToken有效,放行请求,接口正常返回数据。 - 用户无感知,流畅使用软件。
阶段 3:Access Token 过期,自动刷新
当 accessToken 过期时,有两种常见的刷新方案:
方案 A:后端过滤器自动刷新(对前端透明)
- 前端携带过期的
accessToken请求接口。 - 后端过滤器捕获
ExpiredJwtException,检测到accessToken过期。 - 过滤器从请求头读取
refreshToken,验证其有效性:- 若
refreshToken有效:生成新的accessToken和refreshToken,写入响应头:Access-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... Refresh-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... Access-Control-Expose-Headers: Access-Token,Refresh-Token - 若
refreshToken无效 / 过期:返回 401,提示用户重新登录。
- 若
- 前端在响应拦截器中读取响应头的新 Token,覆盖本地存储。
- 前端用新
accessToken重新发起刚才失败的请求,用户完全无感知。
方案 B:前端拦截器主动刷新(更可控)
- 前端请求接口,收到 401 响应,判断为
accessToken过期。 - 前端调用
/user/refresh/token接口,携带本地存储的refreshToken:http
POST /user/refresh/token Content-Type: application/x-www-form-urlencoded refreshToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... - 后端验证
refreshToken有效后,返回新的双 Token。 - 前端更新本地存储的 Token,并重试刚才的请求。
- 若刷新接口也返回 401(
refreshToken过期),清除本地 Token 并跳转到登录页。
阶段 4:Refresh Token 过期,必须重新登录
- 当
refreshToken也过期时,无论哪种刷新方案,后端都会返回 401:json
{ "code": 401, "msg": "登录已过期,请重新登录", "data": null } - 前端捕获 401 响应,清除本地存储的所有 Token。
- 前端跳转到登录页面,提示用户重新输入账号密码登录。
- 回到阶段 1,重新获取双 Token。
三、关键知识点总结
1. Token 过期逻辑
- Access Token 过期:可以用
refreshToken刷新,用户无感知。 - Refresh Token 过期:无法刷新,必须重新登录。
- 每次刷新时,建议生成新的
refreshToken,实现「活跃用户无限续期」:只要用户在refreshToken有效期内使用软件,就能一直续登,直到长期不使用导致refreshToken过期。
2. 安全注意事项
- 传输安全:必须使用 HTTPS,防止 Token 在传输过程中被窃听。
- 存储安全:前端避免将 Token 存在
localStorage(易受 XSS 攻击),推荐存在HttpOnlyCookie 或使用secure+sameSite配置。 - 泄露处理:若
refreshToken泄露,攻击者可长期续登,建议后端实现refreshToken黑名单机制,登出 / 改密时将旧refreshToken加入黑名单。 - 最小权限:
refreshToken仅用于刷新 Token,不能用于接口鉴权。
3. 前端实现要点
- 请求拦截器:统一在请求头添加
Authorization: {tokenPrefix} {accessToken}。 - 响应拦截器:
- 捕获 401 响应,判断是否为 Token 过期。
- 若为
accessToken过期,调用刷新接口。 - 刷新成功后,更新本地 Token 并重试原请求。
- 刷新失败(
refreshToken过期),跳转到登录页。
- Header 读取:若使用后端过滤器自动刷新,需从响应头读取
Access-Token和Refresh-Token,注意配置Access-Control-Expose-Headers让前端可读取。
4. 后端实现要点
- Token 生成:
accessToken包含用户身份信息(如userId、username),refreshToken仅包含用户名即可。 - Token 验证:
- 验证
accessToken时,若过期则尝试用refreshToken刷新。 - 验证
refreshToken时,若过期则直接拒绝,要求重新登录。
- 验证
- 接口设计:
/user/login:生成双 Token。/user/refresh-token:用refreshToken换取新双 Token。/user/token/check:仅解析验证 Token,不生成新 Token。
- 过滤器 / 拦截器:统一处理 Token 验证和刷新逻辑,避免在每个接口中重复实现。
四、前端响应拦截器代码
以下是基于 Axios 的前端响应拦截器实现,处理 Token 自动刷新
import axios from 'axios';
const request = axios.create({
baseURL: '/api',
timeout: 5000
});
// 请求拦截器:添加 Authorization
request.interceptors.request.use(config => {
const accessToken = localStorage.getItem('accessToken');
if (accessToken) {
config.headers.Authorization = `Brear ${accessToken}`;
}
return config;
});
// 响应拦截器:处理 Token 过期和刷新
request.interceptors.response.use(
response => {
// 从响应头读取新 Token(后端过滤器自动刷新场景)
const newAccessToken = response.headers['access-token'];
const newRefreshToken = response.headers['refresh-token'];
if (newAccessToken && newRefreshToken) {
localStorage.setItem('accessToken', newAccessToken);
localStorage.setItem('refreshToken', newRefreshToken);
}
return response.data;
},
async error => {
const { response } = error;
if (response && response.status === 401) {
const msg = response.data?.msg;
// 情况 1:refreshToken 也过期,必须重新登录
if (msg.includes('登录已过期')) {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
window.location.href = '/login';
return Promise.reject(error);
}
// 情况 2:accessToken 过期,尝试刷新
const refreshToken = localStorage.getItem('refreshToken');
if (!refreshToken) {
window.location.href = '/login';
return Promise.reject(error);
}
try {
// 调用刷新接口
const res = await axios.post('/user/refresh-token', { refreshToken });
const { token, refreshToken: newRefreshToken } = res.data.data;
// 更新本地 Token
localStorage.setItem('accessToken', token);
localStorage.setItem('refreshToken', newRefreshToken);
// 重试原请求
error.config.headers.Authorization = `Brear ${token}`;
return request(error.config);
} catch (refreshErr) {
// 刷新失败(refreshToken 过期),跳登录
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
window.location.href = '/login';
return Promise.reject(refreshErr);
}
}
return Promise.reject(error);
}
);
export default request;
五、总结 ✨
双 Token 无感刷新是现代 Web 应用的标准鉴权方案,核心逻辑是:
- 短有效期 Access Token 保证接口鉴权安全。
- 长有效期 Refresh Token 保证用户体验,避免频繁登录。
- Access Token 过期时,用 Refresh Token 自动刷新,用户无感知。
- Refresh Token 过期时,强制重新登录,保证账号安全。
只要实现了这套流程,用户就能在「一直使用软件」的情况下,永远不需要手动重新登录,直到长期不使用导致 Refresh Token 自然过期,完美平衡了安全性和用户体验。
&spm=1001.2101.3001.5002&articleId=162206767&d=1&t=3&u=a7a797142067429abf29b1cf56eee6fc)
1万+

被折叠的 条评论
为什么被折叠?



