Skip to main content

租户用户契约

租户用户契约适用于已开通特殊渠道接入的系统。渠道服务端自行生成加密 Token,AIS 将其映射到租户超级管理员或指定用户,按对应用户的业务权限执行操作。

该协议与 渠道平台 Token 相互独立:平台 Token 用于租户生命周期管理,本页 Token 用于租户内业务。

什么时候使用

目标使用什么身份前置条件
初始化组织、员工、知识库租户管理员契约渠道已开户,已开通契约能力
代表某位员工读取或操作资源指定用户契约用户已存在、启用、属于目标租户且有资源权限
从自己的系统进入 AIS 页面契约兑换短时登录结果服务端先验证外部用户身份,页面集成协议已确认
只管理租户合同与账户不使用本页契约使用 渠道平台 Token

契约由渠道服务端本地签发,不是调用一个“获取租户 Token”接口。共享密钥的开通和安全交付由双方完成;不同环境单独配置。平台 HMAC 密钥与本页 AES 密钥用途不同。

业务调用时序

渠道确定租户用户后签发契约,AIS 认证身份并校验业务权限

窄屏下可横向滚动查看时序图。

这条流程包含两道校验:渠道验证自己的用户,AIS 验证契约及资源权限。不能直接根据浏览器提交的 tenantCode/userCode 签发凭据。

载荷与身份

{
"tenantCode": "partner_tenant_001",
"userCode": "partner_employee_001",
"expireAt": 1790000000
}
字段类型必填说明
tenantCodestring开户返回的 data.code
userCodestringAIS 用户业务编码;不传或为空白时,映射为该租户超级管理员
expireAtintegerUnix 秒级失效时间;必须根据当前时间动态计算

expireAt - 当前时间 必须大于 0,且不超过部署方配置的有效期上限,默认上限为 28800 秒。建议生成短时凭据,并同步双方服务器时间。

AIS 每次请求都会检查租户存在、合同处于有效期内、租户未冻结或释放,以及目标用户启用且属于该租户。Token 不会自动创建租户或用户。缺少 userCode 会选择管理员身份,签发指定用户凭据时务必校验用户编码非空。

加密协议

项目约定
算法AES/GCM/NoPadding
共享密钥双方安全交换的 AES-256 密钥,标准 Base64 文本解码后 32 字节
明文UTF-8 JSON
IV每次随机生成 12 字节,不复用
GCM Tag128 bit,即 16 字节
附加认证数据不设置 AAD
二进制输出IV + 密文 + GCM Tag
最终编码标准 Base64,保留可能出现的 +/=不是 Base64Url

业务请求头:

Authorization: Bearer ais-channel-user:{encryptedToken}

Bearer 后有一个半角空格。ais-channel-user: 是默认协议前缀;部署方若配置其他前缀,应使用双方交付值。平台签名的 Base64Url 与本协议的标准 Base64 不可混用。

Node.js 服务端示例:

const { createCipheriv, randomBytes } = require("node:crypto");

function createChannelUserToken({
tenantCode,
userCode,
keyBase64,
validitySeconds = 1800,
maxValiditySeconds = 28800,
prefix = "ais-channel-user:"
}) {
if (!tenantCode || !Number.isInteger(validitySeconds) ||
validitySeconds <= 0 || validitySeconds > maxValiditySeconds) {
throw new Error("Invalid tenant or token validity");
}
if (userCode !== undefined && (typeof userCode !== "string" || !userCode.trim())) {
throw new Error("Specify a non-empty userCode or omit it for tenant admin");
}
const key = Buffer.from(keyBase64, "base64");
if (key.length !== 32) throw new Error("AES-256 requires a 32-byte key");
const payload = {
tenantCode,
expireAt: Math.floor(Date.now() / 1000) + validitySeconds
};
if (userCode !== undefined) payload.userCode = userCode;
const iv = randomBytes(12);
const cipher = createCipheriv("aes-256-gcm", key, iv);
const ciphertext = Buffer.concat([
cipher.update(JSON.stringify(payload), "utf8"), cipher.final()
]);
return prefix + Buffer.concat([iv, ciphertext, cipher.getAuthTag()]).toString("base64");
}

// 在对接服务端调用;keyBase64、prefix、maxValiditySeconds 使用部署方交付值。
// 返回值作为 "Authorization: Bearer " 后面的内容。
// 省略 userCode 生成管理员凭据;传入 userCode 生成指定用户凭据。

管理员 Token 可用于 组织、员工与知识库授权。用户 Token 按该用户角色和知识权限执行,不会继承管理员权限。

文件精确查询和分片列表 单独要求 tk- 平台 API Key;契约 Token 不能替代该校验。

业务请求与响应

用刚生成的 Token 读取已创建组织,验证租户身份链路:

curl --get "${BASE_URL}/kb/department/queryByCode" \
--header "Authorization: Bearer ${CHANNEL_USER_TOKEN}" \
--data-urlencode "code=partner_org_001"

认证成功后返回目标接口自己的响应,不增加“契约认证成功”包装。例如本请求的 data 为组织对象,详见 组织接口响应。因此 HTTP 200 或成功解密都不等于业务授权成功。

登录兑换

