TGI 网关后端切换不同模型时保持请求级鉴权的 JWT 校验中间件

文章导读
TGI 网关切换后端不同模型时,JWT 校验中间件应当放在网关入口,也就是路由动作之前。切换模型属于路由选择,鉴权是这个选择的前置条件。让中间件在网关注册一次,然后把解析后的用户上下文通过请求头透传给后端模型服务,后端不需要再验签。
📋 目录
  1. Ⅰ 为什么要在网关入口做统一鉴权
  2. Ⅱ JWT 校验中间件需要完成的动作
  3. Ⅲ 模型切换时如何保持上下文透传
  4. Ⅳ 验证清单
A A

TGI 网关切换后端不同模型时,JWT 校验中间件应当放在网关入口,也就是路由动作之前。切换模型属于路由选择,鉴权是这个选择的前置条件。让中间件在网关注册一次,然后把解析后的用户上下文通过请求头透传给后端模型服务,后端不需要再验签。

JWT 中间件应置于网关入口,先鉴权后路由。切换模型不改变鉴权结果,只需把 JWT 解析出的用户标识、权限等上下文以请求头方式透传给后端。后端不重复验签,避免每个模型服务维护独立密钥。注意放行内部健康检查路径,并统一处理密钥轮换和过期时间。

为什么要在网关入口做统一鉴权

如果每个模型服务各自校验 JWT,切换模型时就需要给新接入的模型单独写一遍校验逻辑,密钥和规则也难以保持一致。把校验放在网关入口后,模型服务的职责只剩处理推理请求。网关本身可以是 Nginx、APISIX、Kong,也可以是一段自建的 Express 或 FastAPI 中间件,不改变现有 TGI 后端即可生效。

JWT 校验中间件需要完成的动作

以一个典型的请求为例:客户端携带 Authorization: Bearer Token 访问网关,网关先读 token,校验签名、过期时间、issuer、audience,然后提取用户身份(如 sub)和权限(如 roles)。校验通过后,把用户信息写入可识别的请求头,比如 x-user-id 和 x-user-roles,再交给后续路由逻辑。校验失败直接返回 401,不进入模型路由。

健康检查、部分公开接口需要排除在外,可以通过路径前缀或白名单数组实现。密钥轮换时建议用 JWKS 或支持多密钥验证的库,避免切换密钥瞬间大量请求失败。

TGI 网关后端切换不同模型时保持请求级鉴权的 JWT 校验中间件

模型切换时如何保持上下文透传

模型切换通常由请求中的 model 字段控制,或者按 URL 路径区分。无论哪种方式,都应当先执行鉴权中间件,再读取模型参数并转发。转发时保留之前写入的用户请求头,同时可以考虑把模型名也附加到头部,便于后端做租户隔离或计量。

// 以 Node.js + jsonwebtoken 为例,Express 风格中间件
const jwt = require('jsonwebtoken');

function authMiddleware(req, res, next) {
  const header = req.headers.authorization || '';
  const token = header.startsWith('Bearer ') ? header.slice(7) : null;
  if (!token) {
    return res.status(401).json({ error: 'missing token' });
  }
  try {
    const claims = jwt.verify(token, process.env.JWT_SECRET, {
      algorithms: ['HS256'],
      // audience: 'your-api',
      // issuer: 'your-issuer'
    });
    // 将用户上下文透传给后端模型服务
    req.headers['x-user-id'] = claims.sub;
    req.headers['x-user-roles'] = JSON.stringify(claims.roles || []);
    next();
  } catch (e) {
    return res.status(401).json({ error: 'invalid token' });
  }
}

// 在模型路由上先过鉴权,再根据 model 字段选择 TGI 上游
app.post('/v1/chat/completions', authMiddleware, (req, res, next) => {
  const model = req.body.model;
  if (model === 'llama-3.1-8b') {
    req.url = '/v1/chat/completions';
    proxy.web(req, res, { target: 'http://tgi-a:8080' });
  } else if (model === 'qwen2.5-7b') {
    req.url = '/v1/chat/completions';
    proxy.web(req, res, { target: 'http://tgi-b:8080' });
  } else {
    res.status(400).json({ error: 'unsupported model' });
  }
});

这里的 proxy 可以是 http-proxy-middleware 或其他转发库。关键是 authMiddleware 只写一次,后续新增模型只需要改路由表,不需要动鉴权代码。如果不同模型对应不同权限,可以在 JWT 里放入权限列表,中间件再根据目标模型做二次判断,仍然在网关层完成。

验证清单

  • 签发一个未过期的 JWT,分别指向两个不同模型,确认两个请求都能转发到对应 TGI 后端,且后端收到的请求头中包含 x-user-id。
  • 使用过期 token 或篡改签名的 token 访问任意模型路径,确认返回的是 401 而不是后端 502。
  • 将模型名从 llama-3.1-8b 切换到 qwen2.5-7b,不重新登录,直接用原 token 发起请求,确认鉴权结果不变。
  • 检查健康检查路径(如 /health)是否被中间件放行,避免网关健康检查因缺少 token 而失败。
  • 密钥轮换后,使用新旧两个 token 各请求一次,确认旧 token 在新密钥下失效,新 token 正常通过,且切换过程不会导致未签发的请求进入模型路由。