可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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? Can I do this without using jQuery? I'm out of my depth here, and surely conflating some concepts.
For the JavaScript side, I found this redux-csrf package. I'm not sure how to combine it with my POST
action using Axios:
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.
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.
I've added {% csrf_token %}
to my template.
This is my getCookie
method (stolen from Django docs):
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:
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')
?
回答1:
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
.
1. Adding it manually
Let's say you've got the value of the token stored in a variable called csrfToken
. Set the headers in your axios call:
// ...
method: 'post',
url: '/api/data',
data: {...},
headers: {"X-CSRFToken": csrfToken},
// ...
2. Setting xsrfHeaderName
in the call:
Add this:
// ...
method: 'post',
url: '/api/data',
data: {...},
xsrfHeaderName: "X-CSRFToken",
// ...
Then in your settings.py
file, add this line:
CSRF_COOKIE_NAME = "XSRF-TOKEN"
3. Setting default headers[1]
Rather than defining the header in each call, you can set default headers for axios.
In the file where you're importing axios to make the call, add this below your imports:
axios.defaults.xsrfHeaderName = "X-CSRFToken";
Then in your settings.py
file, add this line:
CSRF_COOKIE_NAME = "XSRF-TOKEN"
EDIT: Apparently it works slightly different with Safari[2]
[1] From Dave Merwin's comment
The confusion:
Django Docs
First, the whole passage from the Django docs that James Evans referenced:
...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.
As a first step, you must get the CSRF token itself. 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.
Note
The CSRF token cookie is named csrftoken by default, but you can
control the cookie name via the CSRF_COOKIE_NAME setting.
The CSRF header name is HTTP_X_CSRFTOKEN by default, but you can
customize it using the CSRF_HEADER_NAME setting.
Axios Docs
This is from the Axios docs. It indicates that you set the name of the cookie which contains the csrftoken
, and the name of the header here:
// `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
. The only cookie I have is the CSRF token I put in the Django template. 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. In Django this is by default
csrftoken
, which is on the left side of the equals sign in the cookie.
- The actual token. This is everything on the right side of the equals sign in the cookie.
- The http header that carries the token value.
Things I tried that didn't work: 1, 2
回答2:
I've found out, that axios.defaults.xsrfCookieName = "XCSRF-TOKEN";
and CSRF_COOKIE_NAME = "XCSRF-TOKEN"
DOESN'T WORK IN APPLE Safari on Mac OS
The solution for MAC Safari is easy, just change XCSRF-TOKEN
to csrftoken
So, in js-code should be:
import axios from 'axios';
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
axios.defaults.xsrfCookieName = "csrftoken";
In settings.py:
CSRF_COOKIE_NAME = "csrftoken"
回答3:
This configuration works for me without problems 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
回答4:
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:
CSRF_COOKIE_NAME = "XCSRF-TOKEN"
回答5:
You could add the Django-provided CSRF token manually into all of your post requests, but that's annoying.
From the 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.
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.
回答6:
There is actually a really easy way to do this.
Add axios.defaults.xsrfHeaderName = "X-CSRFToken";
to your app config and then set CSRF_COOKIE_NAME = "XSRF-TOKEN"
in your settings.py file. Works like a charm.
回答7:
For me, django wasn't listening to the headers that I was sending. I could curl into the api but couldn't access it with axios. Check out the cors-headers package... it might be your new best friend.
I fixed it by installing 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
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