简体   繁体   English

使用 Django 的 CSRF,使用 Axios 的 React+Redux

[英]CSRF with Django, React+Redux using Axios

This is an educational project, not for production.这是一个教育项目,不是为了生产。 I wasn't intending to have user logins as part of this.我不打算让用户登录作为其中的一部分。

Can I make POST calls to Django with a CSRF token without having user logins?我可以在没有用户登录的情况下使用 CSRF 令牌对 Django 进行 POST 调用吗? Can I do this without using jQuery?我可以在不使用 jQuery 的情况下做到这一点吗? I'm out of my depth here, and surely conflating some concepts.我在这里超出了我的深度,并且肯定会混淆一些概念。

For the JavaScript side, I found this redux-csrf package.对于 JavaScript 方面,我找到了这个redux-csrf包。 I'm not sure how to combine it with my POST action using Axios:我不确定如何使用 Axios 将它与我的POST操作结合起来:

export const addJob = (title, hourly, tax) => {
  console.log("Trying to addJob: ", title, hourly, tax)
  return (dispatch) => {
    dispatch(requestData("addJob"));
    return axios({
      method: 'post',
      url: "/api/jobs",
      data: {
        "title": title,
        "hourly_rate": hourly,
        "tax_rate": tax
      },
      responseType: 'json'
    })
      .then((response) => {
        dispatch(receiveData(response.data, "addJob"));
      })
      .catch((response) => {
        dispatch(receiveError(response.data, "addJob"));
      })
  }
};

On the Django side, I've read this documentation on CSRF, and this on generally working with class based views.在 Django 方面,我已经阅读了有关 CSRF 的文档,并且通常使用基于类的视图。

Here is my view so far:到目前为止,这是我的观点:

class JobsHandler(View):

    def get(self, request):
        with open('./data/jobs.json', 'r') as f:
            jobs = json.loads(f.read())

        return HttpResponse(json.dumps(jobs))

    def post(self, request):
        with open('./data/jobs.json', 'r') as f:
            jobs = json.loads(f.read())

        new_job = request.to_dict()
        id = new_job['title']
        jobs[id] = new_job

        with open('./data/jobs.json', 'w') as f:
            f.write(json.dumps(jobs, indent=4, separators=(',', ': ')))

        return HttpResponse(json.dumps(jobs[id]))

I tried using the csrf_exempt decorator just to not have to worry about this for now, but that doesn't seem to be how that works.我尝试使用csrf_exempt装饰器只是为了暂时不必担心这一点,但这似乎不是它的工作原理。

I've added {% csrf_token %} to my template.我已将{% csrf_token %}添加到我的模板中。

This is my getCookie method (stolen from Django docs):这是我的getCookie方法(从 Django 文档中窃取):

function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = cookies[i].trim();
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}

I've read that I need to change the Axios CSRF info: 我读到我需要更改 Axios CSRF 信息:

var axios = require("axios");
var axiosDefaults = require("axios/lib/defaults");

axiosDefaults.xsrfCookieName = "csrftoken"
axiosDefaults.xsrfHeaderName = "X-CSRFToken"

Where do I stick the actual token, the value I get from calling getCookie('csrftoken') ?我在哪里粘贴实际令牌,我通过调用getCookie('csrftoken')获得的值?

There are three ways. 有三种方法。 You can manually include the token in the header of each axios call, you can set axios's xsrfHeaderName in each call, or you set a default xsrfHeaderName . 您可以在每个axios调用的标头中手动包含令牌,您可以在每次调用中设置axios的xsrfHeaderName ,或者设置默认的xsrfHeaderName

1. Adding it manually 1.手动添加

Let's say you've got the value of the token stored in a variable called csrfToken . 假设您已将令牌的值存储在名为csrfToken的变量中。 Set the headers in your axios call: 在axios调用中设置标题:

// ...
method: 'post',
url: '/api/data',
data: {...},
headers: {"X-CSRFToken": csrfToken},
// ...

2. Setting xsrfHeaderName in the call: 2.在调用中设置xsrfHeaderName

Add this: 添加这个:

// ...
method: 'post',
url: '/api/data',
data: {...},
xsrfHeaderName: "X-CSRFToken",
// ...

Then in your settings.py file, add this line: 然后在settings.py文件中添加以下行:

CSRF_COOKIE_NAME = "XSRF-TOKEN"

3. Setting default headers [1] 3.设置默认标题[1]

Rather than defining the header in each call, you can set default headers for axios. 您可以为axios设置默认标头,而不是在每次调用中定义标头。

In the file where you're importing axios to make the call, add this below your imports: 在您要导入axios以进行调用的文件中,在导入下方添加以下内容:

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

Then in your settings.py file, add this line: 然后在settings.py文件中添加以下行:

