身份验证
所有 IDCloud API(Web & SDK 和 API 合约)均使用 OAuth2 with JWT Bearer Grant Type(RFC 7523)。您在后端生成一个短期 JWT assertion,将其换取 Bearer 令牌,并在后续每次调用中使用该令牌。
请勿在客户 端执行此操作
JWT assertion 必须仅在您的后端生成。切勿在前端代码、移动应用、代码仓库或日志中暴露您的私钥。
获取凭据
在生成令牌之前,您需要由 Unico 提供的服务账号。请联系 Unico 支持团队并提供以下信息:
- 服务账号名称(最多 12 个字符)
- 负责人的姓名、电子邮件和电话号码(仅限巴西、美国或墨西哥号码)
您将收到:
- 唯一账号名称
- 租户 ID
- 基础 JWT payload
- 私钥文件(
.pem格式)
每个环境一个账号
请为 UAT 和生产环境分别保留独立的服务账号。
构建 JWT assertion
assertion 是紧凑 JWS 格式的 JWT:{Base64url(Header)}.{Base64url(Payload)}.{Base64url(Signature)}。
Header
{
"alg": "RS256",
"typ": "JWT"
}
Payload
| 声明 | 值 | 说明 |
|---|---|---|
iss | <account_name>@<tenant_id>.iam.acesso.io | 随凭据一同提供 |
aud | https://identityhomolog.acesso.io(UAT)或 https://identity.acesso.io(生产) | 必须与令牌端点主机匹配 |
scope | * | 授予所有权限 |
iat | Unix 时间戳(秒) | JWT 签发时间 |
exp | iat + 最多 3600 | 不得超过 iat 起 1 小时 |
{
"aud": "https://identity.acesso.io",
"scope": "*",
"iat": 1738086000,
"exp": 1738089600
}
Signature
使用 Unico 提供的 .pem 私钥,通过 RS256(RSA + SHA-256)对 header + payload 进行签名。
请勿添加额外声明
上述列表之外的任何字段(例如 sub、jti、nbf)都会导致 1.2.22 错误。请仅使用所示的声明。
请求令牌
令牌端点对两种合约均相同:
| 环境 | 端点 |
|---|---|
| 生产 | POST https://identity.acesso.io/oauth2/token |
| UAT | POST https://identityhomolog.acesso.io/oauth2/token |
Request
| 参数 | 值 |
|---|---|
Content-Type | application/x-www-form-urlencoded |
grant_type | urn:ietf:params:oauth:grant-type:jwt-bearer |
assertion | 您已签名的 JWT |
- cURL
- Node.js
- Python
curl -X POST https://identity.acesso.io/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
-d "assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
import jwt from 'jsonwebtoken';
import fs from 'fs';
import qs from 'querystring';
const privateKey = fs.readFileSync('./private-key.pem');
const now = Math.floor(Date.now() / 1000);
const assertion = jwt.sign(
{
aud: 'https://identity.acesso.io',
scope: '*',
iat: now,
exp: now + 3600,
},
privateKey,
{ algorithm: 'RS256' }
);
const res = await fetch('https://identity.acesso.io/oauth2/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: qs.stringify({
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion,
}),
});
const { access_token, expires_in } = await res.json();
import time
import jwt # PyJWT
import requests
with open("private-key.pem", "rb") as f:
private_key = f.read()
now = int(time.time())
assertion = jwt.encode(
{
"aud": "https://identity.acesso.io",
"scope": "*",
"iat": now,
"exp": now + 3600,
},
private_key,
algorithm="RS256",
)
response = requests.post(
"https://identity.acesso.io/oauth2/token",
data={
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
},
)
token_data = response.json()
access_token = token_data["access_token"]
Response
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600
}
| 字段 | 类型 | 说明 |
|---|---|---|
access_token | string | JWT 访问令牌。在所有 API 调用中通过 Authorization: Bearer <token> 使用。 |
expires_in | integer | 过期时间(秒)。示例:3600。 |
token_type | string | 始终为 Bearer。 |
使用令牌
在每个 API 请求的 Authorization 头中添加令牌。两种合约的头格式相同——不同之处在于所调用 API 的主机和路径:
| 合约 | 生产主机 | UAT 主机 |
|---|---|---|
| Web & SDK | https://api.idcloud.unico.app | https://api.idcloud.uat.unico.app |
| API | https://api.id.unico.app | https://api.id.uat.unico.app |
Web & SDK — 仅需要 Authorization 头:
curl -X POST https://api.idcloud.unico.app/client/v1/process \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ ... }'
API — Authorization 与 APIKEY 头一起使用:
curl -X POST https://api.id.unico.app/processes/v1 \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "APIKEY: $API_KEY" \
-H "Content-Type: application/json" \
-d '{ ... }'
令牌续期
令牌在 3600 秒后过期。请在您的后端实现主动续期:
- 从令牌响应中跟踪
expires_in并存储过期时间戳。 - 在过期前剩余 10 分钟或更少时请求新令牌。
- 切勿在生产环境中等待
401响应才触发续期。
API 参考
令牌端点的完整 OpenAPI 规范可在 authentication.yaml 中查看。
| 方法 | 路径 | 说明 |
|---|---|---|
POST | /oauth2/token | 将已签名的 JWT assertion 换取 Bearer 访问令牌 |
错误代码
| 代码 | 说明 | 处理方式 |
|---|---|---|
1.0.1 | iss 中提供的 ID 不正确 | 验证 iss 字段是否与生成私钥时提供的租户 ID 匹配 |
1.0.14 | 应用程序未激活 | 与项目经理确认所使用的应用程序是否已激活 |
1.1.1 | 未提供 scope 参数 | 在 JWT payload 中添加 "scope": "*" |
1.2.4 | JWT assertion 无效 | JWT assertion 已不再有效。两种原因:(a) 当前时间已超过 exp(JWT 真正过期——每次令牌请求都应生成新的 assertion);或 (b) exp 超过 iat + 3600(有效期过长——将 exp 上限设为 iat + 3600)。 |
1.2.5 | JWT 验证失败 | 无法验证 JWT。验证参数,并确保使用 RS256 和正确的私钥进行签名 |
1.2.6 | 私钥已失效 | 用于签名 JWT 的私钥已不再有效。请为该账号申请新凭据 |
1.2.7 | JWT 已被使用 | 该 JWT 已被使用,不再有效。请为每次令牌请求生成新的 assertion |
1.2.11 | 账号未激活 | 所使用的账号未激活 |
1.2.14 | 账号缺少必要权限 | 所使用的账号没有必要的权限 |
1.2.18 | 账号被临时锁定 | 由于超过无效认证尝试次数,账号已被临时锁定 |
1.2.19 | 未授权的用户模拟 | JWT 包含指向未被授权模拟的账号的 sub 声明。请从 payload 中移除 sub 声明。 |
1.2.20 | JWT 解码失败 | 无法解码 JWT。请验证令牌格式及其是否使用 RS256 签名。 |
1.2.21 | 私钥错误/身份验证失败 | 无法针对该账号的任何已知密钥验证 JWT 签名。请确认您使用的是此服务账号和环境对应的正确 .pem 私钥。 |
1.2.22 | payload 中包含不允许的字段 | JWT 包含不允许的额外 payload 字段。请移除本指南未列出的任何声明(例如 sub、jti、nbf)。注意:若您包含了 sub 声明并收到了 1.2.19 错误,则该错误优先。 |
1.3.1 | IP 访问限制 | 您的 IP 不在该账号的白名单中 |
1.3.2 | 基于时间的访问限制 | 请求超出该账号允许的时间窗口 |
下一步
- 环境 — 沙箱与生产主机
- Web & SDK — 创建流程 — 认证后的首次调用
- API — 创建流程 — 认证后的首次调用