需要进入 AIS 页面时,可将契约凭据兑换为登录 Token。两种路径二选一:

  • 直接兑换:可信服务端调用 token 接口,获得登录 Token。
  • 一次性兑换:服务端先获取一次性 Token,将其安全交付当前用户,再消费为登录 Token。适合不希望向客户端交付可重复使用契约的场景。

服务端用契约申请一次性 Token,客户端消费后取得登录 Token

窄屏下可横向滚动查看时序图。

图示说明认证协议,不代表 AIS 页面接受任意 URL 参数。具体页面入口、Token 存储及跳转方式按已交付的前端集成约定实现。

方法与路径Query 参数用途
GET /user/api/auth/tokensecureKey契约凭据直接换取登录 Token;也可消费一次性 API Token
GET /user/api/auth/apiTokensecureKey用契约凭据签发一次性 API Token,再交给 /token 消费

请求字段

字段位置类型必填说明
secureKeyQuerystringapiToken 接口传契约;token 接口传契约或未消费的一次性 Token

这里的 secureKey 可使用裸 ais-channel-user:...,也兼容带 Bearer 。这项省略规则只适用于登录兑换,普通业务请求头仍必须带 Bearer

curl --get "${BASE_URL}/user/api/auth/token" \
--data-urlencode "secureKey=${CHANNEL_USER_TOKEN}"

必须进行 URL 编码,避免 Base64 中的 + 被解析为空格。直接兑换返回:

{
"code": 8200,
"message": "SUCCESS",
"data": {
"token": "REPLACE_WITH_LOGIN_TOKEN",
"tokenExpireSeconds": 1800
}
}

响应字段

两个兑换接口都返回相同结构,但 token 的用途不同:

字段类型token 接口apiToken 接口
codeinteger8200 表示兑换成功8200 表示签发成功
messagestring状态提示状态提示
data.tokenstring最终登录 Token只能交给 token 接口消费的一次性 Token
data.tokenExpireSecondsinteger本次登录 Token 的有效秒数一次性 Token 的有效秒数
curl --get "${BASE_URL}/user/api/auth/apiToken" \
--data-urlencode "secureKey=${CHANNEL_USER_TOKEN}"

# 从成功响应保存 data.token,作为 ONE_TIME_TOKEN;这不是原契约。
curl --get "${BASE_URL}/user/api/auth/token" \
--data-urlencode "secureKey=${ONE_TIME_TOKEN}"

apiToken 成功响应示例:

{
"code": 8200,
"message": "SUCCESS",
"data": {
"token": "REPLACE_WITH_ONE_TIME_TOKEN",
"tokenExpireSeconds": 300
}
}

示例有效期仅用于说明结构;以本次返回的秒数为准,不硬编码 300 或 1800。

一次性兑换顺序:调用 /apiToken → 保存响应 data.token → 将该值作为 /tokensecureKey → 使用最终登录 Token。一次性 API Token 只能成功消费一次;契约 Token 在有效期内可重复使用。

兑换结果有效期不超过契约凭据剩余时间,并受部署方登录兑换有效期上限约束。最终登录 Token 通常通过 token 请求头使用。

共享密钥和管理员凭据仅保存在渠道服务端;只向对应用户交付所需的短时登录结果。兑换 URL 含凭据,避免记录到访问日志、浏览器历史或工单中。

对接检查

确认租户和用户编码均来自 AIS 成功响应;先验证管理员读取租户内资源,再验证指定用户的已授权与未授权访问。认证失败时检查 Token 前缀、秒级时间、Base64 类型和实际交付密钥,并按 错误编码 处理。

有效期与撤销边界

对象生命周期集成责任
AES 共享密钥由双方配置与轮换流程管理仅服务端保存;轮换计划与部署方协商
契约 TokenexpireAt 前且在允许的最大有效期内短时签发、随机 IV,使用前保证用户身份映射正确
一次性 Token返回有效秒数内,且仅可成功消费一次绑定当前用户交付;已消费或超时需重新申请
登录 Token返回有效秒数内,并受登录配置约束只作为对应用户的登录态使用

契约验证会检查当前租户和用户状态,但已兑换的登录 Token 是另一类会话凭据;不要假设原契约过期或单次请求失败会立即撤销全部已兑换会话。若需要紧急失效或密钥轮换,按部署方的会话治理流程处理。

常见失败与定位

失败阶段优先检查
无法识别凭据是否使用已交付前缀;业务请求是否带 Bearer 和空格
解密/认证 Tag 失败Base64 类型、32 字节密钥、12 字节 IV、16 字节 Tag、拼接次序、未设置 AAD
契约过期或期限超限expireAt 是否动态生成且为秒;是否超过交付上限
用户身份失败租户 code、用户 code、归属、启停与合同状态
身份通过但操作失败角色、知识库访问权限、接口是否额外要求平台 Key
兑换时密文损坏Query 中的加号是否被当成空格;是否进行了 URL 编码
一次性 Token 无效是否已消费、超时或错误地直接用于业务接口

登录兑换超时后,不确定一次性 Token 是否已消费时不要循环消费同一值。重新验证外部用户,再从服务端重新申请一次性 Token。异常结果结构与业务码见 错误处理