拓冰建站拓冰建站
首页 / 资讯中心 / 正文

AI网关模型身份校验实战:从零实现HMAC令牌验证

最近在做 AI 网关相关的东西时踩过一个不算明显但影响很大的坑网关配置上游模型时写的是“模型 A”实际请求转发过去后返回的模型上下文、计费信息和审计记录都和预期对不上。排查了很久才意识到问题出在“模型身份”没有被校验。这类问题在 AI 网关场景里越来越多。像 XTokenChecker 这样一个面向 AI 网关模型身份校验的工具思路刚好可以作为我们自研校验模块的设计参考。本文会从模型身份是什么、为什么容易出问题讲起再用一套可直接运行的 Python 示例从零实现一个轻量的模型身份检查层覆盖响应校验、模型名比对、身份令牌签发与验签等关键环节。适合正在做 LLM 网关、企业内部 AI 平台、或者是想给现有网关补一层安全审计的同学。全文偏工程落地代码都可以直接复制改着用。1. 背景与核心概念1.1 什么是 AI 网关中的模型身份在传统微服务架构里网关负责路由、鉴权和流量控制。到了 AI 网关阶段事情又多了一层网关需要根据用户请求中的模型名把请求转发到不同的模型供应商或者在多个同名模型中做负载均衡。这里的“模型身份”指的是一个响应或者一次模型调用到底来自哪个模型。它不能只理解为“模型名字”而应该是一组可验证的属性至少包括模型供应商是谁上游模型标识是什么实际调用的模型版本是什么请求方申请的模型别名是什么校验发生时的时间戳、请求 ID 等上下文。例如用户请求写的是gpt-4o-mini网关经过别名映射后实际转发的上游模型可能是gpt-4o-mini-2024-07-18。那么在这次调用中模型身份就不只是gpt-4o-mini而是包含了上游版本序列号的完整信息。1.2 模型身份与模型名称的区别很多同学会把“模型身份”等同于“响应里的 model 字段”。这其实是一个容易踩坑的地方。响应体中的model字段有一个典型问题它是由模型服务方返回的本身不携带任何密码学证明。也就是说只要控制返回内容的服务端愿意它完全可以把gpt-4o-mini的返回内容标记成gpt-4o的模型名或者反过来。此外网关层也可能因为缓存、负载均衡、配置漂移等原因把请求转发到错误的模型上。这个时候如果只依赖响应体中的 model 字符串做统计或审计结果可能就是错的。所以我们要讨论的模型身份校验重点并不只是“字符串是否相等”而是“响应中的模型标识是否与网关预期转发的模型标识一致并且这个过程有可验证的证据”。1.3 不校验模型身份会带来什么问题在真实项目中缺少模型身份校验通常会引发几类问题成本核算错误不同模型单价不同。如果网关把贵价模型的调用错误记录成便宜模型成本大盘就会失真。审计不合规企业内外部审计往往要求每条调用记录可追溯。模型身份无法置信审计证据链就断了。A/B 实验判断错误有些团队会同时接入多个模型做效果对比。模型身份错了实验结论就可能完全反转。模型逃逸与管理风险上游配置被篡改或者网关路由配置与预期不一致时调用可能被悄悄路由到其他模型甚至未经授权的模型。这些问题不是靠“等出事了再查日志”就能解决的更重要的是在入口链路加一道校验动作。XTokenChecker 这个名字里有两个关键词Token 和 Checker本质上就是通过 Token 这一类可验证对象来检查 AI 网关中模型身份是否真实可信。2. XTokenChecker 的设计思路2.1 身份校验的本质模型身份校验的核心可以拆成三个问题身份信息从哪来身份信息如何传递身份信息如何证明。第一个问题决定我们校验什么。通常情况下身份信息来自模型服务的响应元数据、网关自身的路由配置、上游模型注册表。第二个问题决定我们如何把这些信息编排到一起。第三个问题则依赖签名、HMAC、甚至上游模型服务返回的可验证元数据。XTokenChecker 这类工具的通用做法并不是去修改模型供应商的接口协议而是在网关与模型服务之间增加一个“校验层”在请求转发完成后、响应返回给调用方之前完成对模型身份信息的提取、核对、签名与审计。2.2 三层校验按防护强度从低到高可以分为三层第一层字段级校验。检查响应中是否存在 model 字段字段非空且字段是合法的字符串。第二层配置级校验。把响应中的模型标识与网关当前生效的模型白名单做比对确认本次响应中的模型确实属于预期范围。第三层令牌级校验。网关校验完模型身份后生成一个带签名的身份令牌随响应返回给调用方。调用方或审计服务可以通过验签确认该令牌确实由当前网关签发且未被篡改。三层校验不是互斥的生产环境建议两层或三层叠加使用。2.3 校验流程我们可以把一次请求的模型身份校验流程拆成以下几个步骤调用方通过 AI 网关发起模型调用请求。网关根据请求中的模型别名查询模型映射配置。网关将请求转发给上游模型服务。模型服务返回响应响应中携带模型标识和生成结果。校验模块从响应中提取模型标识。校验模块对比网关配置中的允许模型列表。校验通过后生成模型身份令牌并随响应返回。校验失败时网关按策略阻断或降级并记录审计日志。3. 环境准备与项目结构3.1 运行环境本文示例使用 Python 3.10核心依赖如下fastapi0.115.6 uvicorn0.34.0 httpx0.28.1 pydantic2.10.4版本可以根据你的项目实际情况调整本文以常见环境为例重点演示配置思路。除了 Python 环境你还需要准备一个可访问的 AI 网关或模型服务接口用于模拟上游响应如果没有实际模型服务可以使用 Mock 协议本地模拟响应。3.2 项目结构建议按下面的目录结构组织示例工程xtokenchecker-demo/ ├── app.py # FastAPI 入口模拟网关入口 ├── verifier/ │ ├── __init__.py │ ├── identity.py # 模型身份对象 │ ├── checker.py # 模型名校验逻辑 │ ├── token.py # HMAC 身份令牌 │ └── models.py # Pydantic 模型 ├── config.yaml # 模型映射配置 ├── requirements.txt └── tests/ └── test_checker.py下面我们按文件逐个实现。4. 核心实现从配置到校验4.1 定义模型身份对象先创建verifier/identity.py用 dataclass 定义模型身份。# 文件路径verifier/identity.py from dataclasses import dataclass from datetime import datetime, timezone from typing import Optional dataclass class ModelIdentity: requested_model: str # 调用方请求的模型名 model: str # 响应中实际返回的模型名 provider: str # 模型供应商标识 version: Optional[str] # 模型版本 checked_at: str # 校验时间 request_id: str # 网关请求 ID def __post_init__(self): if not self.checked_at: self.checked_at datetime.now(timezone.utc).isoformat() def to_dict(self) - dict: return { requested_model: self.requested_model, model: self.model, provider: self.provider, version: self.version, checked_at: self.checked_at, request_id: self.request_id, }这里把requested_model和model分开是因为网关层存在模型别名映射。例如业务侧请求fast-chat但上游实际模型是gpt-4o-mini。两个字段分别记录“业务想要什么”和“实际得到什么”对后续审计会清晰很多。4.2 定义接口请求与响应模型创建verifier/models.py用于定义网关校验接口的请求与响应结构。# 文件路径verifier/models.py from typing import Optional from pydantic import BaseModel, Field class UpstreamResponse(BaseModel): id: str model: str object: str choices: list Field(default_factorylist) class CheckResult(BaseModel): passed: bool reason: str identity: Optional[dict] None token: Optional[str] None在实际网关场景中响应体结构可能远比这个复杂但校验层关心的字段其实很少id、model、choices。所以这里只摘出关键字段避免无关字段影响校验逻辑。4.3 模型名校验逻辑创建verifier/checker.py实现配置级校验。# 文件路径verifier/checker.py from .identity import ModelIdentity class ModelIdentityError(Exception): 模型身份校验失败时抛出 class ModelIdentityChecker: def __init__(self, allowed_models: list[str], provider: str): self.allowed_models set(allowed_models) self.provider provider def check_model_field(self, response_model: str) - bool: 第一层字段级校验 if not response_model: return False if not isinstance(response_model, str): return False return True def check_allowed_model(self, response_model: str) - bool: 第二层配置级校验 if not self.check_model_field(response_model): return False return response_model in self.allowed_models def verify(self, response_model: str, requested_model: str, request_id: str) - ModelIdentity: if not self.check_model_field(response_model): raise ModelIdentityError(model field is empty or invalid) if not self.check_allowed_model(response_model): raise ModelIdentityError( fmodel {response_model} is not in allowed models: {sorted(self.allowed_models)} ) return ModelIdentity( requested_modelrequested_model, modelresponse_model, providerself.provider, request_idrequest_id, )这里有两个容易出错的地方allowed_models如果转成 list 再判断性能在大流量下不好应转成 set比较模型名时不要做模糊匹配比如gpt-4o不能匹配gpt-4o-mini否则白名单就失去了意义。4.4 基于 HMAC 的身份令牌有了模型身份对象后还需要一种方式让调用方或审计服务验证“这个身份是网关生成且没有被篡改的”。这里采用 HMAC 签名方式实现verifier/token.py。# 文件路径verifier/token.py import base64 import hashlib import hmac import json class TokenVerificationError(Exception): 身份令牌校验失败 class IdentityTokenManager: def __init__(self, secret_key: str): self.secret_key secret_key def build_token(self, identity: dict) - str: 将身份信息签名后生成 token payload_bytes json.dumps( identity, sort_keysTrue, separators(,, :) ).encode(utf-8) payload_b64 base64.urlsafe_b64encode(payload_bytes).decode(ascii) signature hmac.new( self.secret_key.encode(utf-8), payload_bytes, hashlib.sha256, ).hexdigest() return f{payload_b64}.{signature} def verify_token(self, token: str) - dict: 校验 token返回原始身份信息 try: payload_b64, signature token.rsplit(., 1) payload_bytes base64.urlsafe_b64decode(payload_b64.encode(ascii)) expected hmac.new( self.secret_key.encode(utf-8), payload_bytes, hashlib.sha256, ).hexdigest() if not hmac.compare_digest(expected, signature): raise TokenVerificationError(signature mismatch) return json.loads(payload_bytes) except (ValueError, TypeError) as exc: raise TokenVerificationError(invalid token format) from exc需要注意HMAC 的比较必须使用hmac.compare_digest不能直接比较两个字符串。这样可以在一定程度上避免时间侧信道攻击。5. 完整接入示例上面的模块已经覆盖了模型身份的核心能力。下面通过一个 FastAPI 应用把这些模块串起来演示一次完整的模型身份校验。5.1 依赖清单先创建requirements.txtfastapi0.115.6 uvicorn0.34.0 httpx0.28.1 pydantic2.10.45.2 模型映射配置创建config.yaml这里的配置是示例思路请按实际网关的上游信息调整gateway: provider: openai-demo allowed_models: - gpt-4o-mini - gpt-4o token_secret: please-change-this-secret enable_token: true其中token_secret在真实环境中不能出现在配置文件里应通过环境变量或密钥管理服务注入。5.3 网关校验入口创建app.py模拟网关收到上游响应后先进行模型身份校验再返回给调用方。# 文件路径app.py import os from fastapi import FastAPI, HTTPException from pydantic import BaseModel from verifier.checker import ModelIdentityChecker, ModelIdentityError from verifier.models import UpstreamResponse, CheckResult from verifier.token import IdentityTokenManager app FastAPI(titleXTokenChecker Demo) ALLOWED_MODELS [gpt-4o-mini, gpt-4o] PROVIDER openai-demo TOKEN_SECRET os.environ.get(TOKEN_SECRET, dev-only-secret) checker ModelIdentityChecker(allowed_modelsALLOWED_MODELS, providerPROVIDER) token_manager IdentityTokenManager(secret_keyTOKEN_SECRET) ENABLE_TOKEN True class CheckRequest(BaseModel): request_id: str requested_model: str upstream_response: UpstreamResponse app.post(/check, response_modelCheckResult) def check_model_identity(req: CheckRequest): try: identity checker.verify( response_modelreq.upstream_response.model, requested_modelreq.requested_model, request_idreq.request_id, ) except ModelIdentityError as exc: raise HTTPException(status_code400, detailstr(exc)) from exc result CheckResult( passedTrue, reasonmodel identity verified, identityidentity.to_dict(), ) if ENABLE_TOKEN: result.token token_manager.build_token(identity.to_dict()) return result这段代码通过一个/check端点接收模拟的上游响应和调用方请求信息然后执行字段级校验配置级校验通过后生成模型身份对象按配置决定是否签发身份令牌。5.4 运行与验证在项目根目录启动服务pip install -r requirements.txt uvicorn app:app --reload --port 8000接着用 curl 发送一个模拟请求curl -X POST http://127.0.0.1:8000/check \ -H Content-Type: application/json \ -d { request_id: req-001, requested_model: gpt-4o-mini, upstream_response: { id: chatcmpl-abc123, model: gpt-4o-mini, object: chat.completion, choices: [ { index: 0, message: {role: assistant, content: hello} } ] } }正常情况下预期返回大致如下{ passed: true, reason: model identity verified, identity: { requested_model: gpt-4o-mini, model: gpt-4o-mini, provider: openai-demo, version: , checked_at: 2025-01-01T12:00:00.12345600:00, request_id: req-001 }, token: eyJtb2RlbCI6ICJncHQt...abc.signature }如果响应中的模型名是gpt-4o-ornot不在允许列表中接口会返回类似下面的错误{ detail: model gpt-4o-ornot is not in allowed models: [gpt-4o, gpt-4o-mini] }这就是最基础的模型身份拦截能力。6. 常见问题与排查思路在实际接入过程中模型身份校验会遇到不少问题。下面按高频问题整理了一份排查表。问题现象常见原因解决思路model 字段为空上游服务返回结构不兼容检查上游响应字段大小写与嵌套结构模型名与白名单不一致网关别名映射配置错误核对网关模型映射配置打印实际转发的模型名校验通过但成本账单仍不准只校验了 model 字符串未校验版本在身份对象中增加 model version 字段token 验签失败网关密钥与消费方密钥不一致统一密钥来源避免硬编码到多个服务流式响应中身份信息缺失流式响应结构里没有 model 字段在首个 chunk 或流结束元数据中获取模型字段缓存命中的响应模型标识过期网关缓存了上游响应未同步更新模型映射缓存 key 加入模型映射版本号校验层拖慢请求延时每次请求都做额外网络调用本地白名单缓存 异步审计日志下面展开两个典型问题。第一个是上游响应结构不兼容。不同模型供应商的响应字段并不完全一致有的是model有的是model_id有的是data.model。如果上游响应结构发生变化简单取值就可能取到 None。建议在校验入口增加统一的响应结构解析层而不是直接访问原始字典。第二个是流式响应。Stream 场景下响应以多个 chunk 陆续到达。模型身份信息可能只在第一个 chunk 里出现。这时候需要在流开始阶段提取模型标识并完成校验而不能等到整个流结束后再校验。7. 工程化与生产落地建议7.1 密钥管理与最小权限身份令牌的本质是可信关系。如果签名密钥泄露攻击者就可以自行签发“合法”的模型身份令牌。在生产环境要注意不使用默认密钥不把密钥提交到代码仓库优先使用 KMS、Vault 等密钥管理服务密钥定期轮换并保留一段新旧密钥并存窗口校验层只读所需的模型映射配置不授予不必要的高权限。7.2 身份校验层如何放置模型身份校验层可以放在两个位置网关插件或中间件。优点是覆盖范围广不用改业务代码。网关之后的独立校验服务。优点是职责单一方便灰度、升级和审计。如果团队已有 LiteLLM、Kong、APISIX 等网关建议优先选择插件或中间件方案。如果是自研网关则可以在网关核心处理链路中预留一个校验 Hook。7.3 流式响应与异步审计对于流式请求最好把“模型身份校验”和“流内容转发”做异步解耦在流开始时同步完成身份校验身份校验结果写入审计日志后续流的转发不再阻塞在身份校验上。这样既满足审计要求又不会明显增加用户等待时间。7.4 可观测性模型身份校验是安全治理的一部分建议将校验结果暴露成指标校验总次数校验失败次数校验失败原因分布身份令牌签发成功率上游模型响应延迟。这样当模型路由出现异常时可以通过指标快速定位是配置问题、上游问题还是校验逻辑问题。8. 总结与下一步学习建议本文围绕 XTokenChecker 的模型身份校验思路从 AI 网关的实际痛点出发实现了以下能力模型身份对象的定义与序列化模型名字段校验和白名单校验基于 HMAC 的身份令牌签发与验签一个可运行的 FastAPI 接入示例。你可以把这段代码继续扩展成支持多供应商的模型注册表支持数据库持久化的审计记录支持 Prometheus 指标的校验层支持流式响应的异步身份校验。后续建议继续深入学习几个方向一是网关流量治理特别是模型路由与限流二是可验证凭证与签名体系比如 JWT 与 HMAC 的适用边界三是成本治理把模型身份校验结果与计费系统打通。如果你正准备给 AI 网关补一套安全审计能力可以先从本文的模型名白名单校验开始。不要一上来就上签名令牌先把“身份信息能不能拿到、白名单配置是否可靠”这两个基础问题解决掉再逐步叠加令牌验证会让整个落地过程更稳。希望这篇能给你一些可落地的参考。如果觉得本文对你有帮助可以收藏备用也欢迎在评论区聊聊你在 AI 网关中遇到的模型身份问题。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门