1. 背景
在前后端分离架构下,用户登录成功后,后端服务会颁发一个 token
作为用户身份凭证。前端(如 Vue 应用)在接收到 token
后,通常将其存储到 LocalStorage
,并在每次请求时通过请求头携带该 token
访问后端服务。
后端服务会在过滤器中对 token
进行校验,包括合法性和是否过期。当 token
已过期时,后端会返回错误信息,引导前端跳转至登录页面,要求用户重新登录。

这种机制虽然简单,但在实际使用中存在一个明显的用户体验问题。例如:
-
• 用户正在填写一个较复杂的表单,提交时发现
accessToken
已过期,不得不重新登录并重新填写; -
• 用户在持续操作系统,但由于
accessToken
有固定时效,依旧会在某一时刻被强制退出。
这类场景都会造成较差的用户体验。那么,有没有办法在用户活跃操作时,自动延长 token
的有效期呢?
本文将介绍两种常见的自动续期方案:基于前端的刷新机制 和 基于后端的自动续期机制。
2. 自动续期实现方案
2.1. 基于前端的自动续期(Refresh Token)
如果系统采用 OAuth2 协议进行认证,并且支持 Refresh Token,就可以通过前端实现自动续期。
典型的认证响应如下:
{
"access_token": "eyJhbGciOiJFUzI1NiIs**********X6wrZHYKDxJkWwhdkrYg",
"token_type": "Bearer",
"expires_in": 7200,
"refresh_token": "eyJhbGciOiJFUzI1NiIs**********XXOYOZz1mfgIYHwM8ZJA",
"refresh_token_expires_in": 604800
......
}
说明:
-
•
access_token
:访问令牌(通常有效期 1~2 小时),前端调用后端接口时使用。 -
•
refresh_token
:刷新令牌(有效期较长,常见 7~30 天),用于在access_token
过期时获取新的access_token
。
此时,可以在前端通过 响应拦截器 自动处理续期逻辑:
// Axios响应拦截器实现自动续期
axios.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const refreshToken = localStorage.getItem('refresh_token');
const refreshResponse = await axios.post('/auth/refresh', {
refresh_token: refreshToken
});
// 更新Token
localStorage.setItem('access_token', refreshResponse.data.access_token);
localStorage.setItem('refresh_token', refreshResponse.data.refresh_token);
// 重新发送原始请求
originalRequest.headers.Authorization = `Bearer ${refreshResponse.data.access_token}`;
returnaxios(originalRequest);
} catch (refreshError) {
// 续期失败,清除Token并跳转登录
localStorage.clear();
window.location.href = '/login';
returnPromise.reject(refreshError);
}
}
returnPromise.reject(error);
}
);
这种方式的优点是清晰易懂,基于 OAuth2 标准实现;缺点是必须有 refresh_token 机制 支持,否则无法使用。
2.2 基于后端实现自动续期
在很多项目中,认证并未采用双 token 模式,而是只有一个 accessToken
(通常为 JWT)。此时,可以在后端引入一层"间接认证"来实现自动续期。
实现思路如下:
登录时生成双 token
-
• 登录成功后生成一个 uuidToken (随机 UUID),同时生成一个
accessToken
; -
• 将
uuidToken
作为 key,accessToken
(及用户信息)作为 value 存入缓存,缓存过期时间为accessToken
有效期的 2 倍; -
• 返回
uuidToken
给前端。
注意:JWT 本身长度较长,不适合作为缓存 key,因此使用短 UUID 替代。
请求拦截与校验
-
• 前端请求时携带
uuidToken
; -
• 后端通过
uuidToken
从缓存中获取accessToken
; -
• 若缓存不存在,则判定为用户长时间未操作,要求重新登录;
-
• 若
accessToken
已过期,但uuidToken
未过期,说明用户仍在活跃操作,此时后端可为其生成新的accessToken
并覆盖缓存,从而实现自动续期。
前端无感知
-
• 前端始终只持有
uuidToken
; -
•
accessToken
的变化仅在后端进行更新,对前端无影响。
登录逻辑示例
public String login(String userName, String password) {
StringuuidToken= UUID.randomUUID().toString();
SysUsersysUser= userService.getUserByUserName(userName);
// ...认证逻辑...
StringaccessToken= JwtUtil.createJWT(sysUser);
LoginUserVOloginUserVO=newLoginUserVO();
loginUserVO.setUserAccount(sysUser.getUserId());
loginUserVO.setName(sysUser.getName());
loginUserVO.setRole("Manager");
loginUserVO.setAccessToken(accessToken);
// 存储用户信息至缓存
userTokenService.storeUserToken(uuidToken, loginUserVO);
return uuidToken;
}
后端请求过滤器示例
@Slf4j
@Component
publicclassTokenFilterextendsOncePerRequestFilter {
@Override
protectedvoiddoFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)throws IOException {
try {
StringrequestPath= request.getRequestURI();
// 白名单放行
if (isWhiteListPath(requestPath)) {
filterChain.doFilter(request, response);
return;
}
// 获取UUID Token
StringuuidToken= getAccessToken(request);
if (uuidToken == null) {
sendUnauthorizedResponse(response, "Missing token");
return;
}
// 获取用户信息
LoginUserVOloginUserVO= userTokenService.getUserByToken(uuidToken);
if (loginUserVO == null) {
sendUnauthorizedResponse(response, "Token expired or invalid");
return;
}
// 检查并续期
loginUserVO = userTokenService.checkAndRefreshIfNeeded(uuidToken, loginUserVO);
// 设置用户上下文
UserContextHolder.setContext(UserContext.fromUserToken(uuidToken, loginUserVO));
filterChain.doFilter(request, response);
} finally {
UserContextHolder.clearContext();
}
}
private String getAccessToken(HttpServletRequest request) {
Stringtoken= request.getHeader("Authorization");
return (token != null) ? token : request.getParameter("Authorization");
}
}
3. 总结
在前后端分离的应用中,token
的续期机制直接影响用户体验。
-
• 基于前端的方案(Refresh Token)适用于标准 OAuth2 认证体系,方案清晰,但依赖协议支持;
-
• 基于后端的方案 (UUID Token + 自动刷新)则适用于只有单一
accessToken
的场景,能在后端无感知地为用户自动续期。
在实际项目中,应根据系统架构和认证机制选择合适的续期方案,从而在安全性和用户体验之间取得平衡。