OpenAPI 加密鉴权接入指南
本文档面向调用 AnyMetrics OpenAPI 的接入方(客户端),说明如何对请求进行加密鉴权。核心动作只有一个:把你要代表的用户 **userId** 加密成 **token**,随请求头发送。
适用对象与前置条件
适用对象:需要调用 AnyMetrics OpenAPI 的系统接入方、后端开发人员、运维或集成实施人员。
接入前请确认:
-
已获得 AnyMetrics 租户 ID,即请求头
tenant-id的取值。 -
已确认要代表哪个 AnyMetrics 用户发起调用,并拿到该用户的
userId。 -
管理员已在管理台开启「OPEN API 鉴权」,并提供 RSA 公钥或 AES 对称密钥。
-
推荐优先使用 RSA;只有在客户端环境完全可信时才考虑 AES。
1. 总览
AnyMetrics OpenAPI 的加密鉴权流程:
-
管理员在 AnyMetrics 管理台「企业安全 → OPEN API 鉴权」开启功能并生成密钥,复制出公钥(RSA)或对称密钥(AES)交给接入方。
-
接入方用拿到的密钥,把目标用户的
userId(明文)加密成token。 -
调用 OpenAPI 时带上两个请求头:
tenant-id和token。 -
服务端用对应私钥/对称密钥解出
userId,以该用户身份执行查询。
1.1 管理员配置入口
管理员进入 AnyMetrics 管理台后,按下图路径打开 OPEN API 鉴权页面:

