You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 19:57:31