Django Rest Framework 将不接受我的 CSRF 令牌
Posted
技术标签:
【中文标题】Django Rest Framework 将不接受我的 CSRF 令牌【英文标题】:Django Rest Framework will not accept my CSRF Token 【发布时间】:2019-09-15 06:23:10 【问题描述】:我正在尝试使用 Django Rest Framework 构建单页应用程序。对于身份验证,我使用了一个登录视图,该视图启动了一个会话并要求对所有 api 路由进行 csrf 保护。因为没有进行模板化,所以从未使用过csrf_token
标签,所以我必须使用get_token
手动获取令牌。我不想将它放在将在主视图中给出的主索引文件中,而是将它设置在它自己的 cookie 上。不,这不是 django 提供的 CSRF Cookie,因为它有 CSRF 秘密,而且我提到我正在使用会话,所以秘密存储在那里。此 cookie 将具有将用于所有变异请求的令牌。
我已经尽一切努力让 django 接受 cookie,但没有任何效果。我第一次可以正常登录,因为之前没有会话,但之后的任何操作都会引发错误 403。我尝试使用 ensure_csrf_cookie
但这没有帮助。我尝试了没有会话,但仍然没有。我什至尝试重新排列中间件顺序,但仍然没有。我什至尝试了我自己的自定义中间件来创建 cookie,但它不起作用。以下是我目前得到的代码:
views.py
@api_view(http_method_names = ["GET"])
def home(request):
"""API route for retrieving the main page of web application"""
return Response(None, status = status.HTTP_204_NO_CONTENT);
class LoginView(APIView):
"""API endpoint that allows users to login"""
def post(self, request, format = None):
"""API login handler"""
user = authenticate(username = request.data["username"], password = request.data['password']);
if user is None:
raise AuthenticationFailed;
login(request, user);
return Response(UserSerializer(user).data);
class LogoutView(APIView):
"""API endpoint that allows users to logout of application"""
def post(self, request, format = None):
logout(request);
return Response(None, status = status.HTTP_204_NO_CONTENT);
settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware'
]
# Sessions
SESSION_ENGINE = "django.contrib.sessions.backends.file"
SESSION_FILE_PATH = os.getenv("DJANGO_SESSION_FILE_PATH")
SESSION_COOKIE_AGE = int(os.getenv("SESSION_LIFETIME")) * 60
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_NAME = os.getenv("SESSION_COOKIE")
SESSION_COOKIE_SECURE = os.getenv("APP_ENV") != "local"
# CSRF
CSRF_USE_SESSIONS = True
# the following setting is for the csrf token only, not for the CSRF secret, which is the default for django
CSRF_TOKEN_CARRIER = os.getenv("XSRF_COOKIE")
CSRF_HEADER_NAME = "X-XSRF-TOKEN"
CSRF_COOKIE_SECURE = SESSION_COOKIE_SECURE
CSRF_COOKIE_AGE = SESSION_COOKIE_AGE
CSRF_TOKEN_HTTPONLY = False
REST_FRAMEWORK =
"EXCEPTION_HANDLER":"django_app.application.exceptions.global_exception_handler",
"DEFAULT_AUTHENTICATION_CLASSES":[
"rest_framework.authentication.SessionAuthentication"
]
这是我编写的自定义中间件,但我现在没有使用:
"""Custom CSRF Middleware for generating CSRF cookies"""
from django.conf import settings;
from django.middleware.csrf import CsrfViewMiddleware, rotate_token, get_token;
class CSRFCookieMiddleware:
"""Sets CSRF token cookie for ajax requests"""
def __init__(self, get_response):
self.get_response = get_response;
def __call__(self, request):
response = self.get_response(request);
if settings.CSRF_USE_SESSIONS:
response.set_cookie(
settings.CSRF_TOKEN_CARRIER,
get_token(request),
max_age=settings.CSRF_COOKIE_AGE,
domain=settings.CSRF_COOKIE_DOMAIN,
path=settings.CSRF_COOKIE_PATH,
secure=settings.CSRF_COOKIE_SECURE,
httponly=settings.CSRF_COOKIE_HTTPONLY,
samesite=settings.CSRF_COOKIE_SAMESITE,
);
return response;
错误 403 响应:
HTTP/1.1 403 Forbidden
Date: Thu, 25 Apr 2019 17:11:28 GMT
Server: WSGIServer/0.2 CPython/3.7.0
Content-Type: application/json
Vary: Accept, Cookie
Allow: POST, OPTIONS
X-Frame-Options: SAMEORIGIN
Content-Length: 59
"message": "CSRF Failed: CSRF token missing or incorrect."
这是我在 REST 客户端中在 vs 代码中使用的 http 请求:
POST http://electro:8000/api/logout
X-XSRF-TOKEN: JFaygAm49v6wChT6CcUJaeLwq53YwzAlnEZmoE0m21cg9xLCnZGvTt6oM9MKbvV8
Cookie: electro=nzzv64gymt1aqu4whdhax1s9t91c3m58
当静态网站和 API 得到大量支持时,我无法相信调整框架以与单页应用程序一起工作是多么困难。那我哪里做错了?
【问题讨论】:
你确定你是sending token from your client-side correctly? @ar-m-an 查看我编辑的问题。我添加了我发送的 http 请求。根据 settings.py 模块,我使用X-XSRF-TOKEN
而不是默认的 X-CSRFToken
标头。我还将会话 cookie 发送回服务器。我也尝试使用令牌值设置 XSRF_TOKEN cookie,但这也不起作用。至于您提供的答案,我目前没有使用任何 javascript,这完全是在 vs 代码中的 REST 客户端上测试的。
【参考方案1】:
我终于明白发生了什么。 Buried deep in the django documentation,我发现CSRF_HEADER_NAME
设置有特定的语法/格式:
# default value
CSRF_HEADER_NAME = "HTTP_X_CSRFTOKEN";
为了解决这个问题,文档字面上说,对于我的情况,我必须根据自己的喜好设置值,如下所示:
CSRF_HEADER_NAME = "HTTP_X_XSRF_TOKEN";
所以现在它可以接受X-XSRF-TOKEN
标头处的令牌以及会话cookie。但是由于我正在使用带有 csrf 的会话,因此我必须使用我创建的自定义中间件(请参阅问题)来手动设置 csrf 令牌 cookie。这是因为 ensure_csrf_cookie 显然只会抛出会话 cookie。
最后,如果您需要保护登录路径,因为我使用的是SessionAuthentication
,我将需要自定义中间件ensure_csrf_cookie
和csrf_protect
,以便我可以使用 csrf 令牌和然后在登录时提交:
@api_view(http_method_names = ["GET"])
@ensure_csrf_cookie
def home(request):
"""API route for retrieving the main page of web application"""
return Response(None, status = status.HTTP_204_NO_CONTENT);
@method_decorator(csrf_protect, 'dispatch')
@method_decorator(ensure_csrf_cookie, 'dispatch')
class LoginView(APIView):
"""API endpoint that allows users to login"""
def post(self, request, format = None):
"""API login handler"""
user = authenticate(username = request.data["username"], password = request.data['password']);
if user is None:
raise AuthenticationFailed;
login(request, user);
return Response(UserSerializer(user).data);
这对使用 django 构建单页应用程序后端的人有帮助
【讨论】:
以上是关于Django Rest Framework 将不接受我的 CSRF 令牌的主要内容,如果未能解决你的问题,请参考以下文章
Django-rest-framework 和 django-rest-framework-jwt APIViews and validation Authorization headers
Django Rest Framework 和 django Rest Framework simplejwt 两因素身份验证