API 认证与签名方法
管理接口的公共请求头、签名算法、多语言实现、错误处理与安全建议。
API认证与签名方法
接口参考待联调:签名编码仍有冲突:本页为 Hex 算法,部分历史业务客户端使用 Base64。尚未取得管理 API 的当前服务端契约,不能确认哪一种被服务端接受。 以下是待核对的接口资料,不作为可直接接入的正式示例。控制台操作请按对应使用指南完成。
概述
企业网关API支持两种认证方式:
- JWT Token认证:适用于Web前端和短期访问场景
- AccessKey/SecretKey签名认证:适用于服务端调用和自动化脚本
BaseUrl: YOUR_GATEWAY_MANAGEMENT_BASE_URL
本文档详细介绍AccessKey/SecretKey签名认证方式。
获取AccessKey和SecretKey
创建AccessKey
联系系统管理员通过企业网关管理后台创建AccessKey,创建成功后会获得:
{
"access_key_id": "YOUR_ACCESS_KEY_ID",
"secret_key": "YOUR_SECRET_KEY"
}
⚠️ 重要提示:
secret_key只在创建时显示一次,请务必妥善保存- 丢失后无法找回,只能重新创建或轮换
- 建议定期轮换SecretKey以提高安全性
公共请求参数
所有使用AccessKey认证的API请求都需要在HTTP请求头中携带以下参数:
| 参数名 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
| X-Access-Key-Id | String | 是 | AccessKey ID | YOUR_ACCESS_KEY_ID |
| X-Timestamp | String | 是 | 请求时间戳(ISO 8601格式) | 2024-01-01T12:00:00+00:00 |
| X-Signature | String | 是 | 请求签名(HMAC-SHA256,十六进制) | abc123...def456 |
| Content-Type | String | 是 | 请求内容类型 | application/json |
时间戳格式说明
- 格式:ISO 8601标准格式
YYYY-MM-DDTHH:mm:ss+HH:mm - 时区:支持任意时区,建议使用UTC时间
- 有效期:请求时间戳与服务器时间差不能超过5分钟
- 示例:
- UTC时间:
2024-01-01T12:00:00+00:00 - 北京时间:
2024-01-01T20:00:00+08:00 - 纽约时间:
2024-01-01T07:00:00-05:00
- UTC时间:
签名算法
签名计算步骤
第1步:计算请求体哈希
BodyHash = Hex(SHA256(RequestBody))
- 对请求体进行SHA256哈希,然后进行十六进制(Hex)编码
- 如果请求体为空(GET请求),则使用空字节数组计算哈希
示例:
// 请求体
const body = '{"name":"test-user","password":"YOUR_SECURE_PASSWORD"}'
// SHA256哈希后十六进制编码
const bodyHash = "013c1a46b7c341e46758ca835e0bc2636b3fcd2976f9df2ac0f5e45519b51f69"
第2步:构造待签名字符串
StringToSign = HTTP方法 + "\n" +
请求URI + "\n" +
时间戳 + "\n" +
请求体哈希
示例:
POST
/ram/user/create
2024-01-01T12:00:00+00:00
013c1a46b7c341e46758ca835e0bc2636b3fcd2976f9df2ac0f5e45519b51f69
第3步:计算HMAC-SHA256签名
Signature = Hex(HMAC-SHA256(SecretKey, StringToSign))
- 使用SecretKey对StringToSign进行HMAC-SHA256签名
- 将签名结果进行十六进制(Hex)编码
完整示例
假设我们要调用创建RAM用户的API:
请求参数:
- Method:
POST - URI:
/ram/user/create - Timestamp:
2024-01-01T12:00:00+00:00 - SecretKey:
YOUR_SECRET_KEY - Body:
{"name":"test-user","password":"YOUR_SECURE_PASSWORD","authority":"member"}
计算过程:
const { createHash, createHmac } = require('node:crypto');
const body = '{"name":"test-user","password":"YOUR_SECURE_PASSWORD","authority":"member"}';
const bodyHash = createHash('sha256').update(body, 'utf8').digest('hex');
const stringToSign = ['POST', '/ram/user/create', '2024-01-01T12:00:00+00:00', bodyHash].join('\n');
const signature = createHmac('sha256', 'YOUR_SECRET_KEY').update(stringToSign, 'utf8').digest('hex');
console.log({ bodyHash, signature });
多语言签名实现
Python
将下方代码保存为 signature_helper.py;实际发送请求时,使用同一份序列化后的字节作为请求体,不要再由 HTTP 库重新序列化。
import hashlib
import hmac
from datetime import datetime, timezone
def calculate_signature(method, uri, body, secret_key):
"""
计算API请求签名
Args:
method: HTTP方法,如 "POST"
uri: 请求URI,如 "/ram/user/create"
body: 请求体(字符串或字典)
secret_key: SecretKey
Returns:
(timestamp, signature) 元组
"""
# 1. 计算Body哈希(十六进制)
if isinstance(body, dict):
import json
body_str = json.dumps(body, separators=(',', ':'))
else:
body_str = body if body else ''
body_bytes = body_str.encode('utf-8')
body_hash = hashlib.sha256(body_bytes).hexdigest()
# 2. 生成时间戳
timestamp = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S%z')
# 格式化时区部分 (+0000 -> +00:00)
timestamp = timestamp[:-2] + ':' + timestamp[-2:]
# 3. 构造待签名字符串
string_to_sign = f"{method}\n{uri}\n{timestamp}\n{body_hash}"
# 4. 计算HMAC-SHA256签名(十六进制)
signature = hmac.new(
secret_key.encode('utf-8'),
string_to_sign.encode('utf-8'),
hashlib.sha256
).hexdigest()
return timestamp, signature
# 使用示例
method = "POST"
uri = "/ram/user/create"
body = {"name": "test-user", "password": "YOUR_SECURE_PASSWORD", "authority": "member"}
secret_key = "YOUR_SECRET_KEY"
timestamp, signature = calculate_signature(method, uri, body, secret_key)
print(f"Timestamp: {timestamp}")
print(f"Signature: {signature}")
JavaScript / Node.js
使用 Node.js 18 或以上版本,将代码保存为 .cjs 文件运行;仅使用内置 crypto,无需安装额外依赖。无请求体时传 null 或空字符串,空 JSON 对象则传 {}。
const crypto = require('crypto');
/**
* 计算API请求签名
* @param {string} method - HTTP方法
* @param {string} uri - 请求URI
* @param {object|string} body - 请求体
* @param {string} secretKey - SecretKey
* @returns {{timestamp: string, signature: string}}
*/
function calculateSignature(method, uri, body, secretKey) {
// 1. 计算Body哈希(十六进制)
const bodyStr = body == null ? '' : typeof body === 'object'
? JSON.stringify(body)
: (body || '');
const bodyHash = crypto
.createHash('sha256')
.update(bodyStr)
.digest('hex');
// 2. 生成时间戳
const timestamp = new Date().toISOString().replace(/\.\d{3}Z$/, '+00:00');
// 3. 构造待签名字符串
const stringToSign = `${method}\n${uri}\n${timestamp}\n${bodyHash}`;
// 4. 计算HMAC-SHA256签名(十六进制)
const signature = crypto
.createHmac('sha256', secretKey)
.update(stringToSign)
.digest('hex');
return { timestamp, signature };
}
// 使用示例
const method = 'POST';
const uri = '/ram/user/create';
const body = {
name: 'test-user',
password: 'YOUR_SECURE_PASSWORD',
authority: 'member'
};
const secretKey = 'YOUR_SECRET_KEY';
const { timestamp, signature } = calculateSignature(method, uri, body, secretKey);
console.log(`Timestamp: ${timestamp}`);
console.log(`Signature: ${signature}`);
Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"time"
)
// CalculateSignature 计算API请求签名
func CalculateSignature(method, uri string, body interface{}, secretKey string) (timestamp, signature string, err error) {
// 1. 计算Body哈希(十六进制)
var bodyBytes []byte
if body != nil {
bodyBytes, err = json.Marshal(body)
if err != nil {
return "", "", fmt.Errorf("序列化请求体失败: %w", err)
}
}
hash := sha256.New()
hash.Write(bodyBytes)
bodyHash := hex.EncodeToString(hash.Sum(nil))
// 2. 生成时间戳
timestamp = time.Now().UTC().Format("2006-01-02T15:04:05-07:00")
// 3. 构造待签名字符串
stringToSign := fmt.Sprintf("%s\n%s\n%s\n%s", method, uri, timestamp, bodyHash)
// 4. 计算HMAC-SHA256签名(十六进制)
mac := hmac.New(sha256.New, []byte(secretKey))
mac.Write([]byte(stringToSign))
signature = hex.EncodeToString(mac.Sum(nil))
return timestamp, signature, nil
}
// 使用示例
func main() {
method := "POST"
uri := "/ram/user/create"
body := map[string]interface{}{
"name": "test-user",
"password": "YOUR_SECURE_PASSWORD",
"authority": "member",
}
secretKey := "YOUR_SECRET_KEY"
timestamp, signature, err := CalculateSignature(method, uri, body, secretKey)
if err != nil {
panic(err)
}
fmt.Printf("Timestamp: %s\n", timestamp)
fmt.Printf("Signature: %s\n", signature)
}
Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
public class SignatureCalculator {
/**
* 计算API请求签名
* @param method HTTP方法
* @param uri 请求URI
* @param body 请求体JSON字符串
* @param secretKey SecretKey
* @return [timestamp, signature]
*/
public static String[] calculateSignature(String method, String uri,
String body, String secretKey) throws Exception {
// 1. 计算Body哈希(十六进制)
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] bodyBytes = (body != null ? body : "").getBytes(StandardCharsets.UTF_8);
byte[] hashBytes = digest.digest(bodyBytes);
String bodyHash = bytesToHex(hashBytes);
// 2. 生成时间戳
String timestamp = ZonedDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX"));
// 3. 构造待签名字符串
String stringToSign = String.format("%s\n%s\n%s\n%s",
method, uri, timestamp, bodyHash);
// 4. 计算HMAC-SHA256签名(十六进制)
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(
secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
mac.init(secretKeySpec);
byte[] signatureBytes = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
String signature = bytesToHex(signatureBytes);
return new String[]{timestamp, signature};
}
/**
* 字节数组转十六进制字符串
*/
private static String bytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(String.format("%02x", b));
}
return result.toString();
}
// 使用示例
public static void main(String[] args) throws Exception {
String method = "POST";
String uri = "/ram/user/create";
String body = "{\"name\":\"test-user\",\"password\":\"YOUR_SECURE_PASSWORD\",\"authority\":\"member\"}";
String secretKey = "YOUR_SECRET_KEY";
String[] result = calculateSignature(method, uri, body, secretKey);
System.out.println("Timestamp: " + result[0]);
System.out.println("Signature: " + result[1]);
}
}
PHP
<?php
/**
* 计算API请求签名
* @param string $method HTTP方法
* @param string $uri 请求URI
* @param array|string $body 请求体
* @param string $secretKey SecretKey
* @return array [timestamp, signature]
*/
function calculateSignature($method, $uri, $body, $secretKey) {
// 1. 计算Body哈希(十六进制)
if (is_array($body)) {
$bodyStr = json_encode($body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
} else {
$bodyStr = $body ?: '';
}
$bodyHash = hash('sha256', $bodyStr);
// 2. 生成时间戳
$timestamp = gmdate('Y-m-d\TH:i:sP');
// 3. 构造待签名字符串
$stringToSign = "$method\n$uri\n$timestamp\n$bodyHash";
// 4. 计算HMAC-SHA256签名(十六进制)
$signature = hash_hmac('sha256', $stringToSign, $secretKey);
return [$timestamp, $signature];
}
// 使用示例
$method = 'POST';
$uri = '/ram/user/create';
$body = [
'name' => 'test-user',
'password' => 'YOUR_SECURE_PASSWORD',
'authority' => 'member'
];
$secretKey = 'YOUR_SECRET_KEY';
list($timestamp, $signature) = calculateSignature($method, $uri, $body, $secretKey);
echo "Timestamp: $timestamp\n";
echo "Signature: $signature\n";
?>
完整请求示例
cURL
#!/bin/bash
# 配置
ACCESS_KEY_ID="YOUR_ACCESS_KEY_ID"
SECRET_KEY="YOUR_SECRET_KEY"
BASE_URL="http://your-domain.com"
METHOD="POST"
URI="/ram/user/create"
BODY='{"name":"test-user","password":"YOUR_SECURE_PASSWORD","authority":"member"}'
# 1. 计算Body哈希
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $2}')
# 2. 生成时间戳
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%S+00:00")
# 3. 构造待签名字符串
STRING_TO_SIGN=$(printf '%s\n%s\n%s\n%s' "$METHOD" "$URI" "$TIMESTAMP" "$BODY_HASH")
# 4. 计算签名
SIGNATURE=$(printf '%s' "$STRING_TO_SIGN" | openssl dgst -sha256 -hmac "$SECRET_KEY" -hex | awk '{print $2}')
# 5. 发送请求
curl -X POST "${BASE_URL}${URI}" \
-H "Content-Type: application/json" \
-H "X-Access-Key-Id: ${ACCESS_KEY_ID}" \
-H "X-Timestamp: ${TIMESTAMP}" \
-H "X-Signature: ${SIGNATURE}" \
-d "$BODY"
Postman Pre-request Script
在 Postman 中,将下方代码放入 Pre-request Script。适用于这里列出的固定管理接口路径,以及无请求体或 Body → raw → JSON 请求;查询参数中的变量会先解析、编码,再按实际发送的 URL 签名。表单和文件上传不适用。
// 配置
const accessKeyId = pm.environment.get("ACCESS_KEY_ID");
const secretKey = pm.environment.get("SECRET_KEY");
// 获取请求信息
const method = pm.request.method;
// 逐项替换查询变量后再编码,让签名与实际发出的 URL 一致。
function encodeQueryPart(value) {
let raw = String(value ?? '');
try { raw = decodeURIComponent(raw); } catch (_) { /* 原值并非百分号编码 */ }
return encodeURIComponent(pm.variables.replaceIn(raw));
}
const query = pm.request.url.query.all()
.filter(param => !param.disabled)
.map(param => encodeQueryPart(param.key) +
(param.value == null ? '' : '=' + encodeQueryPart(param.value)))
.join('&');
const requestUrl = pm.variables.replaceIn(pm.request.url.toString().split('?')[0].split('#')[0]);
pm.request.url.update(requestUrl + (query ? '?' + query : ''));
const uri = pm.request.url.getPathWithQuery();
const body = pm.request.body?.raw ? pm.variables.replaceIn(pm.request.body.raw) : '';
// 1. 计算Body哈希
const bodyHash = CryptoJS.SHA256(body).toString(CryptoJS.enc.Hex);
// 2. 生成时间戳
const timestamp = new Date().toISOString().replace(/\.\d{3}Z$/, '+00:00');
// 3. 构造待签名字符串
const stringToSign = `${method}\n${uri}\n${timestamp}\n${bodyHash}`;
// 4. 计算签名
const signature = CryptoJS.HmacSHA256(stringToSign, secretKey).toString(CryptoJS.enc.Hex);
// 5. 设置请求头
pm.request.headers.upsert({
key: 'X-Access-Key-Id',
value: accessKeyId
});
pm.request.headers.upsert({
key: 'X-Timestamp',
value: timestamp
});
pm.request.headers.upsert({
key: 'X-Signature',
value: signature
});
console.log('Timestamp:', timestamp);
console.log('Signature:', signature);
环境变量设置:
ACCESS_KEY_ID: 你的AccessKey IDSECRET_KEY: 你的SecretKey
签名验证调试
如果遇到签名验证失败(401错误),可以按以下步骤排查:
1. 检查时间戳
- 确保服务器时间准确(使用NTP同步)
- 时间戳格式必须符合ISO 8601标准
- 服务器允许±5分钟的时间误差
# 正确的时间戳格式
"2024-01-01T12:00:00+00:00" # ✅ 正确
"2024-01-01T12:00:00Z" # ❌ 错误(不支持Z格式)
"2024-01-01 12:00:00" # ❌ 错误(格式不对)
2. 检查Body哈希
- 确保Body序列化格式一致(JSON不要有额外空格)
- 空Body应该使用空字符串计算哈希
# 调试Body哈希
import hashlib
body = '{"name":"test-user","password":"YOUR_SECURE_PASSWORD"}'
body_hash = hashlib.sha256(body.encode('utf-8')).hexdigest()
print(f"Body Hash: {body_hash}")
3. 检查StringToSign
- 确保各部分用
\n换行符连接 - URI必须包含查询参数(如果有)
# 调试StringToSign
string_to_sign = f"{method}\n{uri}\n{timestamp}\n{body_hash}"
print("StringToSign:")
print(repr(string_to_sign)) # 使用repr可以看到\n字符
4. 检查签名计算
- 确保SecretKey正确(不包含多余空格)
- 确保使用十六进制编码而不是base64
# 调试签名
import hmac
import hashlib
signature = hmac.new(
secret_key.encode('utf-8'),
string_to_sign.encode('utf-8'),
hashlib.sha256
).hexdigest() # 注意:使用hexdigest()而不是digest()
print(f"Signature: {signature}")
print(f"Signature Length: {len(signature)}") # 应该是64个字符
常见错误码
| 错误码 | 说明 | 解决方法 |
|---|---|---|
| 401 | 签名验证失败 | 检查SecretKey和签名计算过程 |
| 401 | AccessKey不存在 | 检查AccessKey ID是否正确 |
| 401 | 时间戳过期 | 检查系统时间是否准确 |
| 401 | AccessKey已禁用 | 联系管理员启用或重新创建 |
| 403 | 权限不足 | 确认账号有权限调用该API |
安全建议
-
保护SecretKey:
- 不要在代码中硬编码SecretKey
- 使用环境变量或密钥管理服务
- 不要将SecretKey提交到版本控制系统
-
定期轮换:
- 建议每3-6个月轮换一次SecretKey
- 发现泄露立即禁用并创建新的
-
最小权限原则:
- 为不同用途创建不同的AccessKey
- 不再使用的AccessKey及时禁用
-
HTTPS传输:
- 生产环境必须使用HTTPS
- 避免在不安全的网络环境下传输
-
日志脱敏:
- 日志中不要记录完整的SecretKey
- 签名可以记录用于调试