基于湖库一体架构,统一管理结构化、半结构化与非结构化等多模态数据,一个系统承载事务处理、实时分析与 AI 工作负载。
AK/SK 签名生成规则
更新时间:2025-07-02 15:21:03
概述
您将 HTTP 请求发送到 OCP 时,OCP 需要对您的请求进行签名计算并生成认证字符串,以便识别请求者身份、保护传输过程数据安全性并防止重复攻击。
系统将使用 OCP 的访问密钥来进行签名计算,该访问密钥包含访问密钥 ID(Access Key Id,简称 AK)和秘密访问密钥(Secret Access Key,简称 SK)。
使用说明
步骤一:获取 AK/SK
登录 OCP 。
将光标悬停于右上角用户名上,在弹出的菜单栏中单击 个人设置。
进入 个人设置 页面,在 AccessKey 模块单击 一键创建 AccessKey,详情可参考 设置个人信息。
创建成功后请妥善保存 AK/SK。
步骤二:使用 AK/SK 发起请求
您将 HTTP 请求发送至 OCP 时,需保证 HTTP Header 中包含 Authorization 字段及 Date 字段,以便 OCP 服务端对请求进行认证。
Authorization:OCP-ACCESS-KEY-{签名算法} {AK}:{Signature} 。
Date:Tue, 17 Jan 2023 03:36:01 GMT 。
签名算法:目前仅支持
HMACSHA1,需注意此处HMACSHA1为大写形式。AK:密钥 ID,请注意签名算法与 AK 之间存在空格。
Signature:计算得到的签名,具体计算规则请参考 签名生成。
Date:请求发起时间。其遵循 RFC1123 格式,且发起时间至到达服务器时间需 <15 分钟。同时需注意时区、时间一致性问题。
OCP Server 端在接受到您的请求后,会解析出 Authorization 及 Date ,并加以校验。如果校验通过,则继续处理请求,否则将报错。目前仅支持 HTTP Header 中包含认证字符串。
注意
AK/SK 属于某个用户,具备的权限等同于此用户所享有的权限。
签名生成
Signature 的获取流程如下:

