使用Django Rest Framework测试CSRF验证

时间:2015-04-20 13:18:55

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

我正在使用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 个答案:

答案 0 :(得分: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

然后,每当您希望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.

如果这一切都是动态完成的,您还必须确保您的视图在GET上设置了一个cookie,因为according to the Django docs (see the Warning),如果您没有从具有该模板的模板进行回发,则不会自动设置它。 {{1套。

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

{% csrf_token %}

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

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...