图中标注说明:
-
1:顶部导航进入「管理设置」。
-
2:左侧菜单进入「企业安全 → OPEN API 鉴权」。
在该页面开启 OPEN API 鉴权后,选择加密方式并生成密钥。使用 RSA 时复制「公钥」给接入方;使用 AES 时复制「对称密钥」给接入方。
支持两种加密方式:
| 方式 | 客户端持有 | 安全性 | 适用 |
|---|---|---|---|
| RSA(推荐) | 公钥(只能加密,不能解密) | 高 | 任意客户端 |
| AES | 对称密钥(既能加密也能解密) | 较低 | 仅受信任客户端 |
⚠️ 优先用 RSA。 AES 是对称密钥,等同于把"主密钥"交给客户端——任何持有者都能冒充该租户下任意用户。只有在客户端环境完全可信时才使用 AES。详见 §6 安全说明。
2. token 明文规则
明文 = 你要代表的 AnyMetrics 用户的 **userId** 字符串。
-
不需要 JSON 包装,不需要拼时间戳,就是 userId 本身这一个字符串。
-
例如明文
"100023",加密后得到一段 Base64,即为token。 -
每个 userId 加密一次即可复用(token 不含过期时间)。
3. 加密规范(协议契约)
接入方的加密参数必须与服务端完全一致,否则解密失败。以下是系统使用的全部默认值。
3.1 RSA
| 参数 | 值 |
|---|---|
| 变换(transformation) | RSA/ECB/PKCS1Padding |
| 密钥长度 | 2048 bit |
| 公钥格式 | X.509 / SubjectPublicKeyInfo (SPKI),Base64 编码 |
| 加密输入 | userId 的 UTF-8 字节 |
| 加密输出 | 密文字节的 Base64 字符串 → 即 token |
管理员复制给你的"公钥"就是上面的 Base64 SPKI 字符串,直接用。
3.2 AES
重点:以下默认值中,只有「对称密钥」会从管理台复制给你,其余(IV / 算法 / tagSize)都不会下发,必须由你按下表写死,才能和服务端对上。
| 参数 | 值 | 来源 |
|---|---|---|
| 算法(transformation) | AES/GCM/NoPadding |
固定,需写死 |
| 密钥长度 | 256 bit | 由密钥本身决定 |
| 对称密钥 | Base64 编码的 256bit 密钥 | 管理台复制 |
| IV / Nonce | **w7ZdfXjV+o+L**(Base64,解码后 12 字节) |
固定,需写死 |
| GCM 认证标签长度(tagSize) | 128 bit | 固定,需写死 |
| AAD(附加认证数据) | 无 | — |
| 加密输入 | userId 的 UTF-8 字节 | — |
| 加密输出 | 密文 ‖ 16字节GCM认证标签 整体的 Base64 字符串 → 即 token |
— |
关于输出格式:AES/GCM/NoPadding 在 Java Cipher.doFinal / Python AESGCM.encrypt 下都会把 16 字节(128bit)认证标签拼在密文末尾,再整体 Base64。两端格式天然一致,无需自己拆/拼标签。
⚠️ IV 固定是有意为之(这样才能不下发、客户端写死对接),代价是同一密钥下 GCM nonce 复用,密码学强度降低。这也是 AES 仅限受信任客户端的原因。
4. 参考代码
下面的代码做的事完全一致:输入 userId 明文 + 管理台给的密钥,输出 token(Base64)。
4.1 Java
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class AnyMetricsTokenEncryptor {
// ---------- RSA(推荐)----------
// publicKeyBase64:管理台复制的公钥
public static String encryptByRsa(String userId, String publicKeyBase64) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(publicKeyBase64);
PublicKey publicKey = KeyFactory.getInstance("RSA")
.generatePublic(new X509EncodedKeySpec(keyBytes));
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encrypted = cipher.doFinal(userId.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
}
// ---------- AES(仅受信任客户端)----------
private static final String AES_IV = "w7ZdfXjV+o+L"; // 固定默认 IV,需写死
private static final int TAG_SIZE = 128; // 固定,需写死
// keyBase64:管理台复制的对称密钥
public static String encryptByAes(String userId, String keyBase64) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(Base64.getDecoder().decode(keyBase64), "AES");
byte[] iv = Base64.getDecoder().decode(AES_IV);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new GCMParameterSpec(TAG_SIZE, iv));
byte[] encrypted = cipher.doFinal(userId.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
}
public static void main(String[] args) throws Exception {
String userId = "100023";
System.out.println("RSA token: " + encryptByRsa(userId, "<公钥Base64>"));
System.out.println("AES token: " + encryptByAes(userId, "<对称密钥Base64>"));
}
}
4.2 Python
依赖:pip install cryptography
import base64
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import load_der_public_key
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
# ---------- RSA(推荐)----------
# public_key_b64:管理台复制的公钥
def encrypt_by_rsa(user_id: str, public_key_b64: str) -> str:
public_key = load_der_public_key(base64.b64decode(public_key_b64))
# RSA/ECB/PKCS1Padding == RSA PKCS#1 v1.5
encrypted = public_key.encrypt(user_id.encode("utf-8"), padding.PKCS1v15())
return base64.b64encode(encrypted).decode("utf-8")
# ---------- AES(仅受信任客户端)----------
AES_IV = "w7ZdfXjV+o+L" # 固定默认 IV,需写死
# key_b64:管理台复制的对称密钥;AESGCM 默认 128bit 标签,与 tagSize=128 一致
def encrypt_by_aes(user_id: str, key_b64: str) -> str:
key = base64.b64decode(key_b64)
iv = base64.b64decode(AES_IV)
# encrypt 返回 密文 + 16字节标签,与服务端格式一致
encrypted = AESGCM(key).encrypt(iv, user_id.encode("utf-8"), None)
return base64.b64encode(encrypted).decode("utf-8")
if __name__ == "__main__":
uid = "100023"
print("RSA token:", encrypt_by_rsa(uid, "<公钥Base64>"))
print("AES token:", encrypt_by_aes(uid, "<对称密钥Base64>"))
需要其他语言(Node.js / Go / C# 等)的示例可联系我们补充——核心参数与上表一致,照搬即可。
5. 携带 token 发起请求
加密得到 token 后,调用 OpenAPI 时带上请求头:
| 请求头 | 值 |
|---|---|
tenant-id |
你的租户 ID |
token |
上一步加密得到的 token(Base64) |
Content-Type |
application/json |
示例(以指标查询为例):
curl -X POST 'https://<host>/anymetrics/api/v1/metrics/queryMetricResultDetails' \
-H 'tenant-id: tn_1578931' \
-H 'token: <加密后的token>' \
-H 'Content-Type: application/json' \
-d '{
"metricCode": "order_count",
"dimensions": [],
"filters": []
}'
请求体(指标编码、维度、过滤条件等)按各 OpenAPI 接口文档填写,本指南只负责鉴权部分。
6. 安全说明
-
首选 RSA:客户端只持有公钥,只能加密自己的 userId,无法解密或伪造其他内容。
-
AES 是安全降级:对称密钥同时能加密和解密,任何持有者都能冒充该租户下任意用户;且本系统 AES 使用固定 IV(GCM nonce 复用),强度进一步降低。仅在客户端运行环境完全可信时使用。
-
密钥务必通过安全渠道交付与保管,不要硬编码进公开仓库或前端可见代码。
-
管理员可在管理台重新生成密钥进行轮换;轮换后旧 token 立即失效,需用新密钥重新加密。
7. 常见问题与排查
| 现象 | 可能原因 |
|---|---|
| 解密失败 / 鉴权不通过 | 加密参数与 §3 不一致:RSA 变换写成了 RSA/ECB/OAEP...、AES 的 IV/tagSize/算法没按默认值写死、密钥复制有缺漏。 |
openapi auth key not found |
管理员尚未生成密钥,或刚改过密钥格式后未重新生成;让管理员在管理台重新生成。 |
| AES 能加密但服务端解不出 | IV 没用固定值 w7ZdfXjV+o+L,或标签长度不是 128bit。 |
| 提示用户不存在 | 明文 userId 不是该租户下的有效用户。 |
附录:参数速查
| RSA | AES | |
|---|---|---|
| 变换 | RSA/ECB/PKCS1Padding |
AES/GCM/NoPadding |
| 密钥长度 | 2048 bit | 256 bit |
| 客户端持有 | 公钥 (X.509 SPKI Base64) | 对称密钥 (Base64) |
| IV | 不适用 | w7ZdfXjV+o+L(固定,写死) |
| tagSize | 不适用 | 128 bit(固定,写死) |
| 明文 | userId | userId |
| 输出 | 密文 Base64 | (密文‖16字节标签) Base64 |