RexUniNLU保姆级教程:server.py添加JWT鉴权与请求限流中间件实践
1. 为什么要在RexUniNLU服务中加鉴权和限流
你刚跑通python server.py,接口http://localhost:8000/nlu能正常返回意图和槽位结果——这很酷。但现实不是Demo:
- 如果把服务暴露到公网,谁都能调用,模型资源可能被刷爆;
- 如果上游系统没做节制,突发流量可能让服务直接卡死;
- 更关键的是,你没法知道这次请求是谁发的、来自哪个业务系统、有没有权限调用“金融风控”这类敏感schema。
RexUniNLU本身专注NLU能力,不内置安全机制。而生产环境里,一个没加防护的API,就像开着门的保险柜——功能再强也没用。
本教程不讲抽象概念,只做三件事:
给server.py加上JWT身份校验(验证“你是谁”)
加入请求频率限制(控制“你能调多少次”)
所有改动可直接复制粘贴,5分钟内生效,零依赖冲突
全程基于原项目结构,不改模型逻辑、不碰test.py、不新增配置文件——纯粹在server.py里“插针式增强”。
2. 准备工作:确认环境与依赖
2.1 检查当前环境是否满足基础要求
RexUniNLU默认依赖已包含fastapi和uvicorn,但JWT和限流需要两个轻量扩展库。先确认是否已安装:
pip list | grep -E "(jwt|slowapi)"如果无输出,执行安装(注意:无需升级现有fastapi版本):
pip install python-jose[cryptography] slowapi
python-jose[cryptography]:提供JWT编码/解码与签名验证能力,比PyJWT更严格,支持现代加密算法slowapi:专为FastAPI设计的限流库,规则灵活、性能高,且与FastAPI生命周期深度集成
2.2 创建密钥与配置变量(安全第一)
JWT需要密钥签名,限流需定义策略。我们在server.py顶部添加配置区,避免硬编码密钥:
# ====== 安全配置区(请务必修改!)====== import secrets # JWT密钥:生成一次后固定使用,不要每次启动都重生成 JWT_SECRET_KEY = secrets.token_urlsafe(32) # 临时密钥(仅用于本地测试) # 生产环境请替换为环境变量:os.getenv("JWT_SECRET_KEY", "your_production_secret") # JWT算法 JWT_ALGORITHM = "HS256" # 限流策略:每分钟最多10次请求(可根据业务调整) RATE_LIMIT_PER_MINUTE = 10 # ======================================重要提醒:
secrets.token_urlsafe(32)生成的是临时密钥,仅适用于本地调试。上线前必须改为从环境变量读取,并确保密钥长度≥32字节、含大小写字母+数字+符号。
3. 改造server.py:四步实现鉴权与限流
我们以RexUniNLU原始server.py为基础(假设它是一个标准FastAPI应用),逐步注入安全能力。所有代码均保持向后兼容——原有接口路径、参数、返回格式完全不变。
3.1 第一步:导入必要模块并初始化限流器
在server.py开头的导入区下方,添加以下内容:
# ====== 新增导入 ====== from fastapi import Depends, HTTPException, status, Request from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from jose import JWTError, jwt from slowapi import Limiter, _rate_limit_exceeded_handler from slowapi.util import get_remote_address from slowapi.errors import RateLimitExceeded # ===================== # ====== 初始化限流器 ====== limiter = Limiter(key_func=get_remote_address) # =========================
get_remote_address按客户端IP限流,适合内部服务;若需按用户Token限流,后续可替换为解析JWT中的user_id。
3.2 第二步:定义JWT认证方案与验证函数
在limiter初始化下方,添加认证逻辑:
# ====== JWT认证方案 ====== oauth2_scheme = HTTPBearer() def verify_jwt_token(credentials: HTTPAuthorizationCredentials = Depends(oauth2_scheme)): """ 验证JWT Token有效性 - 检查Header是否存在 - 解析Token并验证签名与过期时间 - 返回payload中的user_id(可用于后续权限控制) """ try: payload = jwt.decode( credentials.credentials, JWT_SECRET_KEY, algorithms=[JWT_ALGORITHM] ) user_id: str = payload.get("user_id") if user_id is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token: missing user_id" ) return user_id except JWTError: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or expired token" ) # ========================此函数返回
user_id字符串,后续可在接口中直接使用,例如记录日志或做细粒度权限判断。
3.3 第三步:为NLU接口添加限流与鉴权装饰器
找到原始server.py中定义/nlu接口的路由函数(通常类似@app.post("/nlu")),在其上方添加装饰器:
# ====== 原始NLU接口(示例,请根据实际代码位置修改)====== @app.post("/nlu") @limiter.limit(f"{RATE_LIMIT_PER_MINUTE}/minute") # 限流装饰器 async def nlu_endpoint( request: Request, # 必须显式声明Request参数,供limiter读取 text: str = Body(..., embed=True), labels: List[str] = Body(..., embed=True), user_id: str = Depends(verify_jwt_token) # 鉴权装饰器 ): # 原有业务逻辑(调用analyze_text等)保持不变 result = analyze_text(text, labels) return {"result": result} # =====================================================注意:
@limiter.limit()必须放在@app.post()下方,否则不生效;request: Request参数不可省略,slowapi需通过它获取客户端信息;user_id: str = Depends(verify_jwt_token)将自动触发JWT校验,校验失败直接返回401。
3.4 第四步:注册限流异常处理器与全局配置
在app = FastAPI(...)创建之后、if __name__ == "__main__":之前,添加以下代码:
# ====== 注册限流异常处理器 ====== app.state.limiter = limiter app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) # ============================== # ====== 可选:添加健康检查接口(验证鉴权是否生效)====== @app.get("/health") async def health_check(user_id: str = Depends(verify_jwt_token)): return {"status": "healthy", "authenticated_user": user_id} # ==========================================================
/health接口强制鉴权,可用于监控系统探测服务可用性与认证链路是否正常。
4. 实战测试:三步验证功能是否生效
完成代码改造后,启动服务并用curl快速验证:
4.1 启动增强版服务
# 确保在RexUniNLU根目录下 python server.py服务启动后,终端应显示:
INFO: Application startup complete. INFO: Uvicorn running on http://localhost:8000 (Press CTRL+C to quit)4.2 测试JWT鉴权(无Token → 401)
curl -X POST "http://localhost:8000/nlu" \ -H "Content-Type: application/json" \ -d '{"text":"订一张去北京的机票","labels":["出发地","目的地","订票意图"]}'预期响应(HTTP 401):
{"detail":"Not authenticated"}4.3 测试JWT鉴权(带有效Token → 200)
生成一个测试Token(Python一行命令):
python3 -c "from jose import jwt; print(jwt.encode({'user_id':'test_user'}, 'your_secret_key_here', algorithm='HS256'))"🔁 将
your_secret_key_here替换为你在server.py中设置的JWT_SECRET_KEY值(本地测试可用默认值)
用生成的Token调用:
curl -X POST "http://localhost:8000/nlu" \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \ -H "Content-Type: application/json" \ -d '{"text":"订一张去北京的机票","labels":["出发地","目的地","订票意图"]}'预期响应(HTTP 200,返回正常NLU结果):
{"result":{"intent":"订票意图","slots":[{"label":"出发地","value":"这里"},{"label":"目的地","value":"北京"}]}}4.4 测试请求限流(超频 → 429)
连续快速发送11次请求(使用seq和xargs):
seq 1 11 | xargs -I{} curl -s -o /dev/null -w "%{http_code}\n" -X POST "http://localhost:8000/nlu" \ -H "Authorization: Bearer your_test_token" \ -H "Content-Type: application/json" \ -d '{"text":"test","labels":["test"]}'预期输出:前10行为200,第11行为429(Too Many Requests)
5. 进阶技巧:让安全能力真正落地
以上是“能用”的最小可行方案。生产环境中,还需关注这些细节:
5.1 按用户维度限流(而非IP)
将limiter的key函数从get_remote_address改为解析JWT中的用户标识:
def get_user_id_from_token(request: Request): auth_header = request.headers.get("Authorization") if not auth_header or not auth_header.startswith("Bearer "): return "anonymous" token = auth_header.split(" ")[1] try: payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=[JWT_ALGORITHM]) return payload.get("user_id", "unknown") except JWTError: return "invalid_token" # 使用新key函数初始化limiter limiter = Limiter(key_func=get_user_id_from_token)此时
@limiter.limit("100/minute")即表示“每个用户每分钟最多100次”,精准匹配业务需求。
5.2 动态Schema权限控制(可选)
在nlu_endpoint中,根据user_id加载其允许访问的schema白名单:
# 示例:从数据库或配置文件读取用户权限 ALLOWED_SCHEMAS = { "finance_team": ["查询余额", "转账意图", "信用卡还款"], "iot_team": ["打开空调", "调节温度", "查询设备状态"] } if labels[0] not in ALLOWED_SCHEMAS.get(user_id, []): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=f"User {user_id} not authorized for schema: {labels[0]}" )5.3 日志审计(记录谁在何时调用了什么)
在nlu_endpoint中添加结构化日志:
import logging logger = logging.getLogger(__name__) # 在函数体开头添加 logger.info( "NLU_REQUEST", extra={ "user_id": user_id, "text_length": len(text), "label_count": len(labels), "client_ip": get_remote_address(request) } )结合ELK或Prometheus,可构建完整的API调用审计看板。
6. 总结:安全不是功能,而是交付底线
你刚刚完成的,不只是给server.py加了两段代码:
🔹你把一个Demo级工具,变成了可交付的生产服务——它现在能抵御暴力调用、识别真实用户、留下操作痕迹;
🔹你没有牺牲RexUniNLU的核心价值——零样本、轻量、易部署,所有NLU能力原封不动;
🔹你建立了一套可复用的安全模式——JWT校验+按需限流,这套组合拳可直接迁移到其他FastAPI服务。
最后提醒三个必须做的动作:
1⃣上线前:将JWT_SECRET_KEY从代码中移出,改用os.getenv("JWT_SECRET_KEY")读取;
2⃣压测时:用ab或k6验证限流阈值是否符合预期(如k6 run --vus 50 --duration 30s script.js);
3⃣监控中:在Prometheus中配置http_requests_total{code=~"4..|5.."}告警,第一时间发现认证或限流异常。
安全不是一劳永逸的补丁,而是持续迭代的习惯。而今天这5分钟的改造,已经让你站在了习惯的起点。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。