Django中安全存储全局可用JWT令牌的技术方案问询
我之前刚好处理过几乎一模一样的场景,给你一套落地性很强的方案,完全匹配你的需求:
核心方案:基于缓存的集中式令牌管理
这个思路的核心是把外部API颁发的JWT令牌集中存储在Django的缓存系统中,所有客户端请求复用同一个令牌,同时利用缓存的过期机制自动处理令牌失效,完美解决你提到的三个核心问题:复用令牌、动态更新、安全存储。
为什么选缓存?
- 天然支持过期时间:刚好对应24小时的令牌有效期,到期自动失效,无需手动清理
- 高性能读取:多客户端请求时能快速获取令牌,不会成为性能瓶颈
- 动态可修改:随时可以清空或更新缓存中的令牌,完全不用改动配置文件
- 易于扩展:生产环境可以用Redis这类分布式缓存,支持多实例部署的Django应用共享令牌
具体实现步骤
1. 配置缓存后端(生产环境推荐Redis)
首先在Django的settings.py中配置缓存,这里以Redis为例(性能和安全性都更优):
CACHES = { 'default': { 'BACKEND': 'django_redis.cache.RedisCache', 'LOCATION': 'redis://your-redis-host:6379/1', # 选择指定数据库 'OPTIONS': { 'CLIENT_CLASS': 'django_redis.client.DefaultClient', 'PASSWORD': 'your-strong-redis-password', # 必须设置密码! 'SSL': True, # 如果是云Redis服务,开启SSL加密传输 } } }
2. 编写令牌管理工具函数
写一个封装好的函数,负责令牌的获取、缓存和自动刷新逻辑,所有调用外部API的地方都通过这个函数获取令牌:
import time import requests from django.core.cache import cache from django.core import signing # 配置参数(建议存在环境变量里,更安全) EXTERNAL_API_TOKEN_URL = "https://external-api.com/oauth/token" EXTERNAL_API_CLIENT_ID = "your-client-id" EXTERNAL_API_CLIENT_SECRET = "your-client-secret" CACHE_KEY = "external_api_jwt_token" TOKEN_EXPIRY = 86400 # 24小时,单位秒 LOCK_TIMEOUT = 10 # 并发锁的超时时间,单位秒 def get_external_api_token(): # 先尝试从缓存获取加密后的令牌 encrypted_token = cache.get(CACHE_KEY) if encrypted_token: # 解密令牌 return signing.loads(encrypted_token) # 加锁防止并发请求外部API lock_key = f"{CACHE_KEY}_lock" if cache.add(lock_key, "locked", timeout=LOCK_TIMEOUT): try: # 再次检查缓存(防止等待锁期间已有其他请求获取令牌) encrypted_token = cache.get(CACHE_KEY) if encrypted_token: return signing.loads(encrypted_token) # 向外部API请求新令牌 response = requests.post( EXTERNAL_API_TOKEN_URL, data={ "client_id": EXTERNAL_API_CLIENT_ID, "client_secret": EXTERNAL_API_CLIENT_SECRET, "grant_type": "client_credentials" # 根据外部API的授权类型调整 } ) response.raise_for_status() # 处理请求失败的情况 token_data = response.json() token = token_data["access_token"] # 加密后存入缓存,设置过期时间 encrypted_token = signing.dumps(token) cache.set(CACHE_KEY, encrypted_token, timeout=TOKEN_EXPIRY) return token finally: # 释放锁 cache.delete(lock_key) else: # 等待片刻后重试,避免并发冲突 time.sleep(0.1) return get_external_api_token()
3. 封装外部API调用逻辑
在调用外部API的地方,统一使用上面的函数获取令牌,同时处理令牌提前失效的情况(比如外部API主动吊销):
def call_external_api(endpoint, method="GET", data=None, params=None): token = get_external_api_token() headers = {"Authorization": f"Bearer {token}"} # 发起请求 response = requests.request( method=method, url=f"https://external-api.com{endpoint}", headers=headers, json=data, params=params ) # 处理令牌失效的情况(返回401 Unauthorized) if response.status_code == 401: # 清空缓存中的无效令牌 cache.delete(CACHE_KEY) # 重新获取令牌并重试请求 token = get_external_api_token() headers["Authorization"] = f"Bearer {token}" response = requests.request( method=method, url=f"https://external-api.com{endpoint}", headers=headers, json=data, params=params ) response.raise_for_status() return response.json()
安全增强措施
- 令牌加密存储:上面的代码用了Django的
signing模块对令牌加密,即使缓存被攻破,拿到的也是加密后的内容,需要Django的SECRET_KEY才能解密 - 缓存服务安全:Redis设置强密码、限制访问IP、开启SSL传输,避免缓存数据泄露
- 敏感信息隔离:把外部API的
client_id、client_secret存在环境变量中,不要硬编码在代码里(可以用os.getenv()获取) - 日志监控:添加日志记录令牌的获取、刷新和失效情况,方便排查问题
满足你的所有需求验证
- 多客户端复用令牌:不管多少客户端请求,只要缓存中有有效令牌,都会复用,不会重复请求外部API
- 动态修改令牌:如果需要手动刷新令牌,只需要执行
cache.delete("external_api_jwt_token"),下次请求就会自动获取新令牌,完全不用改配置 - 安全存储:令牌加密后存在带密码保护的Redis中,避免明文泄露
内容的提问来源于stack exchange,提问作者NoahL
相关产品推荐
相关产品推荐

