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

CSRF with Django, React+Redux using Axios

这是一个教育项目,不用于生产。我不打算将用户登录作为其中的一部分。

我可以在没有用户登录的情况下使用 CSRF 令牌对 Django 进行 POST 调用吗?我可以在不使用 jQuery 的情况下执行此操作吗?我在这里超出了我的深度,肯定会混淆一些概念。

对于 JavaScript 方面,我找到了这个 redux-csrf 包。我不确定如何使用 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"));
      })
  }
};

在 Django 方面,我阅读了 this documentation on CSRF, and this 关于一般使用基于 class 的视图的内容。

这是我目前的看法:

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]))

我尝试使用 csrf_exempt 装饰器只是为了暂时不必担心这个,但它似乎不是这样工作的。

我已将 {% csrf_token %} 添加到我的模板中。

这是我的 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 我需要更改 Axios CSRF 信息:

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

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

我在哪里粘贴实际令牌,我通过调用 getCookie('csrftoken') 获得的值?

您可以将 Django-provided CSRF 令牌手动添加到所有 post 请求中,但这很烦人。

来自Django docs

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. For this reason, there is an alternative method: on each XMLHttpRequest, set a custom X-CSRFToken header to the value of the CSRF token. This is often easier, because many JavaScript frameworks provide hooks that allow headers to be set on every request.

文档中有代码可用于从 CSRF 令牌 cookie 中提取 CSRF 令牌,然后将其添加到 AJAX 请求的 header 中。

这个问答是 2016 年的,不出所料,我相信情况已经发生了变化。答案继续收到赞成票,所以我将添加其他答案的新信息,但也保留原始答案。

在评论中让我知道哪种解决方案适合您。

选项 1. 设置默认 headers

在导入 Axios 的文件中,设置默认值 headers:

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

方案2.手动添加到axios调用

假设您已将令牌的值存储在名为 csrfToken 的变量中。在您的 axios 调用中设置 headers:

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

选项3.在调用中设置xsrfHeaderName

添加这个:

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

然后在您的 settings.py 文件中,添加这一行:

CSRF_COOKIE_NAME = "XSRF-TOKEN"

编辑2017 年 6 月 10 日):用户@yestema 说它与 Safari[2]

编辑2019 年 4 月 17 日):用户@GregHolst 说上面的 Safari 解决方案对他不起作用。相反,他在 MacOS Mojave 上对 Safari 12.1 使用了上述解决方案 #3。 (来自评论

编辑2019 年 2 月 17 日):您可能还需要设置[3]:

axios.defaults.withCredentials = true

我尝试过但没有用的东西:1

实际上有一种非常简单的方法可以做到这一点。

axios.defaults.xsrfHeaderName = "X-CSRFToken"; 添加到您的应用程序配置中,然后在您的 settings.py 文件中设置 CSRF_COOKIE_NAME = "XSRF-TOKEN"。就像一个魅力。

"easy way" 几乎对我有用。这似乎有效:

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

并且在 settings.py 文件中:

CSRF_COOKIE_NAME = "XCSRF-TOKEN"

我发现 axios.defaults.xsrfCookieName = "XCSRF-TOKEN";CSRF_COOKIE_NAME = "XCSRF-TOKEN"

在 Mac OS

上的 APPLE Safari 中不工作

MAC Safari 的解决方案很简单,只需将 XCSRF-TOKEN 更改为 csrftoken

所以,在 js 代码中应该是:

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

在settings.py中:

    CSRF_COOKIE_NAME = "csrftoken"

这个配置对我来说没有问题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

对我来说,django 没有听我发送的 headers。我可以卷入 api 但无法使用 axios 访问它。查看 cors-headers package...它可能是您最好的新朋友。

我通过安装 django-cors-headers

修复了它
pip install django-cors-headers

然后添加

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

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

进入我的settings.py

我也有

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

在我的 settings.py 中,虽然这可能有点矫枉过正

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

axios.defaults.withCredentials = true

在花了太多时间研究和实施上述答案后,我发现了我对这个问题的错误!我添加了此答案以补充已接受的答案。我已经按照提到的设置了所有内容,但对我来说 gotcha 实际上在浏览器本身!

如果在本地测试,请确保您通过 127.0.0.1 而不是 localhost 访问 React! localhost 以不同方式处理请求 header,并且不会在 header 响应中显示 CSRF 令牌,而 127.0.0.1 会!所以不要 localhost:3000 试试 127.0.0.1:3000!

希望这对您有所帮助。