Django测试访问/course接口返回403未授权问题排查与解决
问题描述
在Django中测试/course接口时,已在setUp方法中调用/auth/login完成登录(确认setUp执行正常),但测试返回403 Forbidden错误,提示“Authentication credentials were not provided”。打印请求头发现HTTP_AUTHORIZATION的值为“Bearer ”(token为空),响应头未携带授权头。
错误日志:
ERROR:root:the exc is Authentication credentials were not provided. and context is {'view': <course.views.CourseApi object at 0x7fe78ba9ad10>, 'args': (), 'kwargs': {}, 'request': <rest_framework.request.Request: GET '/course'>} and response is <Response status_code=403, "text/html; charset=utf-8"> and code 400
WARNING:django.request:Forbidden: /course
response headers {'Content-Type': 'application/json', 'Vary': 'Accept, origin', 'Allow': 'GET, POST, PUT, DELETE, HEAD, OPTIONS', 'X-Frame-Options': 'DENY', 'Content-Length': '77', 'X-Content-Type-Options': 'nosniff', 'Referrer-Policy': 'same-origin', 'Cross-Origin-Opener-Policy': 'same-origin'}
F
问题根源
测试代码存在三个关键错误:
- 使用
requests.post发起登录请求,而非Django测试客户端self.client.post,导致测试环境上下文不统一,获取的token无法正确关联到测试客户端。 force_authenticate方法使用错误:DRF的force_authenticate需要传入user对象,而非token字符串,该方法无法直接通过token设置认证。- 手动设置请求头时,可能因之前的认证配置错误,导致token未被正确携带。
修复方案
步骤1:改用Django测试客户端发起登录请求
替换requests.post为self.client.post,利用测试客户端的模拟请求能力,确保测试环境上下文一致。
步骤2:正确设置客户端认证凭证
使用self.client.credentials()方法设置默认授权头,后续所有请求都会自动携带该头,无需手动重复设置。
步骤3:删除错误的force_authenticate调用
移除无效的force_authenticate代码,改用正确的凭证设置方式。
修改后的完整测试代码
from django.test import TestCase from rest_framework.test import APIClient class TestUrls(TestCase): def setUp(self): self.client = APIClient() # 使用测试客户端发起登录请求 login_url = '/auth/login' login_data = { 'email': '<my_email>', 'password': '<my_password>', 'token': 'Bearer <my_token_str>' } response = self.client.post(login_url, login_data, format='json') print(response.json()) if response.status_code == 200: # 从响应中提取token self.token = response.json().get('payload').get('token') print(self.token) # 设置客户端默认授权头 self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {self.token}') else: raise AssertionError(f"登录失败,状态码: {response.status_code}") def test_course_view_url_is_resolved(self): # 无需手动设置headers,客户端会自动携带授权头 response = self.client.get('/course') print("response headers", response.headers) self.assertEqual(response.status_code, 200)
额外说明
- 如果你的项目使用的是DRF的TokenAuthentication(而非JWT),授权头格式应为
Token {token},而非Bearer {token},需要根据实际认证方式调整。 - 确保
/auth/login接口返回的payload.token确实是有效的认证token,可通过打印self.token确认值不为空。
内容的提问来源于stack exchange,提问作者Omnia Osman

