eagleye

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. 集成审计和密钥轮换,符合企业安全标准。

建议结合业务场景调整有效期、受众和安全策略,定期审查配置并进行渗透测试,确保

 

posted on 2025-06-30 15:23  GoGrid  阅读(74)  评论(0)    收藏  举报

导航