CSRF 仅在 DRF 中进行身份验证时才检查?

CSRF is only checked when authenticated in DRF?

TLDR;如果客户端有经过身份验证的会话,我的 POSTs(到 DRF 端点)似乎仅受 CSRF 保护。这是错误的,将应用程序选项留给 login CSRF 攻击。我该如何解决这个问题?

我开始为 ReactJS 前端构建一个 django rest 框架 API,我们希望通过 API 处理包括身份验证在内的所有内容。我们正在使用 SessionAuthentication。

如果我有一个经过身份验证的会话,那么 CSRF 将完全按预期工作(当经过身份验证的客户端应该设置 CSRF cookie 时,这需要与 POST 数据中的 csrfmiddlewaretoken 配对) .

然而,当 验证时,似乎没有 POST 受到 CSRF 检查。包括已创建的(基本)登录 API 视图。这使得网站容易受到 login CSRF 攻击。

有谁知道如何在未经身份验证的会话上强制执行 CSRF 检查? and/or DRF 似乎如何绕过 CSRF 检查以进行登录?

下面是我的粗略设置...

settings.py:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}

views.py:

class Login(APIView):

    permission_classes = (permissions.AllowAny,)

    @method_decorator(csrf_protect)  # shouldn't be needed
    def post(self, request, format=None):
        user = authenticate(
            request,
            username=request.POST['username'],
            password=request.POST['password']
        )
        # ... perform login logic ...

    def get(self, request, format=None):
        """
        Client must GET the login to obtain CSRF token
        """
        # Force generation of CSRF token so that it's set in the client
        get_token(request)  
        return Response(None)

urls.py:

urlpatterns = [
    url(r'^login/$', views.Login.as_view(), name='login'),
]

预期行为:

login_url = reverse('login')
login_details = {
    'username': self.user.email,
    'password': self.password,
}
client = APIClient(enforce_csrf_checks=True)
# Try to just POST to a CSRF protected view with no CSRF
response = client.post(reverse('login'), login_details)
# response status should be 403 Missing or incorrect CSRF

# GET the login API first to obtain CSRF
client.get(reverse('login'))
login_details['csrfmiddlewaretoken'] = client.cookies.get('csrftoken').value
# Now POST to the login API with the CSRF cookie and CSRF token in the POST data
response = client.post(reverse('login'), login_details)
# response status should now be 200 (and a newly rotated CSRF token delivered)

实际行为:

client = APIClient(enforce_csrf_checks=True)
# Try to just to a CSRF protected view with no CSRF
response = client.post(reverse('login'), login_details)
# BROKEN: response status is 200, client is now logged in

# Post to the exact same view again, still with no CSRF
response = client.post(reverse('login'), login_details)
# response status is now 403
# BROKEN: This prooves that this view is protected against CSRF, but ONLY for authenticated sessions.

Django REST Framework 在使用 SessionAuthentication 且用户未通过身份验证时禁用 CSRF 令牌要求。这是为了不混淆其他不需要 CSRF 身份验证的身份验证方法(因为它们不是基于 cookie),您应该自己确保 CSRF 在登录请求时得到验证,并且在 SessionAuthentication documentation。建议使用 non-API 登录进程或确保 API-based 登录进程受到完全保护。

您可以检查 DRFs SessionAuthentication 在您登录时如何执行 CSRF 验证,并以此为基础您的观点。

您可以创建强制 CSRF 的 APIView 的子 class。

from rest_framework import views

class ForceCRSFAPIView(views.APIView):
    @classmethod
    def as_view(cls, **initkwargs):
        # Force enables CSRF protection.  This is needed for unauthenticated API endpoints
        # because DjangoRestFramework relies on SessionAuthentication for CSRF validation
        view = super().as_view(**initkwargs)
        view.csrf_exempt = False
        return view

那么您需要做的就是将您的登录视图更改为从此视图下降

class Login(ForceCRSFAPIView)
    # ...