eagleye

Django 自定义 JWT 认证类文档(无username模型适配)

Django 自定义 JWT 认证类文档(无username模型适配)

一、概述

CustomJWTAuthentication是一个基于rest_framework_simplejwt的自定义认证类,主要解决以下核心问题:

  • 适配移除了username字段的用户模型(如使用mobile/email作为登录标识);
  • 增强令牌声明的可观测性(记录令牌包含的所有声明);
  • 细化异常处理逻辑(用户不存在、令牌缺失标识等场景);
  • 通过日志记录关键认证过程(便于生产环境问题排查)。

二、类定义与核心功能

2.1 类继承关系

class CustomJWTAuthentication(JWTAuthentication):

"""

自定义JWT认证类,适配无username字段的用户模型

"""

继承自JWTAuthentication(SimpleJWT 提供的默认认证类),重写get_user方法以适配无username模型。

2.2 核心方法:get_user

该方法从验证后的令牌中提取用户标识(如 UUID),并通过用户模型的id字段查询用户。以下是关键逻辑解析:

2.2.1 变量初始化与模型获取

user_id = None # 提前定义变量以避免引用前未赋值

User = get_user_model() # 获取项目自定义用户模型(支持动态替换)

2.2.2 令牌声明解析与日志记录

# 记录令牌中的所有声明(关键调试信息)

token_claims = list(validated_token.keys()) # 注意:此处可能存在属性引用问题

logger.debug(f"令牌中包含的声明: {token_claims}")

# 获取配置的用户ID声明字段(从 settings.py 中读取)

user_id_claim = api_settings.USER_ID_CLAIM

logger.debug(f"配置的用户ID声明字段: {user_id_claim}")

user_id = validated_token.get(user_id_claim) # 提取用户ID值

注意:潜在问题

validated_token.keys()可能引发Unresolved attribute reference "keys" for class 'Token'警告。

原因validated_token是Token类实例(如AccessToken),其内部通过payload属性存储令牌内容。部分 IDE(如 PyCharm)可能无法正确识别Token类的keys()方法(实际Token类实现了字典接口,支持keys())。

替代方案(更明确):

token_payload = validated_token.payload # 获取令牌负载(字典类型)

token_claims = list(token_payload.keys()) # 从负载中获取声明

2.2.3 用户ID缺失处理

if user_id is None:

# 尝试其他常见用户ID声明名称(兼容不同令牌生成方式)

possible_claims = ['user_id', 'sub', 'uid', 'id']

for claim in possible_claims:

value = validated_token.get(claim)

if value:

logger.warning(f"发现可能的用户标识声明 '{claim}': {value}") # 提示潜在替代声明

logger.warning("令牌中缺少用户标识声明")

raise InvalidToken(_('Token contains no user identifier')) # 抛出无效令牌异常

2.2.4 用户查询与异常处理

try:

logger.debug(f"尝试认证用户: user_id={user_id}") # 记录认证尝试

return User.objects.get(id=user_id) # 通过 UUID 主键查询用户

except User.DoesNotExist:

# 确保 user_id 已定义(避免空值导致日志混乱)

user_id_str = str(user_id) if user_id is not None else "未知"

logger.warning(f"用户不存在: user_id={user_id_str}") # 警告日志(用户不存在)

raise AuthenticationFailed(_('User not found')) # 抛出认证失败异常

except Exception as e:

# 通用异常处理(如数据库错误)

user_id_str = str(user_id) if user_id is not None else "未知"

logger.error(f"用户认证失败 (user_id={user_id_str}): {str(e)}", exc_info=True) # 错误日志(含堆栈)

raise InvalidToken(_('Authentication failed: ') + str(e)) # 抛出无效令牌异常

三、关键改进点

3.1 增强令牌声明可观测性

  • 记录令牌中包含的所有声明(如['user_id', 'exp', 'iat']),便于调试时确认令牌内容是否符合预期;
  • 当配置的USER_ID_CLAIM缺失时,自动尝试其他常见声明(如sub、uid),提高兼容性。
  • 用户不存在:单独捕获User.DoesNotExist异常,记录具体user_id并抛出明确错误;
  • 通用异常:捕获所有其他异常(如数据库连接失败),记录完整堆栈信息,避免认证流程静默失败。
  • DEBUG 级别:记录令牌声明、配置的用户ID字段等调试信息;
  • WARNING 级别:提示潜在的用户ID声明、用户不存在等边界情况;
  • ERROR 级别:记录认证失败的详细原因(含异常堆栈),便于快速定位代码问题。

3.2 细化异常处理逻辑

3.3 安全日志记录

四、使用示例

4.1 配置settings.py

Django 项目配置中指定用户模型和 SimpleJWT 参数:

# settings.py

AUTH_USER_MODEL = 'users.User' # 自定义用户模型路径

SIMPLE_JWT = {

'USER_ID_FIELD': 'id', # 用户模型的唯一标识字段(UUID 主键)

'USER_ID_CLAIM': 'user_id', # JWT 中存储用户ID的声明字段

'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30), # 令牌生命周期(可选)

}

REST_FRAMEWORK = {

'DEFAULT_AUTHENTICATION_CLASSES': [

'apps.users.auth.authentication.CustomJWTAuthentication', # 注册自定义认证类

],

}

4.2 在视图中使用认证类

# views.py

from rest_framework.views import APIView

from rest_framework.response import Response

from rest_framework.permissions import IsAuthenticated

from apps.users.auth.authentication import CustomJWTAuthentication

class SecureAPIView(APIView):

authentication_classes = [CustomJWTAuthentication] # 指定认证类

permission_classes = [IsAuthenticated] # 仅允许认证用户访问

def get(self, request):

# 已认证用户通过 request.user 访问

return Response({

"message": "认证成功",

"user_id": str(request.user.id),

"mobile": request.user.mobile

})

五、已知问题与修复建议

5.1validated_token.keys()的 IDE 警告

  • 问题现象IDE(如 PyCharm)提示Unresolved attribute reference "keys" for class 'Token';
  • 原因Token类的keys()方法通过字典接口动态实现,部分 IDE 无法正确识别;
  • 修复建议(可选):显式访问payload属性的keys()方法(更明确):token_payload = validated_token.payload # 获取令牌负载(字典类型)

token_claims = list(token_payload.keys()) # 从负载中获取声明

5.2 令牌声明兼容性优化

  • 若项目中使用的令牌生成方(如第三方认证服务)使用非标准声明(如uid),可扩展possible_claims列表:possible_claims = ['user_id', 'sub', 'uid', 'id', 'custom_user_id'] # 添加自定义声明

六、总结

CustomJWTAuthentication类通过增强令牌声明解析、细化异常处理和安全日志记录,完美适配无username字段的用户模型。通过本类,可在生产环境中快速排查认证问题(如令牌缺失用户ID、用户不存在),并确保认证流程的稳定性和可观测性。

 

posted on 2025-07-02 21:30  GoGrid  阅读(50)  评论(0)    收藏  举报

导航