0


前端无感刷新token

在这里插入图片描述
摘要:

Axios 无感知刷新令牌是一种在前端应用中实现自动刷新访问令牌(access token)的技术,确保用户在进行 API 请求时不会因为令牌过期而中断操作

目录概览

  • 访问令牌(Access Token):用于访问受保护资源的凭证,通常有一定的有效期。
  • 刷新令牌(Refresh Token):用于获取新的访问令牌,当访问令牌过期时使用。

实现步骤:

  1. 设置拦截器:在 Axios的请求拦截器中添加逻辑,检查当前时间与令牌的过期时间。如果访问令牌已过期但刷新令牌仍然有效,则调用刷新令牌接口获取新的访问令牌。
  2. 更新令牌存储:一旦获得新的访问令牌,将其存储到 localStorage、Vuex 或其他状态管理工具中,以便后续请求使用新令牌。
  3. 重试原始请求:在成功刷新令牌后,重新发送被拦截的请求,此时使用新的访问令牌。

XMLHttpRequest

  1. // 创建 XMLHttpRequest 实例
  2. const xhr = new XMLHttpRequest();
  3. // 登录成功后保存 Token 和 Refresh Token
  4. function onLoginSuccess(response){
  5. localStorage.setItem('accessToken', response.data.accessToken);
  6. localStorage.setItem('refreshToken', response.data.refreshToken);}
  7. // 发起请求的函数
  8. function sendRequest(url, method, data){return new Promise((resolve, reject)=> {
  9. xhr.open(method, url);
  10. xhr.setRequestHeader('Authorization', `Bearer ${localStorage.getItem('accessToken')}`);
  11. xhr.onreadystatechange = function() {
  12. if (xhr.readyState ===4) {
  13. if (xhr.status ===200) {
  14. resolve(JSON.parse(xhr.responseText));}else{
  15. reject({ status: xhr.status, response: xhr.responseText });}}};if(method ==='POST'&& data){
  16. xhr.send(JSON.stringify(data));}else{
  17. xhr.send();}});}
  18. // 刷新 Token 的函数
  19. async functionrefreshToken(){
  20. const refreshToken = localStorage.getItem('refreshToken');
  21. const response = await fetch('/path/to/refresh', {
  22. method: 'POST',
  23. headers: {'Content-Type':'application/json',
  24. },
  25. body: JSON.stringify({ refresh_token: refreshToken }),
  26. });
  27. const res = await response.json();if(res.success){
  28. localStorage.setItem('accessToken', res.data.newAccessToken);returntrue; // 表示刷新成功
  29. }else{returnfalse; // 表示刷新失败
  30. }}
  31. // 拦截响应并处理 Token 刷新
  32. xhr.addEventListener('readystatechange', function(){if(xhr.readyState ===4&& xhr.status ===401){
  33. refreshToken().then(refreshed =>{if(refreshed){
  34. xhr.setRequestHeader('Authorization', `Bearer ${localStorage.getItem('accessToken')}`);
  35. xhr.send(); // 重新发送请求
  36. }else{
  37. alert('请重新登录'); // Token 刷新失败,可能需要用户重新登录
  38. }});}});

Axios

  1. import axios from 'axios';
  2. // 创建 Axios 实例
  3. const apiClient = axios.create({
  4. baseURL: 'https://your-api-url.com',
  5. // 其他配置...
  6. });
  7. // 响应拦截器
  8. apiClient.interceptors.response.use(response =>{return response;}, error =>{
  9. const { response }= error;if(response && response.status ===401){return refreshToken().then(refreshed =>{if(refreshed){
  10. // 令牌刷新成功,重试原始请求
  11. return apiClient.request(error.config);}else{
  12. // 令牌刷新失败,可能需要用户重新登录
  13. return Promise.reject(error);}});}return Promise.reject(error);});
  14. // 令牌刷新函数
  15. functionrefreshToken(){return apiClient.post('/path/to/refresh', {
  16. // 刷新令牌所需的参数,例如 refresh_token
  17. }).then(response =>{if(response.data.success){
  18. // 假设响应数据中包含新的访问令牌
  19. const newAccessToken = response.data.newAccessToken;
  20. // 更新令牌存储
  21. localStorage.setItem('accessToken', newAccessToken);
  22. // 更新 Axios 实例的 headers,以便后续请求使用新令牌
  23. apiClient.defaults.headers.common['Authorization']=`Bearer ${newAccessToken}`;returntrue; // 表示刷新成功
  24. }else{returnfalse; // 表示刷新失败
  25. }});}

