带有 Axios 的 Django CSRF 令牌

Posted

技术标签:

【中文标题】带有 Axios 的 Django CSRF 令牌【英文标题】:Django CSRF Token with Axios 【发布时间】:2020-11-09 01:11:42 【问题描述】:

情况:

我正在尝试使用 Vue.js 作为我的前端和 Django 作为我的后端来构建一个完整的 SPA。这些系统是完全独立的(不是由后端提供index.html 页面的混合应用程序)。

方法

我在我的 Vue-CLI 生成的项目中创建了一个 services 目录,该目录通过 api.js 文件(以下内容)为我的 REST API 提供一般可访问性:

import axios from "axios";
import Cookies from "js-cookie";

axios.defaults.xsrfHeaderName = "X-CSRFToken";
axios.defaults.xsrfCookieName = "csrftoken";

const BackEnd = "http://127.0.0.1:8000/"; //local backend from manage.py runserver

export default axios.create(
  baseURL: `$BackEndapi/`,
  timeout: 5000,
  headers: 
    "Content-Type": "application/json",
    "X-CSRFToken": Cookies.get('csrftoken')
  
);

我怎么知道有这样的令牌可以得到?我编写了一个 API 端点,它在响应标头中提供令牌(如下所示):

Access-Control-Allow-Origin: *
Content-Length: 77
Content-Type: application/json
Date: Sun, 19 Jul 2020 18:04:06 GMT
Server: WSGIServer/0.2 CPython/3.7.6
Set-Cookie: csrftoken=HdM4y6PPOB44cQ7DKmla7lw5hYHKVzTNG5ZZJ2PqAUWE2C79VBCJbpnTyfEdX3ke; expires=Sun, 18 Jul 2021 18:04:06 GMT; Max-Age=31449600; Path=/; SameSite=Lax
Vary: Cookie, Origin
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

问题

虽然我的 Django REST Framework API 正在为我的 GET 请求提供所有数据,但我似乎无法正确分配 csrftoken 来验证我的 POST 请求。即使在我的 axios 请求中适当设置了 X-CSRFToken 标头,我仍然会从服务器获得典型的 403(未设置 CSRF cookie)响应

请求标头

Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Content-Length: 247
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary9vOu1sBaQrXtXseR
DNT: 1
Host: 127.0.0.1:8000
Origin: http://127.0.0.1:8080
Referer: http://127.0.0.1:8080/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36
X-CSRFToken: T2Z7pzxKTAuCvBEIjkgRf8RGEEVLYfOyDYkYIcfkWCfSkPB76wCjMMizZvdTQPKg

更新

好的,现在这只是一种痛苦!我在 A) Set-Cookie 响应标头、B) 我的浏览器 cookie 中 csrftoken 的值和 C) 在 axios POST 请求中有不同的令牌值。谁能帮我弄清楚这里发生了什么?

【问题讨论】:

【参考方案1】:

我只是在我的 vue 应用中使用了这个,一切都很顺利。

axios.defaults.xsrfCookieName = 'csrftoken';
axios.defaults.xsrfHeaderName = 'X-CSRFToken';

axios(
                    method: 'post',
                    url: 'http://127.0.0.1:8000/api/orders-update',
                    xstfCookieName: 'csrftoken',
                    xsrfHeaderName: 'X-CSRFToken',
                    data: updateIDs,
                    headers: 
                        'X-CSRFToken': 'csrftoken',
                    
                ).then(response => console.log(response));

【讨论】:

【参考方案2】:

姜戈

您需要在 django 中使用 djoser 进行身份验证

赖特

pip install djangorestframework-simplejwt
pip install djoser

settings.py 更改

在您的 INSTALLED_APPS 中添加 djoser

INSTALLED_APPS=[
    ...,
    'djoser',
    ...
]

添加你的中间人

MIDDLEWERE=[
    ...,
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    ...
]

添加

# DRF settings
REST_FRAMEWORK = 
    # Default permissions
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
    ],
    # Token types
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework_simplejwt.authentication.JWTAuthentication",
        "rest_framework.authentication.SessionAuthentication"
    ],



DJOSER = 
    'PASSWORD_RESET_CONFIRM_URL': 
    'reset_password/uid/token',
    'ACTIVATION_URL': 'activation/uid/token',
    'SEND_ACTIVATION_EMAIL': True,
    'SEND_CONFIRMATION_EMAIL': True,
    'TOKEN_MODEL': None,
    'HIDE_USERS': True,
    'SERIALIZERS': 
    ,
    'PERMISSIONS': 
        'activation': ['rest_framework.permissions.AllowAny'],
        'password_reset': ['rest_framework.permissions.AllowAny'],
        'password_reset_confirm': ['rest_framework.permissions.AllowAny'],
        'set_password': ['djoser.permissions.CurrentUserOrAdmin'],
        'username_reset': ['rest_framework.permissions.AllowAny'],
        'username_reset_confirm': ['rest_framework.permissions.AllowAny'],
        'set_username': ['djoser.permissions.CurrentUserOrAdmin'],
        'user_create': ['rest_framework.permissions.AllowAny'],
        'user_delete': ['djoser.permissions.CurrentUserOrAdmin'],
        'user': ['djoser.permissions.CurrentUserOrAdmin'],
        'user_list': ['djoser.permissions.CurrentUserOrAdmin'],
        'token_create': ['rest_framework.permissions.AllowAny'],
        'token_destroy': ['rest_framework.permissions.IsAuthenticated'],
    



