使用 django rest 框架和 django oauth 工具包在单元测试中进行 oAuth 身份验证

Posted

技术标签:

【中文标题】使用 django rest 框架和 django oauth 工具包在单元测试中进行 oAuth 身份验证【英文标题】:oAuth authentication in unit tests using django rest framework and django oauth toolkit 【发布时间】:2015-07-24 08:08:50 【问题描述】:

如何为需要 oAuth 身份验证的 API 端点编写单元测试?

仅将 oAuth 令牌添加到请求标头是行不通的(可能是因为测试数据库不是持久的)。将夹具加载到数据库中也无济于事。

我正在使用 django-rest-framework 和 django-oauth-toolkit。

我的 test.py 代码:

class Com_jm_Test(TestCase):
    fixtures=['oauth.json',]
    print 'test com jm'
    multi_db=True

    def test_list_job(self):
        self.client=Client(HTTP_AUTHORIZATION='Bearer 0cx2G9gKm4XZdK8BFxoWy7AE025tvq')
        response=self.client.get('/com-jm/jobs/')
        self.assertEqual(response.status_code,200)

结果:

AssertionError: 401 != 200

【问题讨论】:

【参考方案1】:

查看 DRF 的测试文档,特别是 the chapter on "Forcing authentication"。从这些文档中:

例如,当强制使用令牌进行身份验证时,您可能会执行以下操作:

user = User.objects.get(username='olivia')
request = factory.get('/accounts/django-superstars/')
force_authenticate(request, user=user, token=user.token)

【讨论】:

我之前尝试过,但是当我将用户的查询集放入测试用例时,它总是给我一个错误消息,找不到符合查询集的数据 您能详细说明一下吗?您使用的是什么查询集?并且您是否在测试数据库中加载了测试用户数据? fixture_dirs 似乎有问题,我正在尝试解决这个问题,有没有其他方法可以在不强制验证的情况下进行测试?因为我进行单元测试的目的是显示只有请求有令牌可以访问我的API,非常感谢 如果您想测试未经授权的用户是否无法访问该视图,只需测试HTTP 401 Unauthorized 响应即可。 self.assertEqual(response.status_code, 401 将完成这项工作。当然,您还应该测试授权用户是否可以访问将我们带回到原始问题的视图:)【参考方案2】:

根据您的应用程序,您可能希望从 django-oauth-toolkit 修补此函数,以便为您的客户端返回任意访问令牌:

oauth2_provider.ext.rest_framework.OAuth2Authentication.authenticate

因为这是 django-rest-framework 中默认用于身份验证的方法。

【讨论】:

【参考方案3】:

这样做:

    创建用户 创建应用程序

    创建令牌

    def __create_authorization_header(token):
        return "Bearer 0".format(token)
    
    def __create_token(self, user):
    
        app = Application.objects.create(
            client_type=Application.CLIENT_CONFIDENTIAL,
            authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE,
            redirect_uris='https://www.none.com/oauth2/callback',
            name='dummy',
            user=user
        )
        access_token = AccessToken.objects.create(
            user=user,
            scope='read write',
            expires=timezone.now() + timedelta(seconds=300),
            token='secret-access-token-key',
            application=self.app
        )
        return access_token
    
     user = User(username='dummy', email='dummy@geneu.com')
     user.save()
     self.user = user
     token = __create_authorization_header(__create_token(user))
     response=self.client.get('/com-jm/jobs/', format='json', HTTP_AUTHORIZATION=token)
    

    self.assertEqual(response.status_code,200)

当然,这必须适应您的需求,但这就是想法。对于此类未来的问题(当您在文档中没有找到足够的信息来归档您的目标时)我建议您查看源代码。例如,在这种情况下,您可以在工具包库的测试中找到如何执行此操作。 (django-oauth-toolkit/oauth2_provider/tests/test_authorization_code.py)

【讨论】:

【参考方案4】:

我也遇到了同样的问题。这是我的解决方案。

DRF 提供APIClient 用于提出请求。

APIClient 类支持与 Django 的标准客户端类相同的请求接口。这意味着标准的.get(), .post(), .put(), .patch(), .delete(), .head().options() 方法都可用。

还提供credentials 方法可用于设置标头,然后将包含在测试客户端的所有后续请求中。

client = APIClient()
client.credentials(HTTP_AUTHORIZATION='Token AB7JSH^4454')

DRF docs

创建 Oauth 令牌

创建和获取用户

token = user.oauth2_provider_accesstoken.create(expires='expire_date', token='token')

使用 APIClient 设置 Ouath 令牌

client = APIClient()
client.credentials(Authorization='Bearer '.format(token))
client.get(reverse('accounts_api:profile'))

我们可以设置默认内容类型

REST_FRAMEWORK += 
    'TEST_REQUEST_DEFAULT_FORMAT': 'json'

Git Hub Link for Source

【讨论】:

【参考方案5】:

这是结合@Deepak Kabbur 和@Daniel Albarral 的答案的改进。

    派生 DRF 的 APIClient 的子类,以使用属于给定用户的 OAuth2 应用程序设置凭据:

    import rest_framework.test
    from oauth2_provider.models import Application, AccessToken
    
    class APIClient(rest_framework.test.APIClient):
        def credentials(self, user: User, application_name: str):
            # Create or update OAuth2 application.
            app, created = Application.objects.update_or_create(
                defaults='name': application_name,
                client_type=Application.CLIENT_CONFIDENTIAL,
                authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE,
                redirect_uris='https://www.none.com/oauth2/callback',
                name=application_name,
                user=user)
            # Create or update the token, and set it in a default request header.
            now = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)
            access_token, created = AccessToken.objects.update_or_create(
                defaults='user': user, 'application': app,
                user=user,
                scope='read write',
                expires=now + datetime.timedelta(seconds=300),
                token='secret-access-token-key',
                application=app)
            super().credentials(HTTP_AUTHORIZATION='Bearer '.format(access_token))
    
    

    使用派生的 APIClient 创建测试用例(您必须至少使用 APITestCase,因为设置凭据的过程需要访问数据库):

     class TestAPI(rest_framework.test.APITestCase):
         client_class = APIClient
    
         def test_000_authenticate(self):
             self.client.credentials(ACME_MASTER_ADMIN, 'my-app')
             response = self.client.get('/api/v1/company'), format='json')
             assert response.status_code == 200
    

【讨论】:

以上是关于使用 django rest 框架和 django oauth 工具包在单元测试中进行 oAuth 身份验证的主要内容,如果未能解决你的问题,请参考以下文章

如何在带有 Django Rest 框架的 React 前端使用 Django 用户组和权限

Django rest 框架和跨源请求

django.test.client 上的 Django rest 框架导入错误

django rest框架和表单:怎么做

Facebook Auth 与 AngularJS 和 Django REST 框架

电子邮件验证和密码重置 - django rest 框架和 angularjs