AK/SK 认证模式在开放 API 中的应用

为了验证开放 API 请求的合法性,必须要对 API 请求方进行认证,一般有两种认证模式,即HTTP BasicAK/SK

在 HTTP Basic 认证模式中,API 请求方在调用开放 API 时需要在请求头中传递 用户名/密码 的 BASE64 编码值,BASE64 编码是可逆的,这定然存在密码泄露的风险。

而 AK/SK 认证模式则可以避免明文传输密码,这种认证模式广泛应用于保障云服务商开放 API 的安全性。在 AK/SK 认证模式中,API 请求方需要使用由 API 提供商分配的Access KeySecret Key进行认证。其中,Access Key 是公开的密钥,用于标识 API 请求方的身份;Secret Key 则是私有的密钥,只有 API 请求方和 API 提供商持有

在 API 调用过程中,API 请求方需要使用HMAC算法对签名消息体进行签名,然后将生成的签名和 Access Key 一并传递给 API 提供商;API 提供商根据 Access Key 拿到请求方的 Secret Key,然后使用相同的 HMAC 算法对同一签名消息体进行签名,接着与请求方发送的签名进行比对,从而判断该请求是否合法。

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 。

Http Method + "\n" + Content MD5 + "\n" + Content-Type + "\n" + Date + "\n" + Host + "\n" + URI + Query Parameter
  • Http Method

HTTP 请求方法,大写英文。包括:GET、HEAD、POST、PUT、PATCH、DELETE、OPTIONS 和 TRACE。

  • Content MD5

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");
  • Date

请求发起时间,其遵循 RFC1123 格式。

String rfc1123Date = ZonedDateTime.now(ZoneId.of("GMT")).format(DateTimeFormatter.RFC_1123_DATE_TIME);
  • URI

请求的路径,不包括域名与查询参数部分。URI 与 Query Parameter 拼接是通过 “+” 而非 “\n”。

  • Query Parameter

查询参数。以 “?” 开头,按 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) 能力的,规避重放攻击可以借助timestampnoncesequence number等方案 。

作者:木木爸
链接:https://juejin.cn/post/7245885711941648440

请登录后发表评论

    没有回复内容