2

使用:

  • Django 3.x [Django-Filters 2.2.0,graphene-django 2.8.0,graphql-relay 2.0.1]
  • Vue 2.x [Vue-Apollo]

我正在使用 Django、GraphQL 和 Vue-Apollo 测试单页 vue 应用程序。

如果我csrf_exempt在我的视图中使用一切都在前端工作。

urlpatterns = [
<...>
   path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True))),
<...>

现在我想 CSRF 保护我的请求。在了解 CSRF 保护的过程中,我认为 DjangoGraphQLView需要的只是接收X-Csrftoken请求标头中的“值”。所以我专注于csrf以不同的方式发送价值......通过这样的单一视图

path('csrf/', views.csrf),
path("graphql", GraphQLView.as_view(graphiql=True)),

或通过确保 cookie 与ensure_csrf_cookie

然后在我的ApolloClient我获取这些 Value 并用请求 Header 将他发回。

这是当我从 Django-Vue 页面发送 GraphQL 请求时 Django 打印的内容。

Forbidden (CSRF token missing or incorrect.): /graphql

并行我总是用这些请求进行测试,graphiql IDE这些请求仍然有效。我也每次打印info.context.headers我的查询解析器的值。

{'Content-Length': '400', 'Content-Type': 'application/json',
'Host': 'localhost:7000', 'Connection': 'keep-alive',
'Pragma': 'no-cache', 'Cache-Control': 'no-cache', 
'Accept': 'application/json', 'Sec-Fetch-Dest': 'empty', 'X-Csrftoken': 'dvMXuYfAXowxRGtwSVYQmpNcpGrLSR7RuUnc4IbIarjljxACtaozy3Jgp3YOkMGz',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36',
'Origin': 'http://localhost:7000',
'Sec-Fetch-Site': 'same-origin', 'Sec-Fetch-Mode': 'cors',
'Referer': 'http://localhost:7000/graphql', 'Accept-Encoding': 'gzip, deflate, br', 'Accept-Language': 'en-US,en;q=0.9,de;q=0.8',
'Cookie': 'sessionid=jqjvjfvg4sjmp7nkeunebqos8c7onhiz; csrftoken=dvMXuYfAXowxRGtwSVYQmpNcpGrLSR7RuUnc4IbIarjljxACtaozy3Jgp3YOkMGz'}

我认识到GraphQLView IDE总是将 the X-Csrftoken和 theCookie:..csrftoken.放在请求中。如果在发送请求之前删除 a 的 csrftoken-cookie GraphQLView IDE,我会得到这个

Forbidden (CSRF cookie not set.): /graphql

IDE 显示一个长长的红色报告

.... CSRF verification failed. Request aborted.</p>\n\n\n  
<p>You are seeing this message because this site requires a CSRF cookie when submitting forms.
This cookie is required for security reasons, to ensure that your browser is not being hijacked by third parties.</p>\n

IDE 的信息说请求需要一个 CSRF cookie。但是到目前为止,在 Doc 的论坛中阅读的所有内容都与价值本身有关。这意味着您所需要的只是在 Header 中发送 csrf 值,X-Csrftoken然后 View 就会发挥作用。


问题

因此我的问题是:

我是否必须同时在我的 django 上设置X-CsrftokenCookie:..csrftokenApolloClientGraphQLView

或者是否也可以只发送X-Csrftoken不带 a 的csrf-cookie,反之亦然?

4

1 回答 1

1

经过很长时间并暂停关注该问题后,我再次尝试并找到了解决方案。

设置

  • django 3.1
  • Vue 2.6
  • vue-apollo 3.0.4(支持新的 Apollo-Client 3)
  • @apollo/客户端 3.1.3

假定

  • 我将 Vue 用作多应用程序而不是单个应用程序。
  • *vue.js在 Django 中写入我的文件时,Webpack DevServer 将热重载STATICFILES_DIRS。Django 将从那里获取文件。工作正常

问题回顾

重新审视我的问题后,我注意到我有 2 个问题。一个是由于 CORS,浏览器拒绝了 graphQL 请求。第二个是 CSRF 代币。


