简介:本文详解双token机制与无感刷新token的实现原理,通过Access Token+Refresh Token的组合设计,结合自动续期策略,解决单token频繁失效、手动登录等问题,提升系统安全性和用户体验。
在Web开发中,JWT(JSON Web Token)是常用的认证方式。其核心流程为:用户登录后,服务器生成包含用户信息的token并返回客户端,后续请求携带该token进行身份验证。然而,这种单token设计存在明显缺陷。
首先,token有效期设置面临两难:有效期过短(如1小时),用户需频繁登录,体验差;有效期过长(如7天),一旦token泄露,攻击者可长期冒用。其次,token过期后,客户端必须重新登录获取新token,中断业务流程。例如,用户填写复杂表单时token过期,所有输入数据丢失,导致严重体验问题。
以电商系统为例,用户浏览商品时token过期,需重新登录才能加入购物车,可能直接导致用户流失。据统计,30%的电商网站因认证流程繁琐导致用户放弃操作。
双token机制通过引入Access Token和Refresh Token两个独立token,解决单token的痛点。
用户登录后,服务器返回:
{"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","refresh_token": "8f3e2a1b...","access_expires_in": 900,"refresh_expires_in": 2592000}
客户端存储Access Token于内存,Refresh Token于HttpOnly Cookie。每次请求携带Access Token,过期后使用Refresh Token获取新Access Token,无需用户参与。
无感刷新的核心是:在Access Token过期前自动获取新token,保持业务连续性。
使用Axios等库的请求拦截器:
axios.interceptors.request.use(async (config) => {const { accessToken, refreshToken } = storeToken();// 检查token是否即将过期(提前5分钟)const isExpiringSoon = isTokenExpiring(accessToken, 300);if (isExpiringSoon) {try {const newTokens = await refreshTokens(refreshToken);storeToken(newTokens);config.headers.Authorization = `Bearer ${newTokens.accessToken}`;} catch (error) {redirectToLogin();}} else if (!accessToken) {redirectToLogin();} else {config.headers.Authorization = `Bearer ${accessToken}`;}return config;});
设置Access Token有效期为15分钟,但实际在第12分钟时触发刷新:
const REFRESH_THRESHOLD = 12 * 60 * 1000; // 12分钟function scheduleRefresh(expiresAt) {const now = Date.now();const delay = expiresAt - now - REFRESH_THRESHOLD;setTimeout(async () => {try {const { accessToken } = await refreshTokens();storeAccessToken(accessToken);} catch (error) {console.error('刷新失败', error);}}, delay);}
@PostMapping("/refresh")public ResponseEntity<?> refreshToken(@CookieValue("refresh_token") String refreshToken) {try {// 验证refresh token有效性if (!tokenService.validateRefreshToken(refreshToken)) {return ResponseEntity.status(401).body("无效的refresh token");}// 生成新access tokenString newAccessToken = tokenService.generateAccessToken();// 更新refresh token(实现一次性使用)String newRefreshToken = tokenService.rotateRefreshToken(refreshToken);return ResponseEntity.ok().header("Set-Cookie", "refresh_token=" + newRefreshToken + "; HttpOnly; Path=/").body(Map.of("access_token", newAccessToken));} catch (Exception e) {return ResponseEntity.status(500).body("刷新失败");}}
防止多个请求同时触发refresh:
private final ConcurrentHashMap<String, CompletableFuture<TokenPair>> refreshLocks = new ConcurrentHashMap<>();public TokenPair refreshTokens(String oldRefreshToken) {CompletableFuture<TokenPair> future = refreshLocks.computeIfAbsent(oldRefreshToken,k -> CompletableFuture.supplyAsync(() -> {// 实际刷新逻辑TokenPair newTokens = doRefresh(oldRefreshToken);refreshLocks.remove(oldRefreshToken);return newTokens;}));return future.join();}
某金融平台采用双token机制后:
其实现要点:
双token与无感刷新机制通过合理的token分工和自动化续期策略,在安全性和用户体验间取得平衡。开发者应根据业务场景调整token有效期、存储方式和刷新策略,构建既安全又便捷的认证体系。