
duo admin api 要求使用基于 hmac-sha1 的签名认证,而非简单的 basic auth;本文详解正确签名生成逻辑、请求头构造及 java 实现要点,助你快速修复 401 错误。
duo admin api 要求使用基于 hmac-sha1 的签名认证,而非简单的 basic auth;本文详解正确签名生成逻辑、请求头构造及 java 实现要点,助你快速修复 401 错误。
Duo Security Admin API 不接受标准的 Basic 认证方式(即 integration_key:secret_key 的 Base64 编码),而是强制采用 Duo 自定义签名机制(Signed Request):需将时间戳、HTTP 方法、API 主机名、路径及参数按特定顺序拼接为 canonical string,再用 secret_key 对其进行 HMAC-SHA1 签名,最终组合 integration_key:signature 并 Base64 编码后放入 Authorization: Basic ... 头中。你原始代码中直接 Base64 编码密钥对,正是导致 401 Unauthorized 的根本原因。
✅ 正确签名流程(关键四步)
-
构造 Canonical String
按顺序拼接(换行符\n分隔):- 当前 RFC 1123 格式时间戳(如
Tue, 01 Oct 2024 12:34:56 +0000) - HTTP 方法(大写,如
GET) - API 主机名(不含
https://,如api-xxxx.duosecurity.com) - 请求路径(如
/admin/v1/users) - 查询参数字符串(
key1=value1&key2=value2,按字典序排序,无 URL 编码)
- 当前 RFC 1123 格式时间戳(如
生成 HMAC-SHA1 签名
使用secret_key(UTF-8 字节)对 canonical string 进行 HMAC-SHA1 计算,输出十六进制小写字符串(如a1b2c3...)。组合认证令牌
拼接integration_key:hex_signature,再进行 Base64 编码。-
设置请求头
Authorization: Basic <base64-encoded-token> Date: <rfc-1123-timestamp></rfc-1123-timestamp></base64-encoded-token>
✅ 完整可运行 Java 示例(精简版)
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class DuoAdminApiClient {
private static final String DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss Z";
public static String generateSignature(String ikey, String skey, String method,
String host, String path, String params) {
// 1. 构造 canonical string
String timestamp = OffsetDateTime.now().format(DateTimeFormatter.RFC_1123_DATE_TIME);
String canonical = Stream.of(timestamp, method.toUpperCase(), host, path, params)
.collect(Collectors.joining("\n")) + "\n";
// 2. HMAC-SHA1 签名
try {
Mac hmac = Mac.getInstance("HmacSHA1");
SecretKeySpec keySpec = new SecretKeySpec(skey.getBytes(StandardCharsets.UTF_8), "HmacSHA1");
hmac.init(keySpec);
byte[] hash = hmac.doFinal(canonical.getBytes(StandardCharsets.UTF_8));
// 转为 hex 小写字符串
String signature = bytesToHex(hash);
// 3. 组合并编码
String authString = ikey + ":" + signature;
return Base64.getEncoder().encodeToString(authString.getBytes(StandardCharsets.UTF_8));
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException("Failed to generate Duo signature", e);
}
}
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) {
String ikey = "DI7ABPU9TUJQO14RET9Q";
String skey = "YzDs7ZeQGMllravxDQxcn4jNAwyqF42P1XBDdGd2";
String host = "api-d221a358.duosecurity.com";
String path = "/admin/v1/users";
String method = "GET";
String params = ""; // 无查询参数时为空字符串
String authHeader = "Basic " + generateSignature(ikey, skey, method, host, path, params);
String dateHeader = OffsetDateTime.now().format(DateTimeFormatter.RFC_1123_DATE_TIME);
// 后续用 HttpURLConnection 或 RestTemplate 发起请求
System.out.println("Authorization: " + authHeader);
System.out.println("Date: " + dateHeader);
}
}
⚠️ 注意事项与最佳实践
-
时间戳必须严格同步:Duo 要求请求时间与服务器时间偏差 ≤ 5 分钟,建议使用 NTP 校时或
OffsetDateTime.now(ZoneOffset.UTC)。 -
Host 不含协议和端口:仅传
api-xxxx.duosecurity.com,勿带https://或:443。 -
参数需排序且不编码:若带查询参数(如
?limit=100&offset=0),提取limit=100&offset=0后按 key 字典序重排(Duo 文档明确要求)。 -
避免硬编码密钥:生产环境应通过 Spring Boot
application.yml或环境变量注入ikey/skey。 -
SSL 验证不可禁用:示例中
TrustManager全信任代码(trustAllCerts)仅用于调试,上线前务必移除,启用标准 TLS 验证。
✅ 总结
Duo Admin API 的 401 错误几乎总是源于签名机制误用。牢记:不是 Basic Auth,而是签名式 Basic Auth。正确实现 canonical string 拼接 + HMAC-SHA1 签名 + Base64 封装三步,即可稳定调用所有 Admin API(用户、组、集成等)。建议封装为工具类复用,并配合单元测试验证签名一致性。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!










