使用Django Rest Framework测试CSRF验证

8

我正在使用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验证?

1个回答

11

编辑

经过一番调查,我发现以下内容:

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保护的端点时,如果cookies中没有CSRF令牌,请针对此视图发出GET请求,它应该设置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,因为根据Django文档(请参见警告),如果您不是从设置了{% csrf_token %}的模板进行POST,则不会自动设置。

如果需要设置它,可以在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...

1
你在 @method_decorator(ensure_csrf_cookie) 中出现了错误,这个装饰器确保 Response 带有 csrf cookie,而不是要求 Request 具有该 cookie。因此,你没有检查任何内容,正确的装饰器应该是 @method_decorator(csrf_protect),我希望这只是打字错误。 - Compadre
你说得对,POST没问题。我导入的是正确的,但是在这里编辑器中复制粘贴出了问题。 - erewok
@erewok 是否有必要测试视图函数以处理实际未处理POST请求的POST请求? - vishnu m c

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接