石墨烯 django 端点是不是同时需要 X-Csrftoken 和 CsrfCookie?

Posted

技术标签:

【中文标题】石墨烯 django 端点是不是同时需要 X-Csrftoken 和 CsrfCookie?【英文标题】:Does a graphene django Endpoint expects a X-Csrftoken and CsrfCookie at the same time?石墨烯 django 端点是否同时需要 X-Csrftoken 和 CsrfCookie? 【发布时间】:2020-06-17 09:29:05 【问题描述】:

使用:

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 保护的过程中,我认为 Django GraphQLView 需要的只是在请求标头中接收 X-Csrftoken 的“值”。所以我专注于以不同的方式发送csrf 值......通过这样的单一视图

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

或者通过ensure_csrf_cookie确保一个cookie

之后在我的ApolloClient 中,我获取了这些值并将其与请求 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 总是将X-CsrftokenCookie:..csrftoken. 也在请求中。如果在发送请求之前删除 GraphQLView IDE 的 csrftoken-cookie,我会得到这个

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 就会发挥作用。


问题

因此我的问题是:

我是否必须在我的ApolloClient 中同时设置X-CsrftokenCookie:..csrftoken 才能在我的django GraphQLView 上发出请求?

或者是否也可以只发送X-Csrftoken 而没有csrf-cookie,反之亦然?

【问题讨论】:

【参考方案1】:

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

设置

django 3.1 vue 2.6 vue-apollo 3.0.4(支持新的 Apollo-Client 3) @apollo/client 3.1.3

推定

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

问题回顾

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


解决方案

为了修复 CORS 问题,我注意到我的 Apollo 客户端 uri 与我的 Django 开发服务器不同。而不是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-CSRFTokenvue-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
      
;

【讨论】:

以上是关于石墨烯 django 端点是不是同时需要 X-Csrftoken 和 CsrfCookie?的主要内容,如果未能解决你的问题,请参考以下文章

Django 会话不是使用 JWT 和石墨烯生成的

聚合石墨烯/django 查询中的字段

如何解决具有多对多关系的石墨烯 django 节点字段

Django 模型对象和石墨烯 get_node

用石墨烯 django 数点赞

Django 石墨烯中继限制对用户拥有的对象的查询