Fetch API

  1. // 定义一个函数来处理Fetch请求
  2. async function fetchWithToken(url, options ={}){
  3. const token = localStorage.getItem('token');if(token){
  4. options.headers ={...options.headers,
  5. 'Authorization':`Bearer ${token}`};}
  6. try {
  7. const response = await fetch(url, options);if(response.status ===401){ // 假设401表示令牌过期
  8. const refreshToken = localStorage.getItem('refreshToken');if(!refreshToken){
  9. throw new Error('No refresh token available');}
  10. // 调用刷新令牌接口
  11. const refreshResponse = await fetch('/api/refresh-token', {
  12. method: 'POST',
  13. headers: {'Content-Type':'application/json'},
  14. body: JSON.stringify({ refreshToken })});if(refreshResponse.ok){
  15. const data = await refreshResponse.json();
  16. localStorage.setItem('token', data.newAccessToken);
  17. // 重新尝试原始请求
  18. options.headers['Authorization']=`Bearer ${data.newAccessToken}`;return fetch(url, options);}else{
  19. throw new Error('Failed to refresh token');}}return response;} catch (error){
  20. console.error('Fetch error:', error);
  21. throw error;}}
  22. // 使用示例
  23. fetchWithToken('/api/protected-resource')
  24. .then(response => response.json())
  25. .then(data => console.log(data))
  26. .catch(error => console.error('Error:', error));
  • fetchWithToken函数: 这是一个封装了Fetch API的函数,它首先检查本地存储中的访问令牌是否存在,并在请求头中添加该令牌。如果响应状态码为401(表示令牌过期),则尝试使用刷新令牌获取新的访问令牌,并重新发送原始请求。
  • 刷新令牌逻辑: 在检测到令牌过期时,函数会调用刷新令牌接口,并将新的访问令牌存储到本地存储中。然后,它会重新设置请求头中的授权信息,并重新发送原始请求。
  • 错误处理: 如果在刷新令牌或发送请求的过程中发生错误,函数会抛出相应的错误,并在控制台中记录错误信息。

JQ

  1. // 创建 JQuery 实例
  2. const apiClient = $.ajaxSetup({
  3. baseURL: 'https://your-api-url.com',
  4. // 其他配置...
  5. });
  6. // 响应拦截器
  7. $.ajaxSetup({
  8. complete: function(jqXHR, textStatus){if(textStatus ==='error'&& jqXHR.status ===401){return refreshToken().then(refreshed =>{if(refreshed){
  9. // 令牌刷新成功,重试原始请求
  10. return apiClient.request(this);}else{
  11. // 令牌刷新失败,可能需要用户重新登录
  12. alert('请重新登录');}});}}});
  13. // 令牌刷新函数
  14. functionrefreshToken(){return $.ajax({
  15. url: '/path/to/refresh',
  16. method: 'POST',
  17. data: {
  18. refresh_token: localStorage.getItem('refreshToken')},
  19. dataType: 'json'}).then(response =>{if(response.data.success){
  20. // 假设响应数据中包含新的访问令牌
  21. const newAccessToken = response.data.newAccessToken;
  22. // 更新令牌存储
  23. localStorage.setItem('accessToken', newAccessToken);
  24. // 更新 JQuery 实例的 headers,以便后续请求使用新令牌
  25. apiClient.defaults.headers.common['Authorization']=`Bearer ${newAccessToken}`;returntrue; // 表示刷新成功
  26. }else{returnfalse; // 表示刷新失败
  27. }});}

