Azure AD公钥端点是否有调用限制?Python/FastAPI咨询
Azure AD公钥端点调用限制与密钥缓存优化方案
一、公钥端点调用限制说明
直接调用https://login.microsoftonline.com//discovery/keys?appid=端点不会轻易被限制或封禁:
- 微软对这类OpenID发现端点的默认速率限制远高于3000次/小时(通常单租户端点的限制是每分钟数百次,换算后每小时可达数万次),你的请求量完全在安全范围内。
- 但不建议每次请求都调用该端点,一是没必要浪费网络资源,二是微软官方明确推荐缓存密钥以提升性能和可靠性。
二、现有问题的优化方案
针对你遇到的密钥未通知变更导致异常的问题,结合Python/FastAPI技术栈,推荐以下几种方案:
1. 本地内存缓存+定时刷新(单实例或无状态多实例首选)
- 实现思路:
- 在FastAPI应用启动时,首次拉取密钥并缓存到本地内存(比如用
cachetools库的TTLCache设置过期时间)。 - 用定时任务(比如
APScheduler)每隔一段时间(比如1小时)主动拉取最新密钥,更新本地缓存。 - 每次请求验证JWT时,直接从本地缓存读取密钥,无需额外网络请求。
- 在FastAPI应用启动时,首次拉取密钥并缓存到本地内存(比如用
- 优势:性能最优,完全避免外部调用开销;密钥更新及时,能应对微软的密钥轮换。
- 代码示例片段:
from fastapi import FastAPI from cachetools import TTLCache import requests from apscheduler.schedulers.asyncio import AsyncIOScheduler app = FastAPI() # 缓存1小时,最多存10条密钥(足够应对Azure AD的密钥轮换) key_cache = TTLCache(maxsize=10, ttl=3600) APP_ID = "你的应用ID" KEY_ENDPOINT = f"https://login.microsoftonline.com//discovery/keys?appid={APP_ID}" def fetch_and_update_keys(): response = requests.get(KEY_ENDPOINT) response.raise_for_status() keys = response.json() key_cache.clear() for key in keys.get("keys", []): key_cache[key["kid"]] = key # 启动时初始化缓存 fetch_and_update_keys() # 启动定时任务,每小时更新一次 scheduler = AsyncIOScheduler() scheduler.add_job(fetch_and_update_keys, "interval", hours=1) scheduler.start() # 在JWT验证依赖中使用缓存的密钥 async def get_jwt_key(kid: str): return key_cache.get(kid)
2. 分布式缓存(多实例部署场景)
如果你的API是多实例部署,本地缓存无法同步更新,推荐用Redis做分布式缓存:
- 实现思路:
- 用定时任务拉取最新密钥,写入Redis并设置过期时间(比如1小时)。
- 每个API实例从Redis读取密钥,无需各自维护缓存。
- 优势:多实例间密钥状态一致,避免单个实例缓存过期不及时的问题;性能比S3更优,Redis是内存级读取。
3. S3缓存优化(若坚持使用S3)
如果一定要用S3存储密钥,可以做以下优化减少性能影响:
- 在API实例本地加一层短期内存缓存(比如5分钟),减少S3调用频率。
- 给S3中的密钥文件设置合适的
Cache-Control响应头(比如max-age=300),利用客户端缓存。 - 用S3的
ETag或Last-Modified头做条件请求,只在密钥更新时才下载新文件。
三、额外注意事项
- Azure AD的密钥轮换通常是提前发布新密钥,旧密钥会保留一段时间再失效,所以定时刷新间隔设置为1小时完全足够覆盖轮换周期。
- 验证JWT时,如果遇到缓存中没有对应
kid的密钥,可临时触发一次密钥拉取并更新缓存,避免直接返回错误。
内容的提问来源于stack exchange,提问作者Rodrigo Petrizzo
相关产品推荐
相关产品推荐

