Django单元测试中OAuth2认证失败:401 'invalid_client'错误排查
解决Django OAuth2单元测试返回401 'invalid_client'的问题
核心原因
你使用的是CLIENT_CONFIDENTIAL类型的OAuth2客户端,对于这类客户端,django-oauth-toolkit要求客户端凭证(client_id和client_secret)通过HTTP Basic Authentication方式传递,而非放在POST请求的JSON body中。同时,OAuth2的token端点默认仅接受application/x-www-form-urlencoded格式的请求体,而非JSON格式,这也是导致凭证无法被正确解析的关键原因。
修复步骤
1. 改用HTTP Basic Auth传递客户端凭证
在测试中,使用Django测试客户端的credentials()方法设置Basic Auth,将client_id和client_secret作为认证信息传入:
def test_password_grant(self): token_url = reverse('oauth2_provider:token') # 设置HTTP Basic Auth self.client.credentials(HTTP_AUTHORIZATION='Basic ' + base64.b64encode( f"{self.application.client_id}:{self.application.client_secret}".encode() ).decode()) data = { 'grant_type': 'password', 'username': 'testuser@example.com', 'password': 'testpass123', # 移除body中的client_id和client_secret } # 使用form格式发送请求(默认就是form,也可以显式指定format='form') response = self.client.post(token_url, data) print(response.json()) self.assertEqual(response.status_code, 200)
2. 确保请求格式正确
移除format='json'参数,因为OAuth2 token端点默认不处理JSON格式的请求体。如果确实需要使用JSON格式,需额外配置django-oauth-toolkit支持,但这不符合OAuth2规范的默认实现,不推荐。
3. 额外检查点
- 确认你的
RunMigrationsMiddleware已正确运行所有迁移,包括django-oauth-toolkit的迁移,确保oauth2_provider_application表存在且数据正确。 - 验证测试中创建的Application对象的
client_type确实是Application.CLIENT_CONFIDENTIAL(你已在setUp中设置,这部分无问题)。
修改后的完整测试代码
import base64 from rest_framework.test import APITestCase from django.contrib.auth import get_user_model from oauth2_provider.models import Application from django.urls import reverse class OAuth2Test(APITestCase): def setUp(self): self.user = get_user_model().objects.create_user( email='testuser@example.com', password='testpass123' ) self.application = Application.objects.create( name='Test Application', client_type=Application.CLIENT_CONFIDENTIAL, authorization_grant_type=Application.GRANT_PASSWORD, user=self.user ) def test_password_grant(self): token_url = reverse('oauth2_provider:token') # 配置Basic Auth auth_str = f"{self.application.client_id}:{self.application.client_secret}" auth_bytes = auth_str.encode('utf-8') auth_base64 = base64.b64encode(auth_bytes).decode('utf-8') self.client.credentials(HTTP_AUTHORIZATION=f'Basic {auth_base64}') data = { 'grant_type': 'password', 'username': 'testuser@example.com', 'password': 'testpass123', } response = self.client.post(token_url, data) print(response.json()) self.assertEqual(response.status_code, 200)
内容的提问来源于stack exchange,提问作者user27197082
相关产品推荐
相关产品推荐