解决方案

为了解决 CORS 问题,我注意到我uri的 Apollo Client 与我的 Django Dev Server 不同。而不是http://127.0.0.1:7000/graphql它被设置为http://localhost:7000/graphql. 我还设置了credentials(参见 vue-apollo.js)

为了修复 CSRF,我做了 3 件事

  • 确保发送一个{% csrf_token %}与您的 Vue/GraphQL 客户端应用程序挂钩的 HTML。以便我们以后可以获取它。
  • 安装js-cookie以获取 Cookie
  • 在 Apollo 客户端构造函数中使用X-CSRFTokenin设置标题vue-apollo.js

vue-apollo.js


import Vue from 'vue'
// import path for the new Apollo Client 3 and Vue-Apollo
import { ApolloClient, InMemoryCache } from '@apollo/client/core';
import VueApollo from 'vue-apollo'
import Cookies from 'js-cookie'

  
// Create the apollo client
const apolloClient = new ApolloClient({
  // -------------------
  // # Required Fields #
  // -------------------
  // URI - GraphQL Endpoint
  uri: 'http://127.0.0.1:7000/graphql',
  // Cache
  cache: new InMemoryCache(),

  // -------------------
  // # Optional Fields #
  // -------------------
  // DevBrowserConsole
  connectToDevTools: true,
  // Else
  credentials: 'same-origin',
  headers: {
    'X-CSRFToken': Cookies.get('csrftoken')
  }
});
  
// create Vue-Apollo Instance
const apolloProvider = new VueApollo({
  defaultClient: apolloClient,
})
  
// Install the vue plugin
Vue.use(VueApollo)
  
export default apolloProvider

Vue.config.js


const BundleTracker = require("webpack-bundle-tracker");

// hook your apps
const pages = {
    'page_1': {
        entry: './src/page_1.js',
        chunks: ['chunk-vendors']
    },
    'page_2': {
        entry: './src/page_2.js',
        chunks: ['chunk-vendors']
    },
}

module.exports = {
    pages: pages,
    filenameHashing: false,
    productionSourceMap: false,

    // puplicPath: 
    // Tells Django where do find the bundle.
    publicPath: '/static/',

    // outputDir:
    // The directory where the production build files will be generated - STATICFILES_DIRS
    outputDir: '../dev_static/vue_bundle',
 
    
    chainWebpack: config => {

        config.optimization
        .splitChunks({
            cacheGroups: {
                vendor: {
                    test: /[\\/]node_modules[\\/]/,
                    name: "chunk-vendors",
                    chunks: "all",
                    priority: 1
                },
            },
        });


        // Don´t create Templates because we using Django Templates
        Object.keys(pages).forEach(page => {
            config.plugins.delete(`html-${page}`);
            config.plugins.delete(`preload-${page}`);
            config.plugins.delete(`prefetch-${page}`);
        })

        // create webpack-stats.json. 
        // This file will describe the bundles produced by this build process.
        // used eventually by django-webpack-loader
        config
            .plugin('BundleTracker')
            .use(BundleTracker, [{filename: '/webpack-stats.json'}]);


        // added to use ApolloQuery Tag (Apollo Components) see vue-apollo documentation
        config.module
        .rule('vue')
        .use('vue-loader')
            .loader('vue-loader')
            .tap(options => {
            options.transpileOptions = {
                transforms: {
                dangerousTaggedTemplateString: true,
                },
            }
            return options
            })
        
        // This will allows us to reference paths to static 
        // files within our Vue component as <img src="~__STATIC__/logo.png">
        config.resolve.alias
            .set('__STATIC__', 'static')

        // configure a development server for use in non-production modes,
        config.devServer
            .public('http://localhost:8080')
            .host('localhost')
            .port(8080)
            .hotOnly(true)
            .watchOptions({poll: 1000})
            .https(false)
            .headers({"Access-Control-Allow-Origin": ["*"]})
        
        // DO have Webpack hash chunk filename
        config.output
            .chunkFilename("[id].js")
            },

    devServer: {
        writeToDisk: true
      }
};
于 2020-08-20T06:17:55.350 回答