Django单元测试中JWT解码返回空username问题排查
Django JWT解码后username为空的问题排查与解决
问题场景
以下是生成JWT、测试授权及解码的相关代码,解码后得到的payload中username字段为空:
生成JWT的LoginView代码
class LoginView(APIView): @swagger_auto_schema(request_body=UserSerializer) def post(self, request): username = request.data['email'] password = request.data['password'] user = User.objects.filter(username=username).first() if user is None: raise AuthenticationFailed('User not found!') if not user.check_password(password): raise AuthenticationFailed('Incorrect password!') payload = { 'username': user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=60), 'iat': datetime.datetime.utcnow() } token = jwt.encode(payload, "secret", algorithm="HS256") response = Response() response.set_cookie(key='jwt', value=token, httponly=True) response.data = { 'jwt': token } return response
单元测试授权方式
self.client.credentials(HTTP_AUTHORIZATION = resp_login.data.get("jwt"))
JWT解码代码
def get_payload(request): token = request.META.get('HTTP_AUTHORIZATION') if not token: raise AuthenticationFailed('Unauthenticated!') try: payload = jwt.decode(token, 'secret', options={"verify_signature": False}, algorithms=['HS512']) except jwt.ExpiredSignatureError: raise AuthenticationFailed('Unauthenticated!') return payload
实际解码结果
{'username': '', 'exp': 1691987384, 'iat': 1691983784}
问题根源
- 算法不匹配:生成JWT使用的是
HS256算法,但解码时指定为HS512。即便关闭了签名验证,部分JWT库在算法不匹配时可能出现解析异常,导致字段值丢失或为空。 - payload字段赋值逻辑:生成payload时,
username字段赋值为user.email,若数据库中该用户的email字段本身为空,会直接导致payload中的username为空。 - 签名验证关闭风险:关闭
verify_signature会绕过签名校验,不仅存在安全隐患,也可能掩盖算法不匹配等解析问题。
解决方案
1. 统一JWT算法并开启签名验证
将解码算法改为与生成一致的HS256,同时开启签名验证(移除verify_signature=False),避免解析异常:
def get_payload(request): token = request.META.get('HTTP_AUTHORIZATION') if not token: raise AuthenticationFailed('Unauthenticated!') try: payload = jwt.decode(token, 'secret', algorithms=['HS256']) except jwt.ExpiredSignatureError: raise AuthenticationFailed('Token expired!') except jwt.InvalidTokenError: raise AuthenticationFailed('Invalid token!') return payload
2. 调整payload字段赋值逻辑
如果业务上username字段应存储用户名而非邮箱,将payload中的赋值改为user.username,与查询用户的条件保持一致:
payload = { 'username': user.username, 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=60), 'iat': datetime.datetime.utcnow() }
若确实需要存储邮箱,需检查数据库中用户的email字段是否存在有效值。
3. 规范Authorization请求头格式
遵循JWT标准格式,在请求头中传递带Bearer 前缀的token,同时在解码时处理前缀(适配生产环境场景):
# 单元测试代码修改 self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {resp_login.data.get('jwt')}") # 解码代码修改 def get_payload(request): auth_header = request.META.get('HTTP_AUTHORIZATION') if not auth_header or not auth_header.startswith('Bearer '): raise AuthenticationFailed('Unauthenticated!') token = auth_header.split(' ')[1] try: payload = jwt.decode(token, 'secret', algorithms=['HS256']) except jwt.ExpiredSignatureError: raise AuthenticationFailed('Token expired!') except jwt.InvalidTokenError: raise AuthenticationFailed('Invalid token!') return payload
内容的提问来源于stack exchange,提问作者Echchama Nayak
相关产品推荐
相关产品推荐

