为了验证开放 API 请求的合法性,必须要对 API 请求方进行认证,一般有两种认证模式,即HTTP Basic
和AK/SK
。
在 HTTP Basic 认证模式中,API 请求方在调用开放 API 时需要在请求头中传递 用户名/密码 的 BASE64 编码值,BASE64 编码是可逆的,这定然存在密码泄露的风险。
而 AK/SK 认证模式则可以避免明文传输密码,这种认证模式广泛应用于保障云服务商开放 API 的安全性。在 AK/SK 认证模式中,API 请求方需要使用由 API 提供商分配的Access Key
和Secret Key
进行认证。其中,Access Key 是公开的密钥,用于标识 API 请求方的身份;Secret Key 则是私有的密钥,只有 API 请求方和 API 提供商持有。
在 API 调用过程中,API 请求方需要使用HMAC
算法对签名消息体
进行签名
,然后将生成的签名和 Access Key 一并传递给 API 提供商;API 提供商根据 Access Key 拿到请求方的 Secret Key,然后使用相同的 HMAC 算法对同一签名消息体进行签名,接着与请求方发送的签名进行比对,从而判断该请求是否合法。
MAC (Message Authentication Code) 是一种基于共享密钥的消息认证技术,其被广泛应用于消息真实性和完整性的验证场景。而 HMAC (Hash-based Message Authentication Code) 是一种特殊类型的 MAC,它使用诸如 MD5、SHA-1 和 SHA-256 等哈希函数来计算消息摘要 (HMAC 与 MAC 的差异并不仅仅局限于此)。
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec("Secret Key".getBytes(StandardCharsets.UTF_8), "HmacSHA1"));
Base64.getEncoder().encodeToString(mac.doFinal("签名消息体"));
签名消息体的生成策略参考 OceanBase[1] 。
Http Method + "\n" + Content MD5 + "\n" + Content-Type + "\n" + Date + "\n" + Host + "\n" + URI + Query Parameter
HTTP 请求方法,大写英文。包括:GET、HEAD、POST、PUT、PATCH、DELETE、OPTIONS 和 TRACE。
HTTP 请求体 的 MD5 值,转为十六进制大写英文字母。即使 Content MD5 内容为空,连接符 “\n” 是依然存在的。
byte[] bodyByte = "{\"name\":\"test01\",\"description\":\"test\",\"regionId\":1}".getBytes(StandardCharsets.UTF_8);
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(bodyByte);
String hex = new BigInteger(1, digest.digest()).toString(16).toUpperCase();
String contentMd5 = StringUtils.leftPad(hex, 32, "0");
请求发起时间,其遵循 RFC1123 格式。
String rfc1123Date = ZonedDateTime.now(ZoneId.of("GMT")).format(DateTimeFormatter.RFC_1123_DATE_TIME);
请求的路径,不包括域名与查询参数部分。URI 与 Query Parameter 拼接是通过 “+” 而非 “\n”。
查询参数。以 “?” 开头,按 key 升序排序,以 “=” 连接键值,以 “&” 分隔键值对。key、value 需进行 URL 编码,编码规则遵照 RFC3986 规定。此外,查询参数是需要排序的。注意:对于a=1&a=2&a=3
这种,需转为a=1,2,3
值升序排列且逗号分隔。
// Map<String, List<String>> paramMap
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("&"));
本文部分代码摘自《OceanBase 云平台开放 API 文档》。最后提一嘴:AK/SK 认证模式或者说 HMAC 自身是不具备防御重放攻击 (replay attack) 能力的,规避重放攻击可以借助timestamp
、nonce
和sequence number
等方案[2]。
[1]
OceanBase AK/SK 签名生成规则: https://www.oceanbase.com
[2]
重放攻击与 HMAC: https://www.linkedin.com/advice/0/how-do-you-prevent-replay-attacks-when-using-hmac-authentication