Django REST Framework Simple JWT 企业级安全配置
Django REST Framework Simple JWT 企业级安全配置文档
一、概述
在前后端分离的企业级系统中,JWT(JSON Web Token)是实现身份认证的核心技术。Django REST Framework 的djangorestframework-simplejwt库提供了便捷的JWT支持,但默认配置可能存在安全隐患(如受众验证缺失、令牌管理松散)。本文针对Invalid audience错误及企业级安全需求,提供完整的配置修正方案、自定义扩展方法及最佳实践指南。
二、核心配置修正与安全增强
2.1 基础配置模板(解决AUDIENCE验证问题)
以下是修正后的SIMPLE_JWT配置,重点解决受众(aud)验证问题,并增强安全特性:
# settings.py
from datetime import timedelta
SIMPLE_JWT = {
# 加密算法配置
'ALGORITHM': 'HS256', # 推荐HS256/HS512对称加密(非敏感场景)或RS256非对称加密(跨服务场景)
'SIGNING_KEY': os.getenv('JWT_SECRET_KEY'), # 从环境变量获取密钥(64+字符随机字符串)
'VERIFYING_KEY': None, # 对称加密时无需公钥,非对称加密时需配置公钥
# 认证头配置(防混淆)
'AUTH_HEADER_TYPES': ('Bearer',), # 仅保留标准Bearer类型,避免自定义类型(如"JWT")
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION', # 请求头字段名(默认)
# 令牌有效期(根据业务调整)
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60), # 访问令牌有效期(推荐5-15分钟,企业敏感系统建议更短)
'REFRESH_TOKEN_LIFETIME': timedelta(days=1), # 刷新令牌有效期(推荐1-7天)
'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5), # 滑动令牌自动刷新周期(可选)
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1), # 滑动令牌最大有效期(可选)
# 令牌管理(防重放攻击)
'ROTATE_REFRESH_TOKENS': True, # 刷新时生成新令牌(★关键安全设置)
'BLACKLIST_AFTER_ROTATION': True, # 旧令牌加入黑名单(需安装`djangorestframework-simplejwt-token-blacklist`)
'BLACKLIST_TOKEN_CHECKS': [
'rest_framework_simplejwt.token_blacklist.blacklist.check_blacklisted' # 黑名单检查函数
],
# 用户信息声明
'UPDATE_LAST_LOGIN': True, # 登录时更新用户最后登录时间(审计需求)
'USER_ID_FIELD': 'id', # 用户模型主键字段名(默认)
'USER_ID_CLAIM': 'user_id', # JWT中用户ID的声明名(默认)
# 令牌类型声明(防令牌类型混淆)
'TOKEN_TYPE_CLAIM': 'token_type', # 声明名(默认)
'JTI_CLAIM': 'jti', # 令牌唯一ID声明名(防重放攻击)
# 安全声明(解决Invalid audience错误)
'ISSUER': 'safe-sentry-auth-service', # 签发者(企业服务标识,如"your-company-auth-service")
'AUDIENCE': 'web-app', # 受众(★关键修正:单一字符串值,如"web-app"或"mobile-app")
# 高级安全配置
'LEEWAY': 0, # 时间校验宽限(推荐0,避免令牌超期后仍可使用)
'AUTH_COOKIE': None, # 禁止使用Cookie存储令牌(防XSS攻击,推荐使用HTTP头)
# 强制关键声明(防无效令牌)
'REQUIRED_CLAIMS': ['exp', 'iat', 'jti', 'aud', 'iss'], # 令牌必须包含的声明
# 令牌用户类与认证规则(默认即可,可自定义扩展)
'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',
'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
# 防刷新令牌滥用(企业级限制)
'MAX_REFRESH_TOKENS': 3, # 用户最多同时有效的刷新令牌数(如3个设备登录)
}
三、关键修正点解析
3.1 受众(AUDIENCE)配置修正
问题背景:
原配置可能错误地将AUDIENCE设置为数组(如['web-app', 'mobile-app']),但djangorestframework-simplejwt默认仅支持单一字符串值,导致Invalid audience错误。
修正方案:
- 单一受众:直接配置'AUDIENCE': 'web-app'(推荐,满足大多数场景)。
- 多受众支持:若需支持多受众(如同时服务Web和移动端),需自定义令牌生成和验证逻辑(见3.2节)。
3.2 安全增强配置
|
配置项 |
作用 |
推荐值 |
|
ROTATE_REFRESH_TOKENS |
刷新时生成新令牌,旧令牌失效,防止旧令牌被滥用 |
True |
|
BLACKLIST_AFTER_ROTATION |
旧令牌加入黑名单,需安装djangorestframework-simplejwt-token-blacklist库 |
True |
|
REQUIRED_CLAIMS |
强制令牌包含关键声明(如exp过期时间、aud受众),防止无效令牌 |
['exp', 'iat', 'jti', 'aud', 'iss'] |
|
MAX_REFRESH_TOKENS |
限制用户同时有效的刷新令牌数(如3个设备),防止令牌批量泄露 |
3(按需调整) |
3.3 认证头优化
- 仅保留Bearer类型:移除自定义认证头类型(如JWT),避免与标准混淆。
- 使用HTTP头存储令牌:禁用AUTH_COOKIE,防止XSS攻击窃取Cookie中的令牌。
四、自定义扩展(多受众支持)
若业务需要支持多受众(如同时服务Web和移动端),需自定义令牌生成和验证逻辑。
4.1 自定义令牌生成器(支持多受众)
# authentication.py
from rest_framework_simplejwt.tokens import RefreshToken
from django.conf import settings
class MultiAudienceRefreshToken(RefreshToken):
@classmethod
def for_user(cls, user, audience=None):
"""
为用户生成带指定受众的刷新令牌
:param user: Django用户对象
:param audience: 受众(如'web-app'或'mobile-app')
"""
token = super().for_user(user)
# 设置受众声明(aud)
if audience:
token['aud'] = audience
elif hasattr(settings, 'SIMPLE_JWT_AUDIENCE'):
token['aud'] = settings.SIMPLE_JWT_AUDIENCE # 从配置获取默认受众
return token
# 使用示例(登录视图)
from rest_framework.decorators import api_view
from rest_framework.response import Response
@api_view(['POST'])
def custom_token_obtain_pair(request):
# 假设已通过用户名密码验证用户
user = authenticate(request)
# 生成带受众的刷新令牌(如移动端传'mobile-app')
audience = request.data.get('audience', 'web-app')
refresh = MultiAudienceRefreshToken.for_user(user, audience=audience)
return Response({
'refresh': str(refresh),
'access': str(refresh.access_token), # 访问令牌自动继承刷新令牌的aud
})
4.2 自定义认证类(验证多受众)
# authentication.py
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework_simplejwt.exceptions import AuthenticationFailed
from django.conf import settings
class MultiAudienceJWTAuthentication(JWTAuthentication):
def get_validated_token(self, raw_token):
"""
重写验证逻辑,支持多受众检查
"""
token = super().get_validated_token(raw_token) # 先验证签名和过期时间
# 获取配置的有效受众列表(如['web-app', 'mobile-app'])
valid_audiences = getattr(settings, 'SIMPLE_JWT_AUDIENCES', ['web-app'])
# 检查令牌的aud声明
token_aud = token.get('aud')
if not token_aud:
raise AuthenticationFailed('令牌缺少受众声明(aud)')
# 支持字符串或数组格式的aud声明
if isinstance(token_aud, str):
if token_aud not in valid_audiences:
raise AuthenticationFailed(f'无效受众:{token_aud}(有效受众:{valid_audiences})')
elif isinstance(token_aud, list):
if not any(aud in valid_audiences for aud in token_aud):
raise AuthenticationFailed(f'无匹配受众(有效受众:{valid_audiences})')
else:
raise AuthenticationFailed('受众声明(aud)格式无效(需为字符串或数组)')
return token
# 配置认证类(settings.py)
SIMPLE_JWT = {
# ...其他配置...
'AUTHENTICATION_CLASSES': ('your_app.authentication.MultiAudienceJWTAuthentication',)
}
五、企业级最佳实践
5.1 环境特定配置(区分开发/生产)
根据部署环境动态设置受众和密钥,避免敏感信息泄露:
# settings.py
AUDIENCE_MAP = {
'development': 'dev-web-app', # 开发环境
'staging': 'staging-web-app', # 预发布环境
'production': 'prod-web-app', # 生产环境
}
SIMPLE_JWT = {
'AUDIENCE': AUDIENCE_MAP[os.getenv('DJANGO_ENV', 'development')], # 从环境变量获取当前环境
'SIGNING_KEY': os.getenv('JWT_SECRET_KEY'), # 生产环境使用强密钥
# ...其他配置...
}
5.2 安全审计日志
记录令牌生成、刷新和失效事件,满足合规审计需求:
# audit.py(自定义审计处理器)
import logging
from django.utils import timezone
def log_jwt_event(event_type, token, user=None):
"""
记录JWT相关安全事件
:param event_type: 事件类型(如'TOKEN_ISSUED'/'TOKEN_REFRESHED'/'TOKEN_EXPIRED')
:param token: 令牌对象
:param user: 用户对象(可选)
"""
log_entry = {
'timestamp': timezone.now().isoformat(),
'event_type': event_type,
'token_jti': token['jti'],
'token_aud': token.get('aud'),
'user_id': user.id if user else None,
'ip_address': get_client_ip(request), # 从请求获取客户端IP(需自定义)
}
logging.getLogger('jwt_audit').info(log_entry)
# settings.py(集成审计)
SIMPLE_JWT = {
# ...其他配置...
'TOKEN_AUDIT_LOG': True, # 启用审计
'AUDIT_LOG_HANDLER': 'your_app.audit.log_jwt_event', # 自定义审计函数
}
5.3 密钥轮换策略
定期轮换密钥,防止密钥泄露导致的安全风险:
# settings.py(支持新旧密钥同时有效)
def get_current_key():
return os.getenv('JWT_CURRENT_KEY')
def get_previous_key():
return os.getenv('JWT_PREVIOUS_KEY') # 上一轮密钥(保留30天)
SIMPLE_JWT = {
'SIGNING_KEY': get_current_key(), # 当前签名密钥
'VERIFYING_KEY': [get_current_key(), get_previous_key()], # 验证时支持新旧密钥
'KEY_ROTATION_PERIOD': timedelta(days=30), # 每30天轮换一次
# ...其他配置...
}
5.4 敏感操作增强(短有效期令牌)
对敏感操作(如支付、删除账户)使用短有效期令牌,降低泄露风险:
# tokens.py(自定义敏感操作令牌)
from rest_framework_simplejwt.tokens import AccessToken
from datetime import timedelta
class CriticalActionToken(AccessToken):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_exp(lifetime=timedelta(minutes=5)) # 仅5分钟有效期
self['critical'] = True # 标记为敏感操作令牌
# 使用示例(敏感操作接口)
from rest_framework.decorators import api_view, authentication_classes
@api_view(['POST'])
@authentication_classes([MultiAudienceJWTAuthentication])
def delete_account(request):
# 验证令牌是否为敏感操作类型
if not request.auth.get('critical'):
raise PermissionDenied('需要敏感操作令牌')
# 执行删除逻辑...
六、配置验证与测试
6.1 生成令牌后解码检查
通过代码验证令牌是否包含正确的声明:
# 测试脚本(验证AUDIENCE和ISSUER)
from rest_framework_simplejwt.tokens import AccessToken
from django.conf import settings
def test_token_claims():
token = AccessToken()
token['aud'] = settings.SIMPLE_JWT['AUDIENCE']
token['iss'] = settings.SIMPLE_JWT['ISSUER']
# 解码验证
decoded = jwt.decode(
str(token),
settings.SIMPLE_JWT['SIGNING_KEY'],
audience=settings.SIMPLE_JWT['AUDIENCE'],
issuer=settings.SIMPLE_JWT['ISSUER'],
algorithms=[settings.SIMPLE_JWT['ALGORITHM']]
)
assert decoded['aud'] == settings.SIMPLE_JWT['AUDIENCE'], 'AUDIENCE验证失败'
assert decoded['iss'] == settings.SIMPLE_JWT['ISSUER'], 'ISSUER验证失败'
print('令牌声明验证通过')
test_token_claims()
6.2 使用Apipost测试受众
在API测试工具(如Apipost)中添加脚本验证令牌受众:
// 在登录请求的Tests标签中添加
pm.test("令牌包含正确受众(aud)", function() {
const response = pm.response.json();
const accessToken = response.access;
const payload = JSON.parse(atob(accessToken.split('.')[1])); // 解码JWT负载
// 从环境变量获取预期受众(如'web-app')
const expectedAudience = pm.environment.get("JWT_AUDIENCE");
pm.expect(payload.aud).to.equal(expectedAudience, `受众应为${expectedAudience},实际为${payload.aud}`);
});
七、安全配置检查清单
|
检查项 |
推荐值/要求 |
验证方法 |
|
令牌类型(AUTH_HEADER_TYPES) |
仅保留Bearer |
检查请求头Authorization |
|
受众声明(AUDIENCE) |
非空字符串(如'web-app') |
解码令牌查看aud字段 |
|
签发者声明(ISSUER) |
非空字符串(如'company-auth') |
解码令牌查看iss字段 |
|
刷新令牌轮换(ROTATE_REFRESH_TOKENS) |
True |
检查SIMPLE_JWT配置 |
|
黑名单启用(BLACKLIST_AFTER_ROTATION) |
True |
检查SIMPLE_JWT配置 |
|
访问令牌最短有效期 |
5-15分钟(敏感系统更短) |
检查ACCESS_TOKEN_LIFETIME |
|
加密算法(ALGORITHM) |
HS256/HS512(对称)或RS256(非对称) |
检查ALGORITHM配置 |
|
密钥强度(SIGNING_KEY) |
64+字符随机字符串 |
检查环境变量JWT_SECRET_KEY长度 |
八、总结
通过本文的配置修正和安全增强,您的JWT系统将:
1. 解决Invalid audience错误,确保受众验证合规;
2. 增强令牌管理(轮换、黑名单),防止重放攻击;
3. 支持多受众扩展,满足复杂业务需求;
4. 集成审计和密钥轮换,符合企业安全标准。
建议结合业务场景调整有效期、受众和安全策略,定期审查配置并进行渗透测试,确保
浙公网安备 33010602011771号