Token无感刷新

前端Token无感刷新机制详解与实现

在现代Web应用中,使用Token进行身份验证是一种常见且高效的方法。然而,Token的有效期有限,当其过期时,用户需要重新登录才能继续访问受保护的资源,这会打断用户体验。为了解决这一问题,引入了Token无感刷新机制,即在Token即将过期前自动刷新,使用户在无感知的情况下继续操作。本文将详细介绍Token无感刷新的工作原理,并提供基于Vue.js和Axios的完整代码示例。

Token无感刷新原理

1. 双Token机制

通常采用两种类型的Token:

  • Access Token:用于直接访问受保护资源。
  • Refresh Token:用于获取新的Access Token。

2. Token生命周期

  • 用户登录时,服务器返回Access Token和Refresh Token。
  • Access Token具有较短的有效期,一般几分钟到几小时。
  • Refresh Token有效期较长,可以是一天或更长。

3. 自动刷新流程

  1. 客户端尝试访问资源:使用当前的Access Token。
  2. 检测Token过期:如果Access Token过期,客户端使用Refresh Token请求新的Access Token。
  3. 服务器验证并返回新Token:服务器验证Refresh Token有效后,返回新的Access Token。
  4. 客户端存储新Token:客户端存储新的Access Token,并用它继续访问资源。

Vue.js + Axios代码示例

环境准备

确保你的开发环境中安装了Node.js和npm/yarn,并创建了一个Vue.js项目。

步骤1: 安装依赖

在项目根目录下运行以下命令来安装Axios和Vuex:

npm install axios vuex

步骤2: 配置Axios实例

创建一个axios.js文件,在其中设置全局Axios实例,并添加拦截器用于Token刷新。

// src/axios.js

import axios from 'axios';
import store from '@/store'; // 引入Vuex store

const instance = axios.create({
  baseURL: 'http://your-api-url.com',
});

let isRefreshing = false; // 防止同时刷新Token
let failedQueue = []; // 存储失败的请求

// 请求拦截器
instance.interceptors.request.use(config => {
  const token = store.getters['auth/token'];
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// 响应拦截器
instance.interceptors.response.use(
  response => response,
  error => {
    const originalRequest = error.config;
    if (error.response.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;

      if (!isRefreshing) {
        isRefreshing = true;
        return store.dispatch('auth/refreshToken')
          .then(newAccessToken => {
            isRefreshing = false;
            axios.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}`;
            failedQueue.forEach(request => request.onSuccess());
            failedQueue = [];
          })
          .catch(err => {
            isRefreshing = false;
            failedQueue.forEach(request => request.onFailure());
            failedQueue = [];
            if (error.config && error.config.reject) {
              error.config.reject(err);
            }
          });
      }

      return new Promise((resolve, reject) => {
        failedQueue.push({
          onSuccess: () => {
            originalRequest.headers.Authorization = `Bearer ${store.getters['auth/token']}`;
            resolve(axios(originalRequest));
          },
          onFailure: () => {
            reject(error);
          },
        });
      });
    }

    return Promise.reject(error);
  }
);

export default instance;

步骤3: 配置Vuex Store

store目录下创建index.js,用来管理Token和刷新Token的动作。

// src/store/index.js

import { createStore } from 'vuex';

export default createStore({
  state: {
    accessToken: localStorage.getItem('accessToken') || '',
    refreshToken: localStorage.getItem('refreshToken') || '',
  },
  getters: {
    'auth/token': state => state.accessToken,
  },
  mutations: {
    'auth/setTokens'(state, { accessToken, refreshToken }) {
      state.accessToken = accessToken;
      state.refreshToken = refreshToken;
      localStorage.setItem('accessToken', accessToken);
      localStorage.setItem('refreshToken', refreshToken);
    },
  },
  actions: {
    'auth/login'({ commit }, credentials) {
      return axios.post('/api/auth/login', credentials)
        .then(response => {
          const { accessToken, refreshToken } = response.data;
          commit('auth/setTokens', { accessToken, refreshToken });
        });
    },
    'auth/refreshToken'({ state, commit }) {
      return axios.post('/api/auth/refresh', { refreshToken: state.refreshToken })
        .then(response => {
          const { accessToken } = response.data;
          commit('auth/setTokens', { accessToken, refreshToken: state.refreshToken });
          return accessToken;
        });
    },
  },
});

步骤4: 登录和刷新Token

在登录组件中,使用auth/login action进行登录,获取初始的Access Token和Refresh Token。

<!-- views/Login.vue -->

<template>
  <div>
    <form @submit.prevent="login">
      <!-- 表单字段 -->
      <button type="submit">登录</button>
    </form>
  </div>
</template>

<script>
import { mapActions } from 'vuex';

export default {
  methods: {
    ...mapActions(['auth/login']),
    login() {
      this.auth.login({
        username: 'your_username',
        password: 'your_password',
      });
    },
  },
};
</script>
posted @ 2024-06-22 00:42  shmillly959  阅读(1021)  评论(0)    收藏  举报