阅读量:0
本文使用刷新接口去实现Token的无痕刷新,当拦截器检测到接口返回401响应码(即:认证失败),在拦截器中调用刷新接口去刷新Token,如果返回402(即:刷新token失效)则跳转到登录页,否则就更新Token信息。
定义相关接口
import request from '@/utils/request' export function login(data) { return request({ url: '/api/user/login', method: 'post', data }) } export function getInfo() { return request({ url: '/api/user/info', method: 'get' }) } export function tokenRefresh(data) { return request({ url: '/api/user/token/refresh', method: 'post', data }) } export function logout() { return request({ url: '/api/user/logout', method: 'post' }) }
Token信息
{ "code": 200, "msg": "成功", "data": { "accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjEsInJvbGVJZHMiOiIxIiwibmFtZSI6ImFkbWluIiwiZGVwdElkIjoxLCJ0eXBlIjowLCJqdGkiOiI5Y2Y3MzAwMC00YjNiLTRjYzAtYTI1YS01ZDM5YjFhYmFlMDgiLCJleHAiOjE3MjA2ODkyNTAsImlhdCI6MTcyMDY4OTE5MCwic3ViIjoiUGVyaXBoZXJhbHMiLCJpc3MiOiJPY2VhbiJ9.0-E4S5NuXNZCNslhyVpBjFZVCIqDahn6wdBt0gAxxmU", "accessTokenExpireIn": 1720689250, "refreshToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjEsInJvbGVJZHMiOiIxIiwibmFtZSI6ImFkbWluIiwiZGVwdElkIjoxLCJ0eXBlIjoxLCJqdGkiOiIwZDEzMWUzNC1lOGNiLTRhZjItOTMyZC1iN2JlNzQzZDgzZjgiLCJleHAiOjE3MjA2OTYzOTAsImlhdCI6MTcyMDY4OTE5MCwic3ViIjoiUGVyaXBoZXJhbHMiLCJpc3MiOiJPY2VhbiJ9.TBfL8cNciiS9GbX-2T1ejWWHYdyrUzkJELixAcm8EJ0", "refreshTokenExpireIn": 1720696390 } }
定义前端拦截器 request.js
import axios from 'axios' import { Message } from 'element-ui' import { getToken, getRefreshToken, setToken, removeToken } from '@/utils/auth' import { tokenRefresh } from '@/api/user' // 创建 axios 实例 const service = axios.create({ // 请求地址前缀 baseURL: process.env.VUE_APP_BASE_API, // 请求5s超时 timeout: 5000 // request timeout }) // Request 拦截器 service.interceptors.request.use( config => { // 从cookie中获取令牌信息,并放入接口头信息中 const token = getToken() if (token) { config.headers['Authorization'] = token } return config }, error => { // do something with request error console.log(error) // for debug return Promise.reject(error) } ) // 刷新令牌的标记 let isRefreshing = false // 重试请求队列 let requests = [] // Response 拦截器 service.interceptors.response.use( response => { const res = response.data if (res.code === 200) { return res } if(res.code === 402) { removeToken() location.reload() } // 401: 认证失败,402:令牌过期 if(res.code === 401) { const refreshToken = getRefreshToken() if(!isRefreshing) { isRefreshing = true // 刷新令牌,刷新成功后进行客户端令牌更新 return tokenRefresh( {refreshToken: refreshToken} ).then(result => { const data = result.data setToken(data.accessToken, data.refreshToken) const token = data.accessToken response.config.headers['Authorization'] = token // token 刷新后将数组的方法重新执行 requests.forEach((request) => request(token)) // 重新请求完清空 requests = [] return service(response.config) }).catch(() => { removeToken() location.reload() }).finally(() => { isRefreshing = false }) } else { // 返回未执行 resolve 的 Promise return new Promise(resolve => { // 用函数形式将 resolve 存入,等待刷新后再执行 requests.push(token => { response.config.headers['Authorization'] = token resolve(service(response.config)) }) }) } } Message({ message: res.msg || 'Error', type: 'error', duration: 5 * 1000 }) return Promise.reject(new Error(res.msg || 'Error')) }, error => { console.log('err' + error) // for debug Message({ message: error.msg, type: 'error', duration: 5 * 1000 }) return Promise.reject(error) } ) export default service
定义Token校验路由跳转 permission.js
import router from './router' import store from './store' import { Message } from 'element-ui' import NProgress from 'nprogress' // progress bar import 'nprogress/nprogress.css' // progress bar style import { getToken } from '@/utils/auth' // get token from cookie import getPageTitle from '@/utils/get-page-title' NProgress.configure({ showSpinner: false }) // NProgress Configuration const whiteList = ['/login'] // no redirect whitelist router.beforeEach(async(to, from, next) => { // start progress bar NProgress.start() // set page title document.title = getPageTitle(to.meta.title) // determine whether the user has logged in const hasToken = getToken() if (hasToken) { if (to.path === '/login') { // if is logged in, redirect to the home page next({ path: '/' }) NProgress.done() } else { const hasGetUserInfo = store.getters.name if (hasGetUserInfo) { next() } else { try { // get user info await store.dispatch('user/getInfo') next() } catch (error) { // remove token and go to login page to re-login await store.dispatch('user/resetToken') Message.error(error || 'Has Error') next(`/login?redirect=${to.path}`) NProgress.done() } } } } else { /* has no token*/ if (whiteList.indexOf(to.path) !== -1) { // in the free login whitelist, go directly next() } else { // other pages that do not have permission to access are redirected to the login page. next(`/login?redirect=${to.path}`) NProgress.done() } } }) router.afterEach(() => { // finish progress bar NProgress.done() })
Token前端缓存 auth.js
const TokenKey = 'accessToken' const RefreshTokenKey = 'refreshToken' export function getToken() { return localStorage.getItem(TokenKey) } export function getRefreshToken() { return localStorage.getItem(RefreshTokenKey) } export function setToken(accessToken, refreshToken) { localStorage.setItem(TokenKey, accessToken) localStorage.setItem(RefreshTokenKey, refreshToken) } export function removeToken() { localStorage.removeItem(TokenKey) localStorage.removeItem(RefreshTokenKey) }