使用Django Rest Framework测试CSRF验证

Dan*_*gen 5 testing django django-csrf django-rest-framework

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

首先,我初始化DRF APIClient:

client = APIClient(enforce_csrf_checks=True)
Run Code Online (Sandbox Code Playgroud)

然后我在用户上设置密码,这样我就可以登录并获得一个会话:

superuser.set_password('1234')
superuser.save()
client.login(email=superuser.email, password='1234')
Run Code Online (Sandbox Code Playgroud)

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

response = client.request()
csrftoken = client.cookies['csrftoken'].value
Run Code Online (Sandbox Code Playgroud)

在检查代码时,这似乎有效,我找回了一个有效的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
Run Code Online (Sandbox Code Playgroud)

问题是,这失败了:

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

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

ere*_*wok 7

编辑

所以,在研究了一下之后,我发现了以下内容:

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

然后,只要您希望POST到CSRF保护的端点并且cookie中没有CSRF令牌,就针对此视图发出GET,并且应该设置cookie,然后可以将其用于POST.

原答案如下:


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

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

如果这一切都是动态完成的,你还必须确保你的视图在GET上设置一个cookie,因为根据Django文档(参见警告),如果你没有从模板中回发,它将不会被自动设置.有这个{% csrf_token %}集合.

如果你需要设置它,看起来像这样(在你的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...
Run Code Online (Sandbox Code Playgroud)

最后,对于我的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...
Run Code Online (Sandbox Code Playgroud)