CSRF_COOKIE_NAME = "XSRF-TOKEN"

Edit ( June 10, 2017 ): User @yestema says that it works slightly different with Safari [2] 编辑2017年6月10日 ):用户@yestema说它与Safari [2]略有不同

Edit ( April 17, 2019 ): User @GregHolst says that the Safari solution above does not work for him. 编辑2019年4月17日 ):用户@GregHolst说上面的Safari解决方案对他不起作用。 Instead, he used the above Solution #3 for Safari 12.1 on MacOS Mojave. 相反,他在MacOS Mojave上使用上述解决方案#3 for Safari 12.1。 ( from comments ) 来自评论

Edit ( February 17, 2019 ): You might also need to set [3] : 编辑2019年2月17日 ):您可能还需要设置[3]

axios.defaults.withCredentials = true

Question: Is this next section useful to anyone? 问题:下一部分对任何人都有用吗? I'm wondering if this answer might be improved by only including the solutions. 我想知道只有包含解决方案才能改善这个答案。 Let me know if you have an opinion please. 如果您有意见,请告诉我。

The confusion: 困惑:

Django Docs Django Docs

First, the whole passage from the Django docs that James Evans referenced : 首先,詹姆斯·埃文斯引用Django文章的全文如下:

...on each XMLHttpRequest, set a custom X-CSRFToken header to the value of the CSRF token. ...在每个XMLHttpRequest上,将自定义X-CSRFToken标头设置为CSRF标记的值。 This is often easier, because many JavaScript frameworks provide hooks that allow headers to be set on every request. 这通常更容易,因为许多JavaScript框架提供了允许在每个请求上设置标头的钩子。

As a first step, you must get the CSRF token itself. 作为第一步,您必须自己获取CSRF令牌。 The recommended source for the token is the csrftoken cookie, which will be set if you've enabled CSRF protection for your views as outlined above. 建议的令牌来源是csrftoken cookie,如果您已按照上面的说明为视图启用了CSRF保护,则会设置该cookie。

Note 注意

The CSRF token cookie is named csrftoken by default, but you can control the cookie name via the CSRF_COOKIE_NAME setting. 默认情况下,CSRF令牌cookie名为csrftoken,但您可以通过CSRF_COOKIE_NAME设置控制Cookie名称。

The CSRF header name is HTTP_X_CSRFTOKEN by default, but you can customize it using the CSRF_HEADER_NAME setting. 默认情况下,CSRF标头名称为HTTP_X_CSRFTOKEN,但您可以使用CSRF_HEADER_NAME设置对其进行自定义。


Axios Docs Axios Docs

This is from the Axios docs . 这来自Axios文档 It indicates that you set the name of the cookie which contains the csrftoken , and the name of the header here: 它表示您设置包含csrftoken的cookie的名称,以及此处的标头名称:

  // `xsrfCookieName` is the name of the cookie to use as a value for xsrf token
  xsrfCookieName: 'XSRF-TOKEN', // default

  // `xsrfHeaderName` is the name of the http header that carries the xsrf token value
  xsrfHeaderName: 'X-XSRF-TOKEN', // default

Terms 条款

As indicated in my question, you access cookies with document.cookie . 如我的问题所示,您可以使用document.cookie访问cookie。 The only cookie I have is the CSRF token I put in the Django template. 我唯一的cookie是我放在Django模板中的CSRF令牌。 Here is an example: 这是一个例子:

csrftoken=5knNceCUi9nL669hGGsvCi93XfqNhwTwM9Pev7bLYBOMXGbHVrjitlkKi44CtpFU

There are a few concepts being thrown around in those docs that get confusing: 在那些令人困惑的文档中有一些概念被抛出:

  • The name of the cookie that contains the CSRF token. 包含CSRF令牌的cookie的名称。 In Django this is by default csrftoken , which is on the left side of the equals sign in the cookie. 在Django中,这是默认的csrftoken ,它位于csrftoken号的左侧。
  • The actual token. 实际的令牌。 This is everything on the right side of the equals sign in the cookie. 这是cookie中等号右侧的所有内容。
  • The http header that carries the token value. 带有标记值的http标头。

Things I tried that didn't work: 1 , 2 事情我想,没有工作: 12

I've found out, that axios.defaults.xsrfCookieName = "XCSRF-TOKEN"; 我发现, axios.defaults.xsrfCookieName = "XCSRF-TOKEN"; and CSRF_COOKIE_NAME = "XCSRF-TOKEN" CSRF_COOKIE_NAME = "XCSRF-TOKEN"

DOESN'T WORK IN APPLE Safari on Mac OS 在Mac OS上不适用于APPLE Safari

The solution for MAC Safari is easy, just change XCSRF-TOKEN to csrftoken MAC Safari的解决方案很简单, 只需将XCSRF-TOKEN更改为csrftoken

