概述
ArmCloud OpenAPI 签名验证 V2.0 是一种简化的API鉴权方案,相比V1.0版本,它提供了更简单的请求头设置和签名计算流程,同时保持高安全性。本文档详细说明了如何构建请求并计算签名以通过ArmCloud的身份认证。
请求头设置
V2.0版本需要在HTTP请求中添加以下三个自定义请求头:
请求头名称 | 说明 | 示例 |
---|---|---|
authver | 鉴权版本,固定值"2.0" | 2.0 |
x-ak | 访问密钥ID,由ArmCloud平台分配 | LTAI4FzK8888888888888 |
x-timestamp | 请求发起的时间戳(毫秒) | 1618900299000 |
x-sign | 请求的签名结果 | 5d9f846a5254865894069d9a864a96696115f0edcac66de5f3a3515f58c44200 |
签名计算流程
1. 构建待签名字符串
根据请求方法的不同,构建待签名字符串的方式略有差异:
GET请求
待签名字符串 = {timestamp} + {path} + {queryString}
timestamp
: 请求时间戳,毫秒级时间戳(与x-timestamp请求头值相同)path
: 请求路径,例如:/openapi/open/user/info
queryString
: URL中的查询参数字符串(如果有),例如:id=123&name=test
POST请求
timestamp
: 请求时间戳,毫秒级时间戳(与x-timestamp请求头值相同)path
: 请求路径,例如:/openapi/open/user/create
requestBody
: 请求体内容,通常是JSON字符串
2. 计算签名
待签名字符串 = {timestamp} + {path} + {requestBody}
使用HMAC-SHA256算法,以SecretKey作为密钥,对待签名字符串进行签名计算:
signature = HMAC-SHA256(stringToSign, secretKey)
计算得到的签名值需以小写十六进制字符串形式表示,长度为64个字符。
请求示例
GET请求示例
GET /openapi/open/user/info?id=12345 HTTP/1.1
Host: openapi-hk.armcloud.net
authver: 2.0
x-ak: LTAI4FzK8888888888888
x-timestamp: 1618900299000
x-sign: 5d9f846a5254865894069d9a864a96696115f0edcac66de5f3a3515f58c44200
签名计算过程:
- 待签名字符串:
1618900299000/openapi/open/user/info?id=12345
- 使用SecretKey计算HMAC-SHA256签名
POST请求示例
POST /openapi/open/user/create HTTP/1.1
Host: openapi-hk.armcloud.net
Content-Type: application/json
authver: 2.0
x-ak: LTAI4FzK8888888888888
x-timestamp: 1618900300000
x-sign: a7c29a7e4a8b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d
{"name":"张三","age":30,"email":"zhangsan@example.com"}
签名计算过程:
- 待签名字符串:
1618900300000/openapi/open/user/create{"name":"张三","age":30,"email":"zhangsan@example.com"}
- 使用SecretKey计算HMAC-SHA256签名
错误响应
当签名验证失败时,API网关会返回以下错误:
{
"code": 100005,
"msg": "验证签名失败",
"data": null
}
常见错误情况:
- AccessKey不存在或已禁用
- 请求时间戳与服务器时间相差超过5分钟
- 签名计算错误
与V1.0版本对比
V2.0版本相比V1.0版本有以下优势:
- 更简单的请求头:减少了必要的请求头数量,移除了复杂的authorization格式
- 更直观的签名计算:签名字符串构建逻辑更简单,易于理解和实现
- 兼容多种客户端:简化的设计更适合移动端和IoT设备等资源受限场景
注意事项
- 请确保系统时间准确,否则可能导致请求被拒绝
- SecretKey属于敏感信息,请勿以明文形式存储或传输
- 每个请求都必须重新计算签名,不可重复使用
- 签名计算需包含完整的请求路径,包括最前面的斜杠
客户端实现示例
Java实现示例
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
public class ArmCloudSignatureV2 {
private static final String ALGORITHM = "HmacSHA256";
public static String calculateSignature(String timestamp, String path, String body, String secretKey) throws Exception {
String stringToSign = timestamp + path + (body != null ? body : "");
Mac hmacSha256 = Mac.getInstance(ALGORITHM);
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), ALGORITHM);
hmacSha256.init(secretKeySpec);
byte[] hash = hmacSha256.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
return bytesToHex(hash);
}
private static String bytesToHex(byte[] bytes) {
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
}
public static void main(String[] args) throws Exception {
// 示例参数
String accessKeyId = "LTAI4FzK8888888888888";
String secretKey = "your_secret_key";
String path = "/openapi/open/user/info";
String queryString = "id=12345";
// 生成时间戳
String timestamp = String.valueOf(Instant.now().toEpochMilli());
// 计算签名
String signature = calculateSignature(timestamp, path, queryString, secretKey);
System.out.println("authver: 2.0");
System.out.println("x-ak: " + accessKeyId);
System.out.println("x-timestamp: " + timestamp);
System.out.println("x-sign: " + signature);
}
}
Python实现示例
import hmac
import hashlib
import time
def calculate_signature(timestamp, path, body, secret_key):
string_to_sign = timestamp + path + (body if body else "")
signature = hmac.new(
secret_key.encode('utf-8'),
string_to_sign.encode('utf-8'),
hashlib.sha256
).hexdigest()
return signature
# 示例参数
access_key_id = "LTAI4FzK8888888888888"
secret_key = "your_secret_key"
path = "/openapi/open/user/info"
query_string = "id=12345"
# 生成时间戳
timestamp = str(int(time.time() * 1000))
# 计算签名
signature = calculate_signature(timestamp, path, query_string, secret_key)
print(f"authver: 2.0")
print(f"x-ak: {access_key_id}")
print(f"x-timestamp: {timestamp}")
print(f"x-sign: {signature}")