用户访问控制

通过 JWT 实现应用 Key 的用户级别访问控制

概述

对于应用类型的 API Key,您可以开启用户级别的访问控制。网关会在处理请求时校验客户端传入的 JWT Token。

适用场景:当多个用户共享同一个应用 Key 调用 AI 服务时,通过 JWT 区分每个用户的身份。

示例
假设您的公司搭建了一个知识库 AI 应用,所有员工都可以通过该应用向 AI 提问。应用后端使用同一个 API Key 调用 Tarogo AI 网关,但您希望看到每个员工的 Token 使用情况,以便:
  • 了解哪些员工使用频率最高
  • 发现异常用量(如某个账号的 Token 消耗远超平均水平)
  • 按部门或人员做成本核算

此时只需将 Key 的类型设为「应用」,打开「按用户统计」开关,并配置域名白名单。客户端在调用时额外传入包含员工身份信息的 JWT,网关即可自动区分每个用户的用量,在管理后台的统计页面中按用户维度展示 Token 消耗数据。

工作原理

网关的访问控制流程如下:

1

客户端携带 JWT 发起请求

客户端在请求头中添加 X-JWT-Token,值为已签发的 JWT Token。

2

网关验证 API Key

网关首先验证 API Key 的有效性,然后判断该 Key 的类型是否为「应用」且开启了按用户统计。

3

验证 JWT Token

网关使用该 Key 关联的 secret 对 JWT 进行签名校验。

4

域名白名单校验

网关从 JWT payload 的 email 字段中提取域名,与 Key 配置的域名白名单进行比对。

5

请求转发与用量统计

验证全部通过后,网关将请求转发至 AI 供应商,并在日志和统计中关联用户信息。

配置步骤

1

创建应用类型 Key