uni.request

  1. // 导入封装的request插件
  2. import http from './interface';import{ getRefreshToken } from '@/common/api/apis.js'; // 刷新token接口
  3. let isRefreshing =false; // 是否处于刷新token状态中
  4. let fetchApis =[]; // 失效后同时发送请求的容器
  5. let refreshCount =0; // 限制无感刷新的最大次数
  6. function onFetch(newToken){
  7. refreshCount +=1;if(refreshCount ===3){
  8. refreshCount =0;
  9. fetchApis =[];return Promise.reject();}
  10. fetchApis.forEach(callback =>{
  11. callback(newToken);});
  12. // 清空缓存接口
  13. fetchApis =[];return Promise.resolve();}
  14. // 响应拦截器
  15. http.interceptor.response((response)=> {
  16. if (response.config.loading) {
  17. uni.hideLoading();
  18. }
  19. // 请求成功但接口返回的错误处理
  20. if (response.data.statusCode &&+response.data.statusCode !==200) {
  21. if (!response.config.needPromise) {
  22. console.log('error', response);
  23. uni.showModal({
  24. title: '提示',
  25. content: response.data.message,
  26. showCancel: false,
  27. confirmText: '知道了'
  28. });// 中断
  29. return new Promise(()=> {});
  30. } else {
  31. // reject Promise
  32. return Promise.reject(response.data);
  33. }
  34. }
  35. return response;
  36. },(error)=> {
  37. const token = uni.getStorageSync('token');
  38. const refreshToken = uni.getStorageSync('refreshToken');// DESC: 不需要做无感刷新的白名单接口
  39. const whiteFetchApi = ['/dealersystem/jwtLogin', '/dealersystem/smsLogin', '/sso2/login', '/dealersystem/isLogin'];
  40. switch (error.statusCode) {
  41. case 401:
  42. case 402:
  43. if (token &&!whiteFetchApi.includes(error.config.url)){if(!isRefreshing){
  44. isRefreshing =true;
  45. getRefreshToken({ refreshToken }).then(res =>{let newToken = res.data;
  46. onTokenFetched(newToken).then(res =>{}).catch(err =>{
  47. // 超过循环次数时,回到登录页,这里可以添加你执行退出登录的逻辑
  48. uni.showToast({ title: '登录失效,请重新登录', icon: 'error'});
  49. setTimeout(()=>{
  50. uni.reLaunch({ url: '/pages/login/login'});}, 1500);});}).catch(err =>{
  51. // refreshToken接口报错,证明refreshToken也过期了,那没办法啦重新登录呗
  52. uni.showToast({ title: '登录失效,请重新登录', icon: 'error'});
  53. setTimeout(()=>{
  54. uni.reLaunch({ url: '/pages/login/login'});}, 1500);}).finally(()=>{ isRefreshing =false});}return new Promise((resolve)=>{ // 此处的promise很关键,就是确保你的接口返回值在此处resolve,以便后续代码执行
  55. addFetchApi((newToken)=>{
  56. error.config.header['Authorization']=`Bearer ${newToken}`;
  57. http.request(error.config).then(response =>{
  58. resolve(response);});});});}break;
  59. default:
  60. break;}});

注意事项:

  • 错误处理:确保在刷新令牌失败时,有适当的错误处理机制,例如提示用户重新登录。
  • 并发请求:处理多个请求同时需要刷新令牌的情况,避免重复刷新。
  • 安全性:确保刷新令牌的安全存储和传输,防止被恶意攻击者获取。
标签: 前端 javascript

本文转载自: https://blog.csdn.net/weixin_45788691/article/details/143817612
版权归原作者 我就不思 所有, 如有侵权,请联系我们删除。

“前端无感刷新token”的评论:

还没有评论