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

如何在Django单元测试中Mock JWT令牌认证?

Django单元测试Mock JWT验证异常问题解决

问题描述

在Django中编写单元测试时需要Mock JWT令牌验证,使用以下代码出现异常。怀疑问题在于无法使用decode方法——jwt.encode返回str类型,str没有decode方法,但参考的示例里jwt.encode返回的是bytes类型。

辅助函数

def generate_jwt_token():
    client_secret = getattr(settings, "AUTH0_CLIENT_SECRET", None)
    payload = {'user_id': 1}
    token = jwt.encode(payload, client_secret,  algorithm='HS256')
    # 注释掉下面一行是因为jwt.encode返回str,str没有decode方法
    #return token.decode('utf-8')
    return token

单元测试

@patch('jwt.decode')
def test_post_request_with_valid_data_succeeds(self, mock_decode):
    payload1 = {'user_id': 1}
    mock_decode.return_value = payload1
    response = self.client.post(self.url, self.dataset, format="multipart", HTTP_AUTHORIZATION=f'Bearer {self.token}')

异常信息

Traceback (most recent call last):
  File "/usr/local/lib/python3.8/unittest/mock.py", line 1325, in patched
    return func(*newargs, **newkeywargs)
  File "/app/apps/datasets/test/test_views.py", line 89, in test_post_request_with_valid_data_succeeds
    response = self.client.post(self.url, self.dataset, format="multipart", HTTP_AUTHORIZATION=f'Bearer {self.token}')
  File "/root/.local/lib/python3.8/site-packages/rest_framework/test.py", line 296, in post
    response = super().post(
  File "/root/.local/lib/python3.8/site-packages/rest_framework/test.py", line 210, in post
    return self.generic('POST', path, data, content_type, **extra)
  File "/root/.local/lib/python3.8/site-packages/rest_framework/test.py", line 234, in generic
    return super().generic(
  File "/root/.local/lib/python3.8/site-packages/django/test/client.py", line 473, in generic
    return self.request(**r)
  File "/root/.local/lib/python3.8/site-packages/rest_framework/test.py", line 286, in request
    return super().request(**kwargs)
  File "/root/.local/lib/python3.8/site-packages/rest_framework/test.py", line 238, in request
    request = super().request(**kwargs)
  File "/root/.local/lib/python3.8/site-packages/django/test/client.py", line 719, in request
    self.check_exception(response)
  File "/root/.local/lib/python3.8/site-packages/django/test/client.py", line 580, in check_exception
    raise exc_value
  File "/root/.local/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/root/.local/lib/python3.8/site-packages/django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/root/.local/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/root/.local/lib/python3.8/site-packages/rest_framework/viewsets.py", line 125, in view
    return self.dispatch(request, *args, **kwargs)
  File "/root/.local/lib/python3.8/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "/root/.local/lib/python3.8/site-packages/rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/root/.local/lib/python3.8/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "/root/.local/lib/python3.8/site-packages/rest_framework/views.py", line 497, in dispatch
    self.initial(request, *args, **kwargs)
  File "/root/.local/lib/python3.8/site-packages/rest_framework/views.py", line 414, in initial
    self.perform_authentication(request)
  File "/root/.local/lib/python3.8/site-packages/rest_framework/views.py", line 324, in perform_authentication
    request.user
  File "/root/.local/lib/python3.8/site-packages/rest_framework/request.py", line 227, in user
    self._authenticate()
  File "/root/.local/lib/python3.8/site-packages/rest_framework/request.py", line 380, in _authenticate
    user_auth_tuple = authenticator.authenticate(self)
  File "/root/.local/lib/python3.8/site-packages/rest_framework_simplejwt/authentication.py", line 38, in authenticate
    validated_token = self.get_validated_token(raw_token)
  File "/root/.local/lib/python3.8/site-packages/rest_framework_simplejwt/authentication.py", line 92, in get_validated_token
    return AuthToken(raw_token)
  File "/root/.local/lib/python3.8/site-packages/rest_framework_simplejwt/tokens.py", line 42, in __init__
    self.payload = token_backend.decode(token, verify=verify)
  File "/root/.local/lib/python3.8/site-packages/rest_framework_simplejwt/backends.py", line 136, in decode
    self.get_verifying_key(token),
  File "/root/.local/lib/python3.8/site-packages/rest_framework_simplejwt/backends.py", line 99, in get_verifying_key
    return self.jwks_client.get_signing_key_from_jwt(token).key
  File "/root/.local/lib/python3.8/site-packages/jwt/jwks_client.py", line 59, in get_signing_key_from_jwt
    return self.get_signing_key(header.get("kid"))
  File "/root/.local/lib/python3.8/site-packages/jwt/jwks_client.py", line 50, in get_signing_key
    raise PyJWKClientError(
jwt.exceptions.PyJWKClientError: Unable to find a signing key that matches: "None"

问题分析与解决

1. jwt.encode返回值差异问题

这是PyJWT版本差异导致的:

  • PyJWT v1.x版本中,jwt.encode返回bytes类型,需要decode转成str
  • PyJWT v2.x版本中,jwt.encode默认返回str类型,不需要decode

你的代码里已经注释掉了decode行,这部分是正确的。如果要兼容两个版本,可以修改辅助函数:

def generate_jwt_token():
    client_secret = getattr(settings, "AUTH0_CLIENT_SECRET", None)
    payload = {'user_id': 1}
    token = jwt.encode(payload, client_secret, algorithm='HS256')
    # 兼容PyJWT v1和v2版本
    if isinstance(token, bytes):
        return token.decode('utf-8')
    return token

2. Mock失效导致的异常问题

报错的核心原因是你Mock的对象不对——你patch了jwt.decode,但项目中实际用的是rest_framework_simplejwt的认证流程,它内部并没有直接调用jwt.decode,而是通过自身的认证后端处理令牌,所以你的Mock根本没生效,真实的认证流程仍在执行,导致找不到匹配的签名密钥。

解决方案一:Mock正确的认证方法

直接Mockrest_framework_simplejwt认证类的authenticate方法,跳过真实的令牌验证:

from unittest.mock import patch
from django.contrib.auth.models import User

@patch('rest_framework_simplejwt.authentication.JWTAuthentication.authenticate')
def test_post_request_with_valid_data_succeeds(self, mock_authenticate):
    # 模拟认证通过,返回用户对象
    test_user = User.objects.create(username='test_user')
    mock_authenticate.return_value = (test_user, None)
    
    response = self.client.post(
        self.url, 
        self.dataset, 
        format="multipart", 
        HTTP_AUTHORIZATION=f'Bearer {self.token}'
    )
    
    # 断言请求成功(根据实际需求调整)
    self.assertEqual(response.status_code, 201)

解决方案二:生成真实的测试令牌

如果不需要完全Mock,也可以用rest_framework_simplejwt提供的工具生成合法的测试令牌:

from rest_framework_simplejwt.tokens import AccessToken
from django.contrib.auth.models import User

def setUp(self):
    super().setUp()
    # 创建测试用户
    self.test_user = User.objects.create(username='test_user')
    # 生成该用户的合法访问令牌
    self.token = AccessToken.for_user(self.test_user)

这样发送请求时,令牌会被正常验证通过,不需要Mock。


内容的提问来源于stack exchange,提问作者Neo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 10:49:56