在管理后台的「Key 管理」页面,点击「创建 Key」。

  • 类型:选择「应用」
  • 是否按用户统计:开启此选项:开启此选项
  • 域名白名单:配置允许访问的邮箱域名:配置允许访问的邮箱域名(如 example.com

重要:重要:Key 创建成功后,系统会返回一个 secret,请妥善保存。

2

配置域名白名单

在「系统设置」→「域名管理」页面,配置允许的邮箱域名白名单。

示例:如果域名白名单配置为 example.com,则只有 *@example.com 的用户才能通过校验。

3

服务端生成 JWT Token

在您的服务端使用 Key 对应的 secret 签发 JWT Token。

JWT Payload 规范
{
 "name": "张三",          // 必填:用户真实姓名
 "email": "zhangsan@example.com",  // 必填:用户邮箱(用于域名校验)
 "dept_name": "技术部",   // 可选:用户所属部门(用于日志和统计)
 "iat": 1715328000,       // 签发时间(Unix 秒级时间戳)
 "exp": 1715331600        // 过期时间(Unix 秒级时间戳)
}
字段类型必填说明
namestring用户真实姓名
emailstring用户邮箱(域名部分用于白名单校验)
dept_namestring用户所属部门(用于日志和统计)
iatnumber建议签发时间(Unix 秒级时间戳)
expnumber建议过期时间(Unix 秒级时间戳)
4

客户端携带 JWT 调用 API

客户端在发起 API 请求时,需要在请求头中同时携带 API Key 和 JWT Token。

  • Authorization 头:Bearer <API_KEY>
  • X-JWT-Token 头:服务端签发的 JWT Token

签名算法

Tarogo AI 网关支持以下 JWT 签名算法。

算法类型密钥适用场景
HS256对称加密共享 Secret服务端签发和验证使用同一密钥,配置简单
HS384对称加密共享 Secret更高安全级别,签名长度更长
HS512对称加密共享 Secret最高安全级别的 HMAC 签名
RS256非对称加密私钥 + 公钥服务端持私钥签发,网关持公钥验证,更安全
RS384 / RS512非对称加密私钥 + 公钥更高安全级别的 RSA 签名

HMAC 对称加密模式(HS256)

对称加密模式下,服务端和网关共享同一个 secret 密钥。

获取 Secret

创建应用 Key 并开启「按用户统计」后,系统会自动生成一个 secret。

安全提示:安全提示:secret 相当于密码,请存储在服务端环境变量中。

服务端签发 JWT

服务端使用 secret 对 JWT 进行 HMAC-SHA256 签名。

客户端使用流程

1

客户端向您的服务端请求 JWT Token

2

客户端在请求头中携带 JWT Token 调用 API

3

网关验证 JWT Token 并转发请求

const crypto = require('crypto');

// JWT 生成函数
function base64UrlEncode(obj) {
  return Buffer.from(JSON.stringify(obj))
    .toString('base64')
    .replace(/=/g, '')
    .replace(/\+/g, '-')
    .replace(/\//g, '_');
}

function generateJWT(payload, secret) {
  const header = { alg: 'HS256', typ: 'JWT' };

  const encodedHeader = base64UrlEncode(header);
  const encodedPayload = base64UrlEncode(payload);

  const signature = crypto
    .createHmac('sha256', secret)
    .update(encodedHeader + '.' + encodedPayload)
    .digest('base64')
    .replace(/=/g, '')
    .replace(/\+/g, '-')
    .replace(/\//g, '_');

  return encodedHeader + '.' + encodedPayload + '.' + signature;
}

// 生成 JWT Token
const payload = {
  name: '张三',
  email: 'zhangsan@example.com',
  dept_name: '技术部',
  iat: Math.floor(Date.now() / 1000),
  exp: Math.floor(Date.now() / 1000) + 3600, // 1 小时过期
};

const secret = 'YOUR_KEY_SECRET'; // Key 创建成功后返回的 secret
const token = generateJWT(payload, secret);
console.log('JWT Token:', token);

RSA 非对称加密模式(RS256)

非对称加密模式下,服务端持有私钥签发 JWT,网关持有公钥验证。安全性更高。

第一步:生成 RSA 密钥对

在您的服务端使用 OpenSSL 生成 RSA 私钥和公钥:

OpenSSL
# ===== 服务端:生成 RSA 密钥对 =====
# 生成 RSA 私钥(2048 位)
openssl genrsa -out private_key.pem 2048

# 从私钥提取公钥
openssl rsa -in private_key.pem -pubout -out public_key.pem

# 查看公钥内容(复制此内容配置到管理后台)
cat public_key.pem
  • 私钥private_key.pem):妥善保存在服务端,切勿泄露
  • 公钥public_key.pem):配置到 Tarogo AI 管理后台

第二步:配置公钥到管理后台

创建应用 Key 时,在「JWT 签名算法」中选择 RS256,然后将公钥内容粘贴到公钥配置框中。

网关在收到请求后,会使用您配置的公钥验证 JWT 的 RS256 签名。私钥仅由您的服务端保管,网关无法签发 JWT,即使公钥泄露也不会影响签名安全。

第三步:服务端使用私钥签发 JWT

服务端使用私钥对 JWT 进行 RSA-SHA256 签名,JWT header 中的 alg 字段应设置为 RS256。

客户端使用流程

1

客户端向您的服务端请求 JWT Token(与 HS256 模式完全相同)

2

服务端使用私钥签发 JWT(而非共享 secret)

3

客户端将 JWT 附加到 X-JWT-Token 请求头

4

网关使用您配置的公钥验证签名

import crypto from 'crypto';
import fs from 'fs';

// 服务端:使用私钥签发 JWT(RS256)
function base64UrlEncode(obj: object | string): string {
  const input = typeof obj === 'string' ? obj : JSON.stringify(obj);
  return Buffer.from(input)
    .toString('base64')
    .replace(/=/g, '')
    .replace(/\+/g, '-')
    .replace(/\//g, '_');
}

function signRS256(data: string, privateKey: string): string {
  const sign = crypto.createSign('RSA-SHA256');
  sign.update(data);
  sign.end();
  return sign.sign(privateKey, 'base64')
    .replace(/=/g, '')
    .replace(/\+/g, '-')
    .replace(/\//g, '_');
}

const privateKey = fs.readFileSync('private_key.pem', 'utf8');

const header = base64UrlEncode({ alg: 'RS256', typ: 'JWT' });
const payload = base64UrlEncode({
  name: '张三',
  email: 'zhangsan@example.com',
  dept_name: '技术部',
  iat: Math.floor(Date.now() / 1000),
  exp: Math.floor(Date.now() / 1000) + 3600,
});

const token = header + '.' + payload + '.' + signRS256(header + '.' + payload, privateKey);
console.log('JWT Token:', token);

// 客户端:使用 OpenAI SDK 调用
import OpenAI from 'openai';

const client = new OpenAI({
  apiKey: 'YOUR_API_KEY',
  baseURL: 'https://your-gateway-domain.com/v1',
  defaultHeaders: { 'X-JWT-Token': token },
});

API 调用示例

无论使用 HS256 还是 RS256,客户端的调用方式完全相同——只需在请求头中添加 X-JWT-Token。

使用 cURL

cURL
curl -X POST https://your-gateway-domain.com/v1/chat/completions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "X-JWT-Token: YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
   "model": "deepseek-chat@deepseek",
   "messages": [
      {
       "role": "user",
       "content": "你好,请介绍一下自己"
      }
    ]
  }'

使用 OpenAI SDK(Node.js)

Node.js
import crypto from 'crypto';

function generateJWT(payload: object, secret: string): string {
  const base64UrlEncode = (obj: object) =>
    Buffer.from(JSON.stringify(obj))
      .toString('base64')
      .replace(/=/g, '')
      .replace(/\+/g, '-')
      .replace(/\//g, '_');

  const encodedHeader = base64UrlEncode({ alg: 'HS256', typ: 'JWT' });
  const encodedPayload = base64UrlEncode(payload);

  const signature = crypto
    .createHmac('sha256', secret)
    .update(encodedHeader + '.' + encodedPayload)
    .digest('base64')
    .replace(/=/g, '')
    .replace(/\+/g, '-')
    .replace(/\//g, '_');

  return encodedHeader + '.' + encodedPayload + '.' + signature;
}

const token = generateJWT(
  {
    name: '张三',
    email: 'zhangsan@example.com',
    dept_name: '技术部',
    iat: Math.floor(Date.now() / 1000),
    exp: Math.floor(Date.now() / 1000) + 3600,
  },
  'YOUR_KEY_SECRET'
);

// 使用 OpenAI SDK 调用
import OpenAI from 'openai';

const client = new OpenAI({
  apiKey: 'YOUR_API_KEY',
  baseURL: 'https://your-gateway-domain.com/v1',
  defaultHeaders: {
    'X-JWT-Token': token,
  },
});

const response = await client.chat.completions.create({
  model: 'deepseek-chat@deepseek',
  messages: [{ role: 'user', content: '你好,请介绍一下自己' }],
});

常见错误

错误码说明解决方法
MISS_USERINFOX-JWT-Token header 缺失确保请求中包含 X-JWT-Token 请求头
INVALID_USERINFOJWT 校验失败检查 JWT Token 是否有效、secret 是否正确、payload 中是否包含 email 和 name
INVALID_USERINFO (domain)邮箱域名不在允许列表中确认用户邮箱域名已在 Key 的域名白名单中配置
401 UnauthorizedAPI Key 无效或已过期检查 API Key 是否正确、是否处于启用状态、是否在有效期内

注意事项

  • 访问控制仅在 Key 类型为「应用」且开启「按用户统计」时生效,个人 Key 不经过 JWT 校验
  • JWT Token 的签发必须在服务端完成,不要在客户端直接使用 secret
  • 建议为 JWT 设置合理的过期时间(如 1 小时),避免长期有效的 Token 泄露后被滥用
  • secret 是敏感信息,需妥善保管。如果怀疑 secret 泄露,可通过管理后台「重置 Key」功能更换
  • 域名白名单的修改会实时生效,无需重新创建 Key
  • 网关日志中会记录 JWT 中的用户信息(email、name、dept_name),便于审计和问题排查