【问题标题】:Test CSRF Verification with Django Rest Framework使用 Django Rest 框架测试 CSRF 验证
【发布时间】:2017-03-06 10:57:25
【问题描述】:

我正在使用 Django Rest Framework 3,并想测试 CSRF 验证。

首先,我初始化 DRF APIClient

client = APIClient(enforce_csrf_checks=True)

然后我为用户设置密码,以便我可以登录并获得会话:

superuser.set_password('1234')
superuser.save()
client.login(email=superuser.email, password='1234')

现在我们需要一个 CSRF 令牌。为此,我只需创建一个请求并从 cookie 中检索令牌。

response = client.request()
csrftoken = client.cookies['csrftoken'].value

当检查代码时,这似乎有效,我得到了一个看起来有效的 CSRF 令牌。然后我执行 POST 请求,传入 csrfmiddlewartoken 参数:

data = {'name': 'My fancy test report', 'csrfmiddlewaretoken': csrftoken}
response = client.post(API_BASE + '/reports', data=data, format='json')
assert response.status_code == status.HTTP_201_CREATED, response.content

问题是,这失败了:

tests/api/test_api.py:156: in test_csrf_success
    assert response.status_code == status.HTTP_201_CREATED, response.content
E   AssertionError: {"detail":"CSRF Failed: CSRF token missing or incorrect."}
E   assert 403 == 201
E    +  where 403 = <rest_framework.response.Response object at 0x7f7bd6453bd0>.status_code
E    +  and   201 = status.HTTP_201_CREATED

用 DRF 测试 CSRF 验证的正确方法是什么?

【问题讨论】:

    标签: django testing django-rest-framework django-csrf


    【解决方案1】:

    编辑

    所以,经过一番研究,我发现了以下内容:

    Django 不一定会在标头中设置 CSRF 标记,除非它正在渲染一个明确包含 csrf_token 模板标签的模板。这意味着您需要请求一个使用 csrf 令牌呈现表单的页面,或者您需要创建一个用ensure_csrf_cookie 装饰的令牌请求视图。

    由于每个会话的 csrf 令牌是唯一的,因此可以创建一个通用的令牌设置视图,如下所示:

    from django.views.decorators.csrf import ensure_csrf_cookie
    
    @ensure_csrf_cookie
    def token_security(request):
        return HttpResponse()  # json or whatever
    

    然后,当您希望 POST 到受 CSRF 保护的端点并且 cookie 中没有 CSRF 令牌时,请针对此视图发出 GET,它应该设置 cookie,然后可以使用该 cookie 进行 POST。

    原答案如下:


    以下在我的测试中有效(我使用工厂来创建用户对象,但您可以手动创建它们):

    class TestLoginApi(APITestCase):
        def setUp(self):
            self.client = APIClient(enforce_csrf_checks=True)
            self.path = reverse("registration:login")
            self.user = UserFactory()
    
        def tearDown(self):
            self.client.logout()
    
        def _get_token(self, url, data):
            resp = self.client.get(url)
            data['csrfmiddlewaretoken'] = resp.cookies['csrftoken'].value
            return data
    
       def test_login(self):
            data = {'username': self.user.username,
                    'password': PASSWORD}
            data = self._get_token(self.path, data)
    
            # This should log us in.
            # The client should re-use its cookies, but if we're using the
            # `requests` library or something, we'd have to re-use cookies manually.
            resp = self.client.post(self.path, data=data)
            self.assertEqual(resp.status_code, 200)
            etc.
    

    如果这一切都是动态完成的,您还必须确保您的视图在 GET 上设置了一个 cookie,因为 according to the Django docs (see the Warning),如果您不是从具有 @987654326 的模板回帖,它将不会自动设置@ 放。

    如果您需要设置它,看起来像这样(在您的 DRF views.py 中):

    from django.utils.decorators import method_decorator
    from django.views.decorators.csrf import ensure_csrf_cookie
    
        @method_decorator(ensure_csrf_cookie)
        def get(self, request, *args, **kwargs):
            return SomeJson...
    

    最后,对于我的 Django Rest Framework 视图,我必须确保 POST 也受到 csrf 保护(但这看起来不像您遇到的问题):

    from django.views.decorators.csrf import csrf_protect
    
        @method_decorator(csrf_protect)
        def post(self, request, *args, **kwargs):
            return SomeJson...
    

    【讨论】:

    • 你在@method_decorator(ensure_csrf_cookie) 中有错误,这个装饰器确保Response 将带有csrf cookie,而不是Request 应该有那个cookie。因此你没有检查任何东西,正确的装饰器是@method_decorator(csrf_protect),我希望它只是错字)
    • 您在POST 上是对的。我有正确的导入,但在这里的编辑器中复制粘贴不正确。
    • @erewok 是否有必要为实际上不处理 POST 请求的 post 请求测试视图功能
    猜你喜欢
    • 2015-03-29
    • 2017-07-02
    • 2016-03-18
    • 2019-07-28
    • 2015-04-19
    • 1970-01-01
    • 2016-06-23
    • 2015-09-01
    相关资源
    最近更新 更多