vue3 + django csrf 跨域问题
解决 vue3 + django csrf 跨域问题
可下载本文项目,gitee地址如下
https://gitee.com/longjing-with-sugar/vue3--django
git clone https://gitee.com/longjing-with-sugar/vue3--django.git
Django设置
1、安装 django-cors-headers库
pip3 install django-cors-headers -i https://pypi.mirrors.ustc.edu.cn/simple/
2、setting.py设置
DEBUG = True
ALLOWED_HOSTS = [] # 默认则允许所ip访问
# 1、配置白名单与安全设置
CORS_ALLOWED_ORIGINS = [
"http://localhost:5173", # Vue开发服务器地址
"http://127.0.0.1:5173",
]
# 2、允许携带Cookie
CORS_ALLOW_CREDENTIALS = True
# 3、CSRF 可信源配置 - Django 4.x + 必需
CSRF_TRUSTED_ORIGINS = [
"http://localhost:5173",
"http://127.0.0.1:5173",
]
# 自定义Cookie名称,默认值给是 'csrftoken' 可选定义
# CSRF_COOKIE_NAME = 'my_project_csrftoken'
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'login.apps.LoginConfig',
'corsheaders', # 4、添加跨域 app
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', # 5、跨域中间件,确保在CommonMiddleware之间,现在放在首行
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
...
注:本人后端文件夹路径如下,大家可能跟我的不一样,注意路径的配置即可

3. (同项目文件夹)backend/urls.py设置
此处创建了两个方法 获取 csrf 和 登陆前的注册
from django.contrib import admin
from django.urls import path, include
# from login import views as login_views
urlpatterns = [
path('admin/', admin.site.urls),
# 引入Login应用下的urls.py,匹配Login/开头的URL
path('login/', include('login.urls')),
# 不使用 include 则可以使用原 from login import views as login_views的方式
# 并注释掉 app/login/urls.py 中的内容
# path('login/csrf/', login_views.get_csrf_token),
# path('login/register/', login_views.register),
]
app/login/urls.py
from django.urls import path
from .views import *
urlpatterns = [
path('csrf/', get_csrf_token),
path('register/', get_register),
]
4. app/login/views.py
说明:@ensure_csrf_cookie 是 Django 提供的一个视图装饰器,它的核心作用是:强制让 Django 在响应中设置 CSRF 令牌的 Cookie(即使视图本身没有用到 CSRF 验证)。Django 为了防御这种攻击,默认开启了CSRF 中间件,要求 POST、PUT、DELETE 等非 GET 请求携带CSRF 令牌(csrf_token) 才能通过验证。
PS: 当然你也可以使用@csrf_exempt,用于跳过CSRF保护的,如果您在视图函数上添加了 @csrf_exempt 那么之前的settting.py的设置都不需要了
import json
from backend import settings
from .models import User
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import ensure_csrf_cookie
from django.middleware.csrf import get_token, _unmask_cipher_token
# @ensure_csrf_cookie 的作用是检查请求的 Cookie 中是否已有 csrftoken
# 如果没有,生成一个新的 CSRF 令牌,并通过响应头的 Set-Cookie写入客户端 Cookie
# 如果已有,直接复用这个已有的令牌,不会重新生成
@ensure_csrf_cookie
def get_csrf_token(request):
print("获取CSRF令牌")
csrfToken = get_token(request)
print("csrfToken:", csrfToken)
"""获取CSRF令牌的API"""
return JsonResponse({
'success': True,
'csrfToken': csrfToken
})
# 只返回 return JsonResponse({ 'success': True }) 也行, @ensure_csrf_cookie 已帮助在响应头中
# 返回了CSRF令牌,所以这里可以不需要再返回CSRF令牌,返回只是方便接收打印测试
# @ensure_csrf_cookie
# def get_csrf_token(request):
# # 1. 从Cookie获取原始秘钥(32位),在settings.py中设置了 CSRF_COOKIE_NAME = 'xxx'
# cookie_secret = request.COOKIES.get(settings.CSRF_COOKIE_NAME)
# # 2. 获取掩码后的令牌(64位)
# masked_token = get_token(request)
# # 3. 解掩码得到原始秘钥
# unmasked_secret = _unmask_cipher_token(masked_token)
#
# # 打印对比(结果是True)
# print(f"Cookie原始秘钥:{cookie_secret}")
# print(f"解掩码后的秘钥:{unmasked_secret}")
# print(f"是否一致:{cookie_secret == unmasked_secret}")
#
# return JsonResponse({
# 'success': True,
# 'my_project_csrftoken': masked_token
# })
def get_register(request):
if request.method == 'POST':
print("注册post请求")
print("-------", request.body)
data = json.loads(request.body) if request.body else {}
username = data.get('username')
password = data.get('password')
if username and password:
user = User.objects.create(username=username, password=password)
# 返回JSON格式数据,而非直接返回User对象
res = json.dumps({'id': user.id, 'username': user.username})
print("res:", res)
return HttpResponse(res, content_type="application/json", status=201)
else:
# 返回JSON格式的错误提示
res = json.dumps({"error": "Username and password are required"})
print("res:", res)
return HttpResponse(res, content_type="application/json", status=400)
else:
print("注册get请求")
# 返回JSON格式的错误提示
res = json.dumps({"error": "Invalid request method"})
return HttpResponse(res, content_type="application/json", status=405)
Vue3 设置
1、vite.config.js 代理设置
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
//定义代理
server: {
proxy: {
//(/api)表示获取路径中包含api的请求
'/api': {
//后台服务所在的源
target: 'http://127.0.0.1:8000',
changeOrigin: true,
// 把(/api)替换陈空字符串
rewrite: (path) => path.replace(/^\/api/, '')
},
}
}
})
2、axios 封装 utils/request.js修改
需要先安装: js-cookie,并在 request中 引入
npm install js-cookie --registry=https://registry.npmmirror.com
// axios基础的封装
import axios from 'axios'
import Cookies from 'js-cookie' // 需要先安装: npm install js-cookie
const request = axios.create({
baseURL: '/api', //开发环境
timeout: 50000,
withCredentials: true, // 允许携带cookie
})
// 获取CSRF令牌的函数
function getCSRFToken() {
// 先从cookie中获取,'csrftoken'是默认值,如果django后端 settings.py 设置了自定义Cookie名称,
// 如 CSRF_COOKIE_NAME = 'my_project_csrftoken',那么Cookies.get('xxx') 中的xxx也要跟着改
let token = Cookies.get('my_project_csrftoken')
// 如果没有,尝试从DOM获取
if (!token && document.querySelector('[name=csrfmiddlewaretoken]')) {
token = document.querySelector('[name=csrfmiddlewaretoken]').value
}
return token
}
// axios请求拦截器
request.interceptors.request.use(
config => {
// 对于非GET请求,添加CSRF令牌
if (config.method !== 'get' && config.method !== 'GET') {
const csrfToken = getCSRFToken()
if (csrfToken) {
config.headers['X-CSRFToken'] = csrfToken
}
}
// 设置内容类型
if (!config.headers['Content-Type']) {
config.headers['Content-Type'] = 'application/json'
}
return config
},
e => Promise.reject(e)
)
// axios响应式拦截器
request.interceptors.response.use(res => res.data,
e => {
// 处理CSRF错误
if (e.response && e.response.status === 403) {
const data = e.response.data
if (data.detail && data.detail.includes('CSRF')) {
// CSRF验证失败,重新获取令牌
console.error('CSRF验证失败,请刷新页面重试')
// 可以在这里跳转到登录页面或刷新页面
window.location.reload()
}
}
return Promise.reject(e)
}
)
export default request
3. main.js 加载时获取CSRF令牌
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import request from '@/utils/request';
const app = createApp(App)
// 应用启动时获取CSRF令牌
const initCSRFToken = async () => {
try {
await request.get('login/csrf/')
} catch (error) {
console.log('初始化CSRF令牌失败,但应用继续运行')
}
}
// 执行初始化
initCSRFToken()
app.use(createPinia())
app.use(router)
app.mount('#app')
4. 调用 举例 login.js
import request from '@/utils/request'
export const register = (queryParams) => {
return request({
url: '/login/register/',
method: 'post',
data: queryParams
})
}
5. App.vue 调用注册 API
<template>
...
</template>
<script setup>
import { register } from '@/api/login';
import { ref } from 'vue'
const username = ref('')
const password = ref('')
const login = async () => {
try {
const response = await register({
username: username.value,
password: password.value
})
console.log(JSON.stringify(response));
alert(JSON.stringify(response))
} catch (error) {
console.log(error.response || error);
}
}
</script>
效果图
对比浏览器 cookie 中的 csrftoken 值和 django 后台打印

可以看到名称一样,但值不一样:
为什么看起来 “不一样”?
get_token(request)会调用_mask_cipher_secret(csrf_secret),把原始秘钥(Cookie 里的csrftoken)和一个随机生成的 32 位掩码结合,生成 64 位的掩码令牌。- 验证时,Django 会通过
_unmask_cipher_token把掩码令牌还原为原始秘钥,再和 Cookie 里的csrftoken对比(这一步是自动的)。
验证两者的一致性
@ensure_csrf_cookie
def get_csrf_token(request):
# 1. 获取掩码后的令牌
masked_token = get_token(request)
# 2. 解掩码(验证一致性)
unmasked_secret = _unmask_cipher_token(masked_token)
# 3. 验证是否一致
print(f" 获取掩码后的令牌:{masked_token}")
print(f"解掩码后的秘钥:{unmasked_secret}")
return JsonResponse({
'success': True,
'csrfToken': masked_token,
})
可看到解掩码后的秘钥与浏览器的cookie csrftoken值一致:


情况2、
settings.py 设置
CSRF_COOKIE_NAME = 'my_project_csrftoken'
修改app/login/views.py
@ensure_csrf_cookie
def get_csrf_token(request):
# 1. 从Cookie获取原始秘钥(32位),在settings.py中设置了 CSRF_COOKIE_NAME = 'xxx'
cookie_secret = request.COOKIES.get(settings.CSRF_COOKIE_NAME)
# 2. 获取掩码后的令牌(64位)
masked_token = get_token(request)
# 3. 解掩码得到原始秘钥
unmasked_secret = _unmask_cipher_token(masked_token)
# 打印对比(结果是True)
print(f"Cookie原始秘钥:{cookie_secret}")
print(f"解掩码后的秘钥:{unmasked_secret}")
print(f"是否一致:{cookie_secret == unmasked_secret}")
return JsonResponse({
'success': True,
'my_project_csrftoken': masked_token
})
以及 vue中request.js Cookies.get('csrftoken') => Cookies.get('my_project_csrftoken')
function getCSRFToken() {
...
let token = Cookies.get('my_project_csrftoken')
...
}
效果图

对比浏览器 cookie 中的 csrftoken 值和 django 后台打印

关于设置自定义的 settings.py 属性 CSRF_COOKIE_NAME = 'xxx' 的作用?
举个例子:当你启动了多个后端,不同的后端的 CSRF_COOKIE_NAME 属性是相同设置,单个网页就可以方便的访问多个后端了。
更多推荐
所有评论(0)