# JWT settings
SIMPLE_JWT = 
    'ACCESS_TOKEN_LIFETIME': timedelta(days=2),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=5),
    'ROTATE_REFRESH_TOKENS': False,
    'BLACKLIST_AFTER_ROTATION': True,
    'UPDATE_LAST_LOGIN': False,

    'ALGORITHM': 'HS256',
    'SIGNING_KEY': SECRET_KEY,
    'VERIFYING_KEY': None,
    'AUDIENCE': None,
    'ISSUER': None,

    'AUTH_HEADER_TYPES': ('JWT',),
    'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
    'USER_ID_FIELD': 'id',
    'USER_ID_CLAIM': 'user_id',
    'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',

    'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
    'TOKEN_TYPE_CLAIM': 'token_type',

    'JTI_CLAIM': 'jti',

    'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
    'SLIDING_TOKEN_LIFETIME': timedelta(days=2),
    'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=5),


在你的应用 urls.py 添加 djoser url

urlpatterns = [
    # DRF router
    path('', include(router.urls)),
    # djoser auth urls
    url(r'^auth/', include('djoser.urls')),
    # djoser auth jwt urls
    url(r'^auth/', include('djoser.urls.jwt')),
    # Login GUI DRF
    path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]

Vue

基础 API URL 项目/src/api/common.js

import axios from 'axios'

export const HTTP = axios.create(
    baseURL: 'http://api-url',
)

基础元素 project/src/api/element.js

import HTTP from './common'

function createHTTP(url) 
    return 
        async post(config) 
            return HTTP.post(`$url`, config).then(response => 
                console.log(response)
                return response.data
            )
        ,
        async get(element) 
            return HTTP.get(`$url$element.id/`)
        ,
        async patch(element) 
            console.log(element)
            return HTTP.patch(`$url$element.id/`, element).then(response => 
                console.log(response)
                return response.data
            )
        ,
        async delete(id) 
            HTTP.delete(`$url$id/`)
            return id
        ,
        async list(queryParams = '') 
            return HTTP.get(`$url$queryParams`).then(response => 
                return response.data.results
            )
        
    


export const Todos = createHTTP(`/todos/`)

你的商店 project/src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import todos from "@/store/modulse/todos";

Vue.use(Vuex)

export default new Vuex.Store(
    modules: 
        todos,
    
)

你的突变类型 project/src/store/mutation-types.js

export const SET_TODOS ='SET_TODOS'
export const PATCH_TODO ='PATCH_TODO'
export const DELETE_TODO ='DELETE_TODO'
export const CREATE_TODO ='CREATE_TODO'

你的模块 project/src/store/modulse/todos.js

import 
    Todos,
 from '@/api/elements'
import 
    SET_TODOS, PATCH_TODO, DELETE_TODO, CREATE_TODO
 from '../mutation-types'


// Getters
export default 
    state: 
        todos: []
    ,
    getters: 
        getTodos(state) 
            return state.todos
        ,
    ,
// Mutations
    mutations: 
        [SET_TODOS](state, todos) 
            state.todos = todos
        ,
        [PATCH_TODO](state, todos) 
            let id = todos.id
            state.todos.filter(todos => 
                return todos.id === id
            )[0] = todos
        ,
        [CREATE_TODO](state, todo) 
            state.todos = [todo, ...state.todos]
        ,
        [DELETE_TODO](state, id) 
            state.todos = state.todos.filter(todo =>
                return todo.id !==id
            )
        ,

    ,
// Actions
    actions: 
        async setTodos(commit, queryParams) 
            await Todos.list(queryParams)
                .then(todos => 
                    commit(SET_TODOS, todos)
                ).catch((error) => 
                    console.log(error)
                )
        ,
        async patchTodo(commit, todoData) 
            await Todos.patch(todoData)
                .then(todo => 
                    commit(PATCH_TODO, todo)
                ).catch((error) => 
                    console.log(error)
                )
        ,
        async deleteTodo(commit, todo_id) 
            await Todos.delete(todo_id)
                .then(resp => 
                    commit(DELETE_TODO, todo_id)
                ).catch((error) => 
                    console.log(error)
                )
       ,
       async createTodo(commit, todoData) 
            await Todos.create(todoData)
                .then(todo => 
                    commit(CREATE_TODO, todo)
                ).catch((error) => 
                    console.log(error)
                )
       ,


在你的项目/src/main.js中

import Vue from 'vue'
import store from './store'
import App from './App.vue'
import Axios from 'axios'

Vue.prototype.$http = Axios;

new Vue(
  store,
  render: h => h(App),
).$mount('#app')

在你的项目/src/App.vue

import mapActions, mapGetters from "vuex";

export default 
  name: 'App',
  components: ,
  data() 
    return 
  ,
  methods: 
    ...mapActions(['setTodos','patchTodo','createTodo','deleteTodo']),
  ,
  computed: 
    ...mapGetters(['getTodos']),
  ,
  async mounted() 
     await this.setTodos()
  ,


【讨论】:

以上是关于带有 Axios 的 Django CSRF 令牌的主要内容,如果未能解决你的问题,请参考以下文章

带有 jwt 身份验证的 django rest api 要求 csrf 令牌

带有 axios 和 vue 的 laravel 5.8 中的 CSRF

如何使用 Axios 将 CSRF Coo​​kie 从 React 发送到 Django Rest Framework

Laravel + Vue.js (axios) - CSRF 令牌不匹配

你如何在 django rest 框架中实现 CSRF 令牌?

如何将 POST 数据中的 CSRF 令牌传递给 Django?