生成公式为:$\mathrm{signature} = Base64(H(K, \mathrm{message}))$,其中:
Base64:代表用 Base64 编码。
H:代表 Hash 函数,目前仅支持 HMACSHA1。
K:AccessKey Secret 即 SK。
message:签名消息体,具体请参考 签名消息体 message。
签名消息体 message
message 计算公式如下:
message = Http Method + "\n" + Content-md5 + "\n" + Content-Type + "\n" + Date+ "\n" + Host+ "\n" + x-ocp-headers + "\n" + uri + query 参数
注意
uri 与 query 参数拼接是通过 “+” 而非 “\n”。另外某项内容可能为空(例如: Content-md5),当为空时需注意连接符 “\n” 是存在的。
从公式可以看出 message 由 Http Method、Content-md5、Content-Type、Date、Host、x-ocp-headers、uri 和 query 参数八部分组成,它们的具体含义及获取方式如下:
| 名称 | 内容格式 | 示例 |
|---|---|---|
| Http Method | HTTP 请求方法,大写英文。包括:GET、HEAD、POST、PUT、PATCH、DELETE、OPTIONS 和 TRACE。 | GET |
| Content-md5 | HTTP 请求消息 Body 的 MD5 哈希值,转为十六进制大写英文字符串。 | 1572A15D7DE7EE9E7BB86461FFEA9499 |
| Content-Type | HTTP 请求中 Body 部分的类型。 | application/json |
| Date | 请求发起时间,其遵循 RFC1123 格式。 | Mon, 3 Jan 2010 08:33:47 GMT |
| Host | HTTP 请求 Host。 | xxx.xxx.xxx.xxx:8080 |
| x-ocp-headers | header 中以 x-ocp- 为前缀的键值对,按 key 升序排序,以 “:” 连接键值(多个 value 逗号分隔升序排列),以换行符分隔键值对。 |
x-ocp-date:Tue, 17 Jan 2023 03:36:01 GMT |
| uri | 请求的路径,不包括域名与请求参数部分。 | /api/v2/iam/users |
| query 参数 | 请求的查询参数。以 “?” 开头,按 key 升序排序,以 “=” 连接键值(多个 value 时逗号分隔升序排列),以 “&” 分隔键值对。key、value 需进行 URL 编码,编码规则遵照 RFC3986 规定。 | ?a=1&b=2
注意 |
注意
对于 x-ocp-headers、query 参数 中涉及的排序规则为:按照按字典顺序比较两个字符串,比较基于字符串中每个字符的 Unicode 值。如果 Unicode 值都相等,则较短的字符串按字典顺序排在较长字符串的前面。对于 query 参数 中的 URL 编码,需注意特殊字符,例如 “+” 号转义为 “%20”。
完整的 message 如下(除最后一行外,其他行末尾都有换行):
POST
186974DB33A090A16D3E2CA35F547B56
application/json
Tue, 17 Jan 2023 03:36:01 GMT
ocp.alibaba.net:8080
x-ocp-data:1,2,A,J,Z
/api/v2/compute/idcs?userId=1001
请求签名过程示例
上述介绍了签名算法及签名消息体的生成,下面将通过实例,为您演示生成的详细步骤。
假设当前获取到的 AK/SK 如下:
AK:cqammmxBpfGjFlto
SK:2fc0c299cc94c6be266f2ceece765d4d
案例一
我们以调用 OCP 创建 IDC 为例,假设将要发起的 HTTP 请求如下(省略部分无关信息):
POST /api/v2/compute/idcs HTTP/1.1
Content-Type application/json
x-ocp-data A,1
Host ocp.alibaba.net:8080
请求体:{"name":"test01","description":"test","regionId":1} 请求发起时间:Tue, 17 Jan 2023 09:13:57 GMT
注意
上述请求体其 MD5 结算结果为 186974DB33A090A16D3E2CA35F547B56。
根据 签名消息题 message 构造规则,上述 API 构造出的 message 如下:
POST\n + 186974DB33A090A16D3E2CA35F547B56\n + application/json\n + Tue, 17 Jan 2023 09:13:57 GMT\n + ocp.alibaba.net:8080\n + x-ocp-data:A,1\n + /api/v2/compute/idcs
根据 SK 及 message 最终计算出的签名如下:
XN8P+O+v3vUabB16ZCooq5wMJoY=
最终得到的 Authorizaton 为:
OCP-ACCESS-KEY-HMACSHA1 cqammmxBpfGjFlto:XN8P+O+v3vUabB16ZCooq5wMJoY=
将 Authorizaton、Date 设置进请求 Header,最终发起的 HTTP 请求内容如下:
POST /api/v2/compute/idcs HTTP/1.1
Content-Type application/json
x-ocp-data A,1
Authorization OCP-ACCESS-KEY-HMACSHA1 cqammmxBpfGjFlto:XN8P+O+v3vUabB16ZCooq5wMJoY=
Date Tue, 17 Jan 2023 09:13:57 GMT
Host ocp.alibaba.net:8080
案例二
我们以分页查询 IDC 列表为列,假设将要发起的 HTTP 请求如下(省略部分无关信息):
GET /api/v2/compute/idcs?size=100 HTTP/1.1
Content-Type application/json;charset=utf-8
Host ocp.alibaba.net:8080
请求发起时间:Tue, 17 Jan 2023 04:14:02 GMT
注意
由于请求 body 为空,因此 MD5 值为空字符串。由于无 x-ocp 开头的 header 数据,因此 x-ocp-header 也是空字符串。
上述 API 构造出的 message 如下:
GET\n + \n + application/json;charset=utf-8\n + Tue, 17 Jan 2023 04:14:02 GMT\n + ocp.alibaba.net:8080\n + \n + /api/v2/compute/idcs?size=100
同样,以前面示例中的 SK 做签名运算,得到的签名为:
TsQD6HDOuZuJ409m0wdnZPmijlc=
最终得到的 Authorizaton 为:
OCP-ACCESS-KEY-HMACSHA1 cqammmxBpfGjFlto:TsQD6HDOuZuJ409m0wdnZPmijlc=
将 Authorizaton、Date 设置进请求 Header,最终发起的 HTTP 请求内容如下:
GET /api/v2/compute/idcs?size=100 HTTP/1.1
Content-Type application/json;charset=utf-8
Authorization OCP-ACCESS-KEY-HMACSHA1 cqammmxBpfGjFlto:TsQD6HDOuZuJ409m0wdnZPmijlc=
Date Tue, 17 Jan 2023 04:14:02 GMT
Host ocp.alibaba.net:8080
Connection keep-alive
Java 签名示例代码
import com.google.common.collect.Lists;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.BitSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.stream.Collectors;
public class ExampleTest {
public static void main(String[] args) {
String sk = "2fc0c299cc94c6be266f2ceece765d4d";
String httpMethod = "POST";
byte[] bodyByte = "{\"name\":\"test01\",\"description\":\"test\",\"regionId\":1}".getBytes(StandardCharsets.UTF_8);
String contentType = "application/json";
String rfc1123Date = "Tue, 17 Jan 2023 09:13:57 GMT";
String host = "ocp.alibaba.net:8080";
Map<String, List<String>> xOcpHeaders = new HashMap<>();
ArrayList<String> list = Lists.newArrayList("A", "1");
xOcpHeaders.put("x-ocp-data", list);
String path = "/api/v2/compute/idcs";
Map<String, List<String>> queryParams = new HashMap<>();
String generator = generator(sk, httpMethod, bodyByte, contentType, rfc1123Date, host, xOcpHeaders,
path, queryParams);
// XN8P+O+v3vUabB16ZCooq5wMJoY=
System.out.println(generator);
}
/**
* 生成签名
*/
public static String generator(String accessKeySecret,
String httpMethod,
byte[] body,
String contentType,
String rfc1123Date,
String host,
Map<String, List<String>> headers,
String path,
Map<String, List<String>> queryParams) {
String message = buildMessage(
httpMethod,
md5Crypt(body),
contentType,
rfc1123Date,
host,
buildXOcpHeader(headers),
buildUrlAndParam(path, queryParams));
System.out.println(message);
// encode
return Base64.getEncoder().encodeToString(
hmacSha1(accessKeySecret, message.getBytes(StandardCharsets.UTF_8)));
}
/**
* 对参数排序,构造新的请求 URL 用于签名
*
* @param requestUri 请求 URL
* @param paramMap 请求参数
* @return 待加签的 URL + param
*/
public static String buildUrlAndParam(String requestUri, Map<String, List<String>> paramMap) {
if (MapUtils.isEmpty(paramMap)) {
return requestUri;
}
String urlParam = paramMap.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.map(e -> {
String value = e.getValue().stream()
.filter(StringUtils::isNotEmpty).sorted().collect(Collectors.joining(","));
return String.format("%s=%s", encoder(e.getKey()), encoder(value));
})
.collect(Collectors.joining("&"));
urlParam = urlParam.replace("%2B", "%20");
return requestUri + "?" + urlParam;
}
/**
* 构建待加签的 header
*
* @param headers 请求 header
* @return 待加签的 header
*/
public static String buildXOcpHeader(Map<String, List<String>> headers) {
return headers.entrySet().stream()
.filter(e -> StringUtils.startsWithIgnoreCase(e.getKey(), "x-ocp"))
.sorted(Map.Entry.comparingByKey())
.map(e -> String.format("%s:%s", e.getKey(), String.join(",", e.getValue())))
.collect(Collectors.joining("\n"));
}
/**
* MD5 加密
*
* @param content 待 MD5 加密的 byte content
* @return MD5 加密结果
*/
private static String md5Crypt(byte[] content) {
try {
if (content == null || content.length == 0) {
return null;
}
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(content);
String hex = new BigInteger(1, digest.digest()).toString(16).toUpperCase();
return StringUtils.leftPad(hex, 32, "0");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Not supported signature method MD5", e);
}
}
/**
* HMAC-SHA1 加密
*
* @param accessKeySecret accessKeySecret
* @param content 待 HMAC-SHA1 加密的 byte 数组
* @return 加密结果
*/
private static byte[] hmacSha1(String accessKeySecret, byte[] content) {
try {
Validate.notEmpty(accessKeySecret, "cannot be null or empty.");
Validate.notNull(content, "content");
Validate.isTrue(content.length != 0, "content cannot be empty.");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec(accessKeySecret.getBytes(StandardCharsets.UTF_8), "HmacSHA1"));
return mac.doFinal(content);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Not supported signature method HmacSHA1", e);
} catch (InvalidKeyException e) {
throw new RuntimeException("Failed to calculate the signature", e);
}
}
private static String buildMessage(String... contents) {
StringJoiner joiner = new StringJoiner("\n");
for (String content : contents) {
if (StringUtils.isNotEmpty(content)) {
joiner.add(content);
} else {
joiner.add("");
}
}
return joiner.toString();
}
private static final BitSet URI_UNRESERVED_CHARACTERS = new BitSet();
private static final String[] PERCENT_ENCODED_STRINGS = new String[256];
static {
for (int i = 'a'; i <= 'z'; i++) {
URI_UNRESERVED_CHARACTERS.set(i);
}
for (int i = 'A'; i <= 'Z'; i++) {
URI_UNRESERVED_CHARACTERS.set(i);
}
for (int i = '0'; i <= '9'; i++) {
URI_UNRESERVED_CHARACTERS.set(i);
}
URI_UNRESERVED_CHARACTERS.set('-');
URI_UNRESERVED_CHARACTERS.set('.');
URI_UNRESERVED_CHARACTERS.set('_');
URI_UNRESERVED_CHARACTERS.set('~');
for (int i = 0; i < PERCENT_ENCODED_STRINGS.length; ++i) {
PERCENT_ENCODED_STRINGS[i] = String.format("%%%02X", i);
}
}
/**
* URLEncoder 遵照 RFC3986规定。"URI 非保留字符" 包括以下字符:字母(A-Z,a-z)、数字(0-9)、连字号(-)、点号(.)、下划线(_)、波浪线(~),
* 算法实现如下: 1.将字符串转换成UTF-8编码的字节。2.保留所有“URI 非保留字符”原样不变。3.对其余字节做一次 RFC3986 中规定的百分号编码(Percent-encoding), 即一个“%”后面跟着两个表示该字节值的十六进制字母,字母一律采用大写形式。
*/
public static String encoder(String value) {
if (StringUtils.isEmpty(value)) {
return StringUtils.EMPTY;
}
StringBuilder builder = new StringBuilder();
for (byte b : value.getBytes(StandardCharsets.UTF_8)) {
if (URI_UNRESERVED_CHARACTERS.get(b & 0xFF)) {
builder.append((char) b);
} else {
builder.append(PERCENT_ENCODED_STRINGS[b & 0xFF]);
}
}
return builder.toString();
}
}