58、如何实现token的刷新:询问在token过期后如何实现其刷新

张开发
2026/4/17 3:49:11 15 分钟阅读

分享文章

58、如何实现token的刷新:询问在token过期后如何实现其刷新
这是前端面试里非常常见的问题核心是access token有效期短用来请求接口refresh token有效期长用来换新的 access token当access token过期时不要求用户重新登录而是通过refresh token去服务端换一个新的access token。一、典型方案一般会有两种 token1. Access Token用于正常接口请求有效期较短比如2h2. Refresh Token用于刷新 access token有效期较长比如7天/30天二、整体流程1. 用户登录成功 2. 服务端返回 accessToken refreshToken 3. 前端请求接口时带上 accessToken 4. 如果接口返回 token 过期如 401 5. 前端拿 refreshToken 去调用刷新接口 6. 刷新成功后拿到新的 accessToken 7. 用新的 accessToken 重新发起刚才失败的请求 8. 如果 refreshToken 也失效则跳转登录页三、实现思路前端一般在axios 响应拦截器中处理如果响应正常直接返回如果返回401且是 access token 过期调用刷新 token 接口更新本地 token把原请求重新发送一次四、基础版实现1. 登录后保存 tokenlocalStorage.setItem(accessToken, res.accessToken); localStorage.setItem(refreshToken, res.refreshToken);2. 请求拦截器自动带上 access tokenimport axios from axios; const service axios.create({ baseURL: /api }); service.interceptors.request.use(config { const token localStorage.getItem(accessToken); if (token) { config.headers.Authorization Bearer ${token}; } return config; });3. 响应拦截器遇到 401 时刷新 tokenservice.interceptors.response.use( response response, async error { const originalRequest error.config; if (error.response?.status 401 !originalRequest._retry) { originalRequest._retry true; const refreshToken localStorage.getItem(refreshToken); try { const res await axios.post(/auth/refresh, { refreshToken }); const newAccessToken res.data.accessToken; localStorage.setItem(accessToken, newAccessToken); originalRequest.headers.Authorization Bearer ${newAccessToken}; return service(originalRequest); } catch (refreshError) { localStorage.removeItem(accessToken); localStorage.removeItem(refreshToken); window.location.href /login; return Promise.reject(refreshError); } } return Promise.reject(error); } );五、面试官更关注的问题并发请求怎么处理这才是重点。假设某一刻 token 过期了同时发出了 10 个请求这 10 个请求都会返回 401如果每个请求都去刷新 token就会导致重复刷新接口压力大可能出现 token 覆盖问题所以需要做“单飞”控制只允许一个刷新请求发出去其他失败的请求先等待刷新成功后统一拿新 token 重新请求六、进阶版刷新队列方案核心变量let isRefreshing false; let requests [];isRefreshing是否正在刷新 tokenrequests存放等待重试的请求完整实现示例import axios from axios; const service axios.create({ baseURL: /api }); let isRefreshing false; let requests []; function refreshTokenRequest(refreshToken) { return axios.post(/auth/refresh, { refreshToken }); } service.interceptors.request.use(config { const accessToken localStorage.getItem(accessToken); if (accessToken) { config.headers.Authorization Bearer ${accessToken}; } return config; }); service.interceptors.response.use( res res, async error { const { config, response } error; if (!response) { return Promise.reject(error); } if (response.status ! 401) { return Promise.reject(error); } // 防止刷新接口自己进入死循环 if (config.url.includes(/auth/refresh)) { localStorage.removeItem(accessToken); localStorage.removeItem(refreshToken); window.location.href /login; return Promise.reject(error); } const refreshToken localStorage.getItem(refreshToken); if (!refreshToken) { window.location.href /login; return Promise.reject(error); } // 如果已经在刷新让当前请求进入队列等待 if (isRefreshing) { return new Promise(resolve { requests.push((token) { config.headers.Authorization Bearer ${token}; resolve(service(config)); }); }); } config._retry true; isRefreshing true; try { const res await refreshTokenRequest(refreshToken); const newAccessToken res.data.accessToken; const newRefreshToken res.data.refreshToken || refreshToken; localStorage.setItem(accessToken, newAccessToken); localStorage.setItem(refreshToken, newRefreshToken); // 唤醒队列中等待的请求 requests.forEach(cb cb(newAccessToken)); requests []; // 重试当前请求 config.headers.Authorization Bearer ${newAccessToken}; return service(config); } catch (err) { requests []; localStorage.removeItem(accessToken); localStorage.removeItem(refreshToken); window.location.href /login; return Promise.reject(err); } finally { isRefreshing false; } } );七、这个方案的关键点1. 为什么要加isRefreshing避免多个请求同时触发刷新。2. 为什么要有requests队列当第一个请求在刷新 token 时其他 401 请求不能再去刷新只能先挂起等刷新成功后再重试。3. 为什么要排除刷新接口本身如果/refresh接口返回 401再去触发刷新会形成死循环。八、推荐的存储方式这个问题面试里也经常会追问。方案 1存 localStorage优点实现简单缺点有 XSS 风险方案 2refresh token 放 HttpOnly Cookie更推荐access token存在内存中 / 短期存储refresh token放在HttpOnly Cookie中这样前端 JS 拿不到 refresh token更安全。刷新时浏览器会自动带上 cookieaxios.post(/auth/refresh, {}, { withCredentials: true })九、更安全的实践实际项目里更推荐这样设计推荐方案access token短效refresh token长效 HttpOnly Secure SameSite服务端维护 refresh token 的有效性refresh token 最好支持轮换机制rotationRefresh Token Rotation每次刷新时服务端不只返回新 access token还返回一个新的 refresh token旧 refresh token 立即失效这样即使 refresh token 泄露也能降低风险。十、面试回答模板你可以直接这样说一般会采用 access token 和 refresh token 双 token 机制。access token 有效期较短请求接口时放在请求头里refresh token 有效期较长用于在 access token 过期后换发新的 access token。前端通常在响应拦截器里统一处理 401当发现 token 过期时调用刷新接口获取新 token然后把失败的原请求重新发送。如果存在多个并发请求同时过期我会加一个isRefreshing锁并维护一个请求队列保证只发一次刷新请求其余请求等待刷新成功后再统一重试。如果 refresh token 也失效则清空登录态并跳转登录页。安全上更推荐把 refresh token 放在 HttpOnly Cookie 中避免被 JS 读取从而降低 XSS 风险。十一、简化版伪代码if (接口返回 401) { if (没有在刷新) { 标记正在刷新; 调用 refresh 接口; 刷新成功后 保存新 token; 重试当前请求; 重放队列中的请求; 刷新失败后 跳转登录; } else { 当前请求进入等待队列; } }十二、你可以这样写成一个工具函数思路let isRefreshing false; let queue []; async function handleTokenExpire(originalRequest) { if (isRefreshing) { return new Promise(resolve { queue.push((token) { originalRequest.headers.Authorization Bearer ${token}; resolve(service(originalRequest)); }); }); } isRefreshing true; try { const newToken await refreshToken(); queue.forEach(cb cb(newToken)); queue []; originalRequest.headers.Authorization Bearer ${newToken}; return service(originalRequest); } finally { isRefreshing false; } }十三、可能的追问1. token 快过期了能否提前刷新可以。比如在请求前解析 token 过期时间如果发现剩余 5 分钟内过期先静默刷新再发业务请求这叫无感刷新。2. 刷新失败怎么办清空本地登录状态跳转登录页提示用户重新登录3. 为什么不把 access token 也放 cookie可以但要看系统设计。常见做法若走纯 cookie session 风格可以都放 cookie若走前后端分离 JWT 方案常见是 access token 放请求头refresh token 放 HttpOnly Cookie十四、总结一句话概括通过响应拦截器监听 401使用 refresh token 换取新的 access token并将失败请求重试为避免并发刷新需要加锁和请求队列。

更多文章