基于子域名用不同Django密钥生成多租户JWT的实现疑问
多租户子域名场景下用django-rest-framework-simplejwt自定义密钥生成Token的解决方案
核心思路
根据请求的子域名匹配对应租户的密钥,在Token生成和验证流程中替换全局密钥,无需重新初始化API。
具体实现步骤
1. 编写租户密钥获取函数
从请求的子域名提取租户标识,读取对应环境文件中的密钥:
import os from django.conf import settings def get_tenant_secret(request): host = request.get_host() # 按实际域名结构提取子域名,示例为tenant1.local -> tenant1 subdomain = host.split('.')[0] env_file_path = os.path.join(settings.BASE_DIR, f'.{subdomain}.local.env') if os.path.exists(env_file_path): with open(env_file_path, 'r') as env_file: for line in env_file: stripped_line = line.strip() if stripped_line.startswith('DJANGO_SECRET_KEY='): return stripped_line.split('=', 1)[1] # fallback到全局密钥,避免密钥不存在导致服务异常 return settings.SECRET_KEY
2. 重写Token生成序列化器
替换Token生成时使用的密钥,覆盖TokenObtainPairSerializer的逻辑:
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer from rest_framework_simplejwt.tokens import RefreshToken class TenantTokenObtainPairSerializer(TokenObtainPairSerializer): @classmethod def get_token(cls, user): request = cls.context.get('request') tenant_secret = get_tenant_secret(request) # 生成RefreshToken并替换其加密密钥 refresh_token = RefreshToken.for_user(user) refresh_token.encoder.secret_key = tenant_secret # AccessToken会关联到RefreshToken,无需单独设置密钥 return refresh_token def validate(self, attrs): # 继承父类验证逻辑,可按需追加租户相关字段返回 data = super().validate(attrs) return data
3. 重写登录视图传递请求上下文
确保序列化器能获取到当前请求对象:
from rest_framework_simplejwt.views import TokenObtainPairView class TenantTokenObtainPairView(TokenObtainPairView): serializer_class = TenantTokenObtainPairSerializer def get_serializer_context(self): context = super().get_serializer_context() # 将request传入序列化器上下文 context['request'] = self.request return context
4. 配置URL路由
将自定义视图替换默认的Token获取路由:
from django.urls import path from .views import TenantTokenObtainPairView urlpatterns = [ path('api/token/', TenantTokenObtainPairView.as_view(), name='token_obtain_pair'), # 其他路由... ]
5. (可选)重写Token验证逻辑
如果认证服务器需要验证租户提交的Token,需替换验证时的密钥:
from rest_framework_simplejwt.authentication import JWTAuthentication from rest_framework_simplejwt.tokens import Token from rest_framework_simplejwt.exceptions import InvalidToken class TenantJWTAuthentication(JWTAuthentication): def get_validated_token(self, raw_token): tenant_secret = get_tenant_secret(self.request) try: token = Token(raw_token) # 使用租户密钥验证Token签名 token.verify(secret=tenant_secret) return token except Exception as e: raise InvalidToken(str(e))
在settings.py中配置自定义认证类:
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'your_app.path.to.TenantJWTAuthentication', ], }
注意事项
- 子域名提取逻辑需根据实际域名结构调整,避免解析错误。
- 建议缓存租户密钥(如使用Django缓存框架),减少文件IO开销。
- 确保租户环境文件的权限安全,避免密钥泄露。
内容的提问来源于stack exchange,提问作者Evren Bingøl
相关产品推荐
相关产品推荐

