简介:本文深入解析双token机制(Access Token+Refresh Token)与无感刷新实现原理,通过代码示例和架构设计说明如何提升系统安全性与用户体验,涵盖核心逻辑、异常处理及最佳实践。
在Web应用开发中,用户认证是保障系统安全的核心环节。传统JWT(JSON Web Token)方案因单token设计存在有效期管理困难、用户体验割裂等问题。双token机制(Access Token+Refresh Token)结合无感刷新技术,成为解决安全与体验平衡的高效方案。本文将从原理到实践,系统解析这一认证架构。
单token方案将用户身份信息、权限数据和过期时间全部封装在JWT中。当token过期时,用户必须重新登录,导致体验断层。更严重的是,若token泄露,攻击者可在有效期内持续访问系统,而缩短有效期又会加剧频繁登录问题。
双token体系通过两个独立token实现功能解耦:
这种设计使系统能在Access Token过期时,通过Refresh Token静默续期,用户无感知。同时,Refresh Token泄露风险更低,因其不直接携带权限数据。
sequenceDiagramClient->>+Auth Server: 用户名+密码Auth Server-->>-Client: {Access Token(15min), Refresh Token(7d)}Client->>+API Server: 请求(携带Access Token)API Server-->>-Client: 响应数据...15分钟后...Client->>+API Server: 请求(过期Access Token)API Server-->>-Client: 401 UnauthorizedClient->>+Auth Server: 刷新请求(Refresh Token)Auth Server-->>-Client: 新Access TokenClient->>+API Server: 重试请求(新Access Token)API Server-->>-Client: 响应数据
初始登录:
// 前端示例(React)const login = async (credentials) => {const res = await fetch('/api/auth/login', {method: 'POST',body: JSON.stringify(credentials)});const { accessToken, refreshToken } = await res.json();localStorage.setItem('accessToken', accessToken);document.cookie = `refreshToken=${refreshToken}; Secure; HttpOnly; SameSite=Strict; Path=/`;};
请求拦截处理:
// Axios拦截器示例apiClient.interceptors.response.use(response => response,async error => {const originalRequest = error.config;if (error.response.status === 401 && !originalRequest._retry) {originalRequest._retry = true;const refreshToken = getCookie('refreshToken');const res = await fetch('/api/auth/refresh', {method: 'POST',headers: { 'Authorization': `Bearer ${refreshToken}` }});const { accessToken } = await res.json();localStorage.setItem('accessToken', accessToken);originalRequest.headers.Authorization = `Bearer ${accessToken}`;return apiClient(originalRequest);}return Promise.reject(error);});
服务端验证逻辑:
# Flask后端示例@app.route('/api/auth/refresh', methods=['POST'])def refresh_token():refresh_token = request.headers.get('Authorization').split()[1]try:# 验证Refresh Token有效性payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=['HS256'])user_id = payload['sub']# 生成新Access Tokennew_access = jwt.encode({'sub': user_id, 'exp': datetime.utcnow() + timedelta(minutes=15)},SECRET_KEY,algorithm='HS256')return jsonify({'accessToken': new_access})except:return jsonify({'error': 'Invalid token'}), 401
为防止Refresh Token被长期滥用,应实现:
| 存储位置 | Access Token | Refresh Token |
|---|---|---|
| localStorage | ✅ 短期 | ❌ 风险高 |
| HttpOnly Cookie | ❌ 不适用 | ✅ 最佳选择 |
| Session Storage | ❌ 标签页隔离 | ❌ 不适用 |
当检测到多个并行请求因token过期失败时,可合并为单次刷新操作:
let isRefreshing = false;let subscribers = [];const subscribeTokenRefresh = (cb) => {subscribers.push(cb);return () => {subscribers = subscribers.filter(subscriber => subscriber !== cb);};};apiClient.interceptors.response.use(undefined, async (error) => {// ...前述401处理逻辑if (!isRefreshing) {isRefreshing = true;const newToken = await refreshAccessToken();subscribers.forEach(cb => cb(newToken));subscribers = [];}// ...});
配合使用CSRF Token或SameSite Cookie属性:
Set-Cookie: refreshToken=xxx; Secure; HttpOnly; SameSite=Lax
对于WebView环境,需额外处理:
在微服务架构中,可通过JWT的iss/aud字段实现跨服务认证:
{"iss": "auth-service","aud": ["api-gateway", "order-service"],"sub": "user123"}
通过双token与无感刷新机制,开发者可在保障系统安全性的同时,提供接近无感知的用户体验。实际实施时需根据业务场景调整参数,如Access Token的有效期长度、Refresh Token的旋转频率等。建议通过A/B测试验证不同配置对安全性和用户体验的影响,找到最优平衡点。