You need to enable JavaScript to run this app. Vue3结合Django SimpleJWT实现认证,页面刷新后出现Permission denied
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.08 08:06:03