Vue3结合Django SimpleJWT实现认证,页面刷新后出现Permission denied
问题根因与完整修复方案
核心问题定位
- 你当前遇到的刷新页面即触发权限拒绝的问题,和refresh token逻辑缺失没有直接关系:目前仅在登录成功的运行时回调里给Axios全局设置了
Authorization请求头,这类内存配置会在页面刷新时直接重置,哪怕localStorage里存了有效token,你也没有在应用初始化阶段把存储的token同步到Axios配置中,后端收不到鉴权信息自然返回权限拒绝。 - 现有代码还存在一个Vuex的常见写法错误:Vuex的mutation仅支持接收两个入参(state、单个载荷对象),你目前
commit("auth_success", access_token, refresh_token, user)这种传多个独立参数的写法,后两个refresh_token、user参数会被直接丢弃,导致Vuex内部状态更新异常。
分步修复实现
1. 解决刷新丢失请求头的问题
在应用入口文件(如main.js)挂载根实例前,增加初始化逻辑,把localStorage中存储的token同步到Axios全局配置:
// main.js 入口核心逻辑 import { createApp } from 'vue' import App from './App.vue' import store from './store' import axios from 'axios' // 应用初始化时同步本地存储的token到Axios全局配置 const savedAccessToken = localStorage.getItem('access_token') if (savedAccessToken) { axios.defaults.headers.common['Authorization'] = `Bearer ${savedAccessToken}` } createApp(App).use(store).mount('#app')
同时修正Vuex配置里的参数传值问题,补全state的初始字段定义保证响应式正常:
const authStore = { state: { status: "", access_token: localStorage.getItem("access_token") || "", refresh_token: localStorage.getItem("refresh_token") || "", user: null, // 提前定义user字段,避免动态新增属性丢失响应式 }, mutations: { auth_request(state) { state.status = "loading"; }, // 修正mutation为接收单个载荷对象的写法 auth_success(state, payload) { state.status = "success"; state.access_token = payload.access_token; state.refresh_token = payload.refresh_token; state.user = payload.user; }, auth_error(state) { state.status = "error"; }, logout(state) { state.status = ""; state.access_token = ""; state.refresh_token = ""; state.user = null; }, }, actions: { login({ commit }, user) { return new Promise((resolve, reject) => { commit("auth_request"); axios({ url: API_ENDPOINTS.CREATE_TOKEN, data: user, method: "POST", }) .then((resp) => { const access_token = resp.data.access; const refresh_token = resp.data.refresh; const userInfo = resp.data.user; localStorage.setItem("access_token", access_token); localStorage.setItem("refresh_token", refresh_token); axios.defaults.headers.common[ "Authorization" ] = `Bearer ${access_token}`; // 把多个参数打包成单个对象传给mutation commit("auth_success", { access_token, refresh_token, user: userInfo }); resolve(resp); }) .catch((err) => { commit("auth_error"); localStorage.removeItem("access_token"); localStorage.removeItem("refresh_token"); reject(err); }); }); }, logout({ commit }) { return new Promise((resolve) => { commit("logout"); localStorage.removeItem("access_token"); localStorage.removeItem("refresh_token"); delete axios.defaults.headers.common["Authorization"]; resolve(); }); }, }, getters: { isLoggedIn: (state) => !!state.access_token, authStatus: (state) => state.status, }, }; export default authStore;
2. 集成完整的Refresh Token自动续期能力
等初始化请求头的问题修复后,后续会遇到access token短有效期过期的场景,这时就需要通过refresh token实现无感知自动续期,避免用户重新登录。通过Axios响应拦截器统一处理401错误,同时做并发控制,避免多个接口同时报401时重复发起刷新请求:
// 新建utils/axios.js 全局拦截器配置文件 import axios from 'axios' import { API_ENDPOINTS } from '../constants/API' import store from '../store' // 刷新状态锁,避免重复发起token刷新请求 let isRefreshing = false // 缓存等待刷新完成的请求队列 let pendingRequests = [] axios.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config // 仅处理401未授权错误,同时标记请求需要重试,避免死循环 if (error.response?.status === 401 && !originalRequest._retry) { // 如果当前正在刷新token,就把请求挂起等待刷新结果 if (isRefreshing) { return new Promise((resolve) => { pendingRequests.push((newToken) => { originalRequest.headers['Authorization'] = `Bearer ${newToken}` resolve(axios(originalRequest)) }) }) } // 开启刷新锁 originalRequest._retry = true isRefreshing = true try { const refreshToken = localStorage.getItem('refresh_token') // 调用Django SimpleJWT内置的token刷新接口 const refreshResp = await axios.post(API_ENDPOINTS.REFRESH_TOKEN, { refresh: refreshToken }) const newAccessToken = refreshResp.data.access // 同步更新本地存储、全局请求头、Vuex状态 localStorage.setItem('access_token', newAccessToken) axios.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}` store.commit('auth_success', { access_token: newAccessToken, refresh_token: refreshToken, user: store.state.user }) // 把队列中挂起的所有请求重新发送 pendingRequests.forEach((callback) => callback(newAccessToken)) pendingRequests = [] // 重发当前触发401的原请求 originalRequest.headers['Authorization'] = `Bearer ${newAccessToken}` return axios(originalRequest) } catch (refreshError) { // 如果refresh token也失效,直接执行登出逻辑清空本地状态 store.dispatch('logout') return Promise.reject(refreshError) } finally { // 不管刷新成功失败,都释放刷新锁 isRefreshing = false } } return Promise.reject(error) } )
注意:需要在
API_ENDPOINTS常量中配置对应路径,CREATE_TOKEN对应SimpleJWT的获取token接口/api/token/,REFRESH_TOKEN对应刷新token接口/api/token/refresh/。
内容的提问来源于stack exchange,提问作者Ytech Python
相关产品推荐
相关产品推荐

