Django Rest Framework 不会接受我的 CSRF 令牌

Ozz*_*ant 2 python django cookies csrf django-rest-framework

我正在尝试使用 Django Rest Framework 构建单页应用程序。对于身份验证,我使用登录视图来启动会话并要求对所有 api 路由进行 csrf 保护。由于没有进行模板化,因此csrf_token从未使用该标记,因此我必须使用 手动获取令牌get_token。我不想将其放在主视图中给出的主索引文件中,而是想将其设置在自己的 cookie 上。不,这不是 django 提供的 CSRF Coo​​kie,因为它具有 CSRF 秘密,而且我提到我正在使用会话,因此秘密存储在那里。该 cookie 将具有将用于所有变异请求的令牌。

我已经尝试了一切让 django 接受 cookie 但没有任何效果。我第一次可以正常登录,因为没有之前的会话,但此后的任何操作都会抛出错误 403。我尝试使用,ensure_csrf_cookie但没有帮助。我尝试不进行会话,但仍然一无所获。我什至尝试重新排列中间件顺序,但仍然一无所获。我什至尝试了自己的自定义中间件来创建 cookie,但它不起作用。这是我现在得到的代码:

视图.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);
Run Code Online (Sandbox Code Playgroud)

设置.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"
    ]
}
Run Code Online (Sandbox Code Playgroud)

这是我编写的自定义中间件,但我现在没有使用:

"""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;
Run Code Online (Sandbox Code Playgroud)

错误 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."
}
Run Code Online (Sandbox Code Playgroud)

这是我在 vs code 的 REST 客户端中使用的 http 请求:

POST http://electro:8000/api/logout
X-XSRF-TOKEN: JFaygAm49v6wChT6CcUJaeLwq53YwzAlnEZmoE0m21cg9xLCnZGvTt6oM9MKbvV8
Cookie: electro=nzzv64gymt1aqu4whdhax1s9t91c3m58


Run Code Online (Sandbox Code Playgroud)

我简直不敢相信,当对静态网站和 API 提供大量支持时,调整框架以与单页应用程序配合使用是多么困难。那么我到底哪里出了问题呢?

Ozz*_*ant 6

我终于明白发生了什么事。深入 django 文档,我发现该CSRF_HEADER_NAME设置具有特定的语法/格式:

# default value
CSRF_HEADER_NAME = "HTTP_X_CSRFTOKEN";
Run Code Online (Sandbox Code Playgroud)

因此,为了解决这个问题,文档字面意思是,对于我的情况,我必须根据我的喜好设置该值,如下所示:

CSRF_HEADER_NAME = "HTTP_X_XSRF_TOKEN";
Run Code Online (Sandbox Code Playgroud)

所以现在它可以接受 header 处的令牌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);
Run Code Online (Sandbox Code Playgroud)

这可能对使用 django 构建单页应用程序后端的人有所帮助