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

bla*_*sun 4 django graphql graphene-python vue-apollo graphene-django

使用:

\n\n
    \n
  • Django 3.x [ Django 过滤器 2.2.0、graphene-django 2.8.0、graphql-relay 2.0.1 ]
  • \n
  • Vue 2.x [Vue-阿波罗]
  • \n
\n\n
\n\n

我正在使用 Django、GraphQL 和 Vue-Apollo 测试单页 vue app\xc2\xb4s。

\n\n

如果我csrf_exempt在我的视图中使用,一切都会在前端运行。

\n\n
urlpatterns = [\n<...>\n   path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True))),\n<...>\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
path(\'csrf/\', views.csrf),\npath("graphql", GraphQLView.as_view(graphiql=True)),\n
Run Code Online (Sandbox Code Playgroud)\n\n

或者通过确保 cookieensure_csrf_cookie

\n\n

然后在我的ApolloClienti 中获取这些 Value 并将其与请求 Header 一起发回。

\n\n

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

\n\n
Forbidden (CSRF token missing or incorrect.): /graphql\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
{\'Content-Length\': \'400\', \'Content-Type\': \'application/json\',\n\'Host\': \'localhost:7000\', \'Connection\': \'keep-alive\',\n\'Pragma\': \'no-cache\', \'Cache-Control\': \'no-cache\', \n\'Accept\': \'application/json\', \'Sec-Fetch-Dest\': \'empty\', \'X-Csrftoken\': \'dvMXuYfAXowxRGtwSVYQmpNcpGrLSR7RuUnc4IbIarjljxACtaozy3Jgp3YOkMGz\',\n\'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\',\n\'Origin\': \'http://localhost:7000\',\n\'Sec-Fetch-Site\': \'same-origin\', \'Sec-Fetch-Mode\': \'cors\',\n\'Referer\': \'http://localhost:7000/graphql\', \'Accept-Encoding\': \'gzip, deflate, br\', \'Accept-Language\': \'en-US,en;q=0.9,de;q=0.8\',\n\'Cookie\': \'sessionid=jqjvjfvg4sjmp7nkeunebqos8c7onhiz; csrftoken=dvMXuYfAXowxRGtwSVYQmpNcpGrLSR7RuUnc4IbIarjljxACtaozy3Jgp3YOkMGz\'}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
Forbidden (CSRF cookie not set.): /graphql\n
Run Code Online (Sandbox Code Playgroud)\n\n

IDE 显示一个长的红色报告

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

IDE 的信息表明\xc2\xb4s 请求需要 CSRF cookie。但到目前为止在论坛 Doc\xc2\xb4s 中阅读的所有内容都与价值本身更相关。这意味着您所需要的只是在标头中发送 csrf 值X-Csrftoken,然后视图就会发挥作用。

\n\n
\n\n

问题

\n\n

因此我的问题是:

\n\n

我是否必须在 my 中同时设置X-Csrftoken和 the才能对我的 django 发出请求?Cookie:..csrftokenApolloClientGraphQLView

\n\n

或者是否也可以简单地仅发送X-Csrftoken不带 a 的内容csrf-cookie,反之亦然?

\n

bla*_*sun 5

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

\n

设置

\n
    \n
  • Django 3.1
  • \n
  • 版本2.6
  • \n
  • vue-apollo 3.0.4(支持新的 Apollo-Client 3)
  • \n
  • @apollo/客户端 3.1.3
  • \n
\n

假定

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

问题回顾

\n

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

\n
\n

解决方案

\n

为了修复 CORS 问题,我注意到我uri的 Apollo 客户端与我的 Django 开发服务器不同。相反,http://127.0.0.1:7000/graphql它被设置为http://localhost:7000/graphql. 我还设置了credentials(参见 vue-apollo.js)

\n

为了修复 CSRF 我做了 3 件事

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

vue-apollo.js

\n
\n
\n
import Vue from 'vue'\n// import path for the new Apollo Client 3 and Vue-Apollo\nimport { ApolloClient, InMemoryCache } from '@apollo/client/core';\nimport VueApollo from 'vue-apollo'\nimport Cookies from 'js-cookie'\n\n  \n// Create the apollo client\nconst apolloClient = new ApolloClient({\n  // -------------------\n  // # Required Fields #\n  // -------------------\n  // URI - GraphQL Endpoint\n  uri: 'http://127.0.0.1:7000/graphql',\n  // Cache\n  cache: new InMemoryCache(),\n\n  // -------------------\n  // # Optional Fields #\n  // -------------------\n  // DevBrowserConsole\n  connectToDevTools: true,\n  // Else\n  credentials: 'same-origin',\n  headers: {\n    'X-CSRFToken': Cookies.get('csrftoken')\n  }\n});\n  \n// create Vue-Apollo Instance\nconst apolloProvider = new VueApollo({\n  defaultClient: apolloClient,\n})\n  \n// Install the vue plugin\nVue.use(VueApollo)\n  \nexport default apolloProvider\n
Run Code Online (Sandbox Code Playgroud)\n
\n

Vue.config.js

\n
\n
\n
const BundleTracker = require("webpack-bundle-tracker");\n\n// hook your apps\nconst pages = {\n    'page_1': {\n        entry: './src/page_1.js',\n        chunks: ['chunk-vendors']\n    },\n    'page_2': {\n        entry: './src/page_2.js',\n        chunks: ['chunk-vendors']\n    },\n}\n\nmodule.exports = {\n    pages: pages,\n    filenameHashing: false,\n    productionSourceMap: false,\n\n    // puplicPath: \n    // Tells Django where do find the bundle.\n    publicPath: '/static/',\n\n    // outputDir:\n    // The directory where the production build files will be generated - STATICFILES_DIRS\n    outputDir: '../dev_static/vue_bundle',\n \n    \n    chainWebpack: config => {\n\n        config.optimization\n        .splitChunks({\n            cacheGroups: {\n                vendor: {\n                    test: /[\\\\/]node_modules[\\\\/]/,\n                    name: "chunk-vendors",\n                    chunks: "all",\n                    priority: 1\n                },\n            },\n        });\n\n\n        // Don\xc2\xb4t create Templates because we using Django Templates\n        Object.keys(pages).forEach(page => {\n            config.plugins.delete(`html-${page}`);\n            config.plugins.delete(`preload-${page}`);\n            config.plugins.delete(`prefetch-${page}`);\n        })\n\n        // create webpack-stats.json. \n        // This file will describe the bundles produced by this build process.\n        // used eventually by django-webpack-loader\n        config\n            .plugin('BundleTracker')\n            .use(BundleTracker, [{filename: '/webpack-stats.json'}]);\n\n\n        // added to use ApolloQuery Tag (Apollo Components) see vue-apollo documentation\n        config.module\n        .rule('vue')\n        .use('vue-loader')\n            .loader('vue-loader')\n            .tap(options => {\n            options.transpileOptions = {\n                transforms: {\n                dangerousTaggedTemplateString: true,\n                },\n            }\n            return options\n            })\n        \n        // This will allows us to reference paths to static \n        // files within our Vue component as <img src="~__STATIC__/logo.png">\n        config.resolve.alias\n            .set('__STATIC__', 'static')\n\n        // configure a development server for use in non-production modes,\n        config.devServer\n            .public('http://localhost:8080')\n            .host('localhost')\n            .port(8080)\n            .hotOnly(true)\n            .watchOptions({poll: 1000})\n            .https(false)\n            .headers({"Access-Control-Allow-Origin": ["*"]})\n        \n        // DO have Webpack hash chunk filename\n        config.output\n            .chunkFilename("[id].js")\n            },\n\n    devServer: {\n        writeToDisk: true\n      }\n};\n
Run Code Online (Sandbox Code Playgroud)\n