So, in js-code should be: 所以,在js-code中应该是:

    import axios from 'axios';
    axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
    axios.defaults.xsrfCookieName = "csrftoken";

In settings.py: 在settings.py中:

    CSRF_COOKIE_NAME = "csrftoken"

This configuration works for me without problems Config axios CSRF django 这个配置对我来说没有问题Config axios CSRF django

 import axios from 'axios' /** * Config global for axios/django */ axios.defaults.xsrfHeaderName = "X-CSRFToken" axios.defaults.xsrfCookieName = 'csrftoken' export default axios 

After spending too many hours researching, and implementing the above answer, I found my error for this problem!在花了太多时间研究并实施上述答案之后,我发现我的错误是这个问题! I have added this answer to be supplemental of the accepted answer.我已添加此答案以补充已接受的答案。 I had set up everything as mentioned, but the gotcha for me was actually in the browser itself!我已经按照上述设置了所有内容,但对我来说,问题实际上在于浏览器本身!

If testing locally, make sure you are accessing react through 127.0.0.1 instead of localhost !如果在本地进行测试,请确保您通过127.0.0.1而不是localhost访问 react! localhost handles request headers differently and doesn't show the CSRF tokens in the header response, where as 127.0.0.1 will! localhost以不同的方式处理请求标头,并且不会在标头响应中显示 CSRF 令牌,而127.0.0.1会! So instead of localhost:3000 try 127.0.0.1:3000 !所以代替localhost:3000试试127.0.0.1:3000

Hope this helps.希望这可以帮助。

The "easy way" almost worked for me. “简单的方式”几乎对我有用。 This seems to work: 这似乎有效:

import axios from 'axios';
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
axios.defaults.xsrfCookieName = "XCSRF-TOKEN";

And in the settings.py file: 并在settings.py文件中:

CSRF_COOKIE_NAME = "XCSRF-TOKEN"

You could add the Django-provided CSRF token manually into all of your post requests, but that's annoying. 您可以手动将Django提供的CSRF令牌添加到您的所有帖子请求中,但这很烦人。

From the Django docs : 来自Django文档

While the above method ( manually setting CSRF token ) can be used for AJAX POST requests, it has some inconveniences: you have to remember to pass the CSRF token in as POST data with every POST request. 虽然上述方法( 手动设置CSRF令牌 )可用于AJAX POST请求,但它有一些不便之处:您必须记住在每次POST请求时都将CSRF令牌作为POST数据传递。 For this reason, there is an alternative method: on each XMLHttpRequest, set a custom X-CSRFToken header to the value of the CSRF token. 因此,有一种替代方法:在每个XMLHttpRequest上,将自定义X-CSRFToken标头设置为CSRF标记的值。 This is often easier, because many JavaScript frameworks provide hooks that allow headers to be set on every request. 这通常更容易,因为许多JavaScript框架提供了允许在每个请求上设置标头的钩子。

The docs have code you can use to pull the CSRF token from the CSRF token cookie and then add it to the header of your AJAX request. 文档具有可用于从CSRF令牌cookie中提取CSRF令牌的代码,然后将其添加到AJAX请求的标头中。

There is actually a really easy way to do this. 实际上有一种非常简单的方法可以做到这一点。

Add axios.defaults.xsrfHeaderName = "X-CSRFToken"; 添加axios.defaults.xsrfHeaderName = "X-CSRFToken"; to your app config and then set CSRF_COOKIE_NAME = "XSRF-TOKEN" in your settings.py file. 到您的应用配置,然后在settings.py文件中设置CSRF_COOKIE_NAME = "XSRF-TOKEN" Works like a charm. 奇迹般有效。

For me, django wasn't listening to the headers that I was sending. 对我来说,django没有听我发送的标题。 I could curl into the api but couldn't access it with axios. 我可以卷入api但无法使用axios访问它。 Check out the cors-headers package ... it might be your new best friend. 查看cors-headers包 ...它可能是你最好的朋友。

I fixed it by installing django-cors-headers 我通过安装django-cors-headers来修复它

pip install django-cors-headers

And then adding 然后加入

INSTALLED_APPS = (
    ...
    'corsheaders',
    ...
)

and

MIDDLEWARE = [  # Or MIDDLEWARE_CLASSES on Django < 1.10
    ...
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    ...
]

into my settings.py 进入我的settings.py

I also had 我也有

ALLOWED_HOSTS = ['*']
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
CORS_EXPOSE_HEADERS = (
    'Access-Control-Allow-Origin: *',
)

in my settings.py although that is probably overkill 在我的settings.py虽然这可能是矫枉过正

除了yestema所说的(并且由krescruz,cran_man,Dave Merwin等人回应),您还需要:

axios.defaults.withCredentials = true

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM