You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用DUO Security Admin API /admin/v1/users返回40103错误求助

调用Duo Security /admin/v1/users接口时40103签名错误排查

错误响应

{
 "code":40103,
 "message":"Invalid signature in request credentials",
 "stat":"FAIL"
}

问题代码

public class DuoCreateUsers {

    private static SortedMap<String, Object> params = new TreeMap<String, Object>();

    public static void main(String[] args) throws IOException {
        String ikey = "x";
        String skey = "x";
        String host = "api-d221a358.duosecurity.com";
        String httpMethod = "POST";
        String requestPath = "/admin/v1/users";
        String timestamp = OffsetDateTime.now().format(DateTimeFormatter.RFC_1123_DATE_TIME);

        String username = "newuser";
        String email = "abc@example.com";

        params.put("username",username);
        params.put("email",email);

        String queryString = canonQueryString();

        String canonicalRequest = timestamp + "\n" + httpMethod + "\n" + requestPath +  "\n" + queryString;
        System.out.println("canonicalRequest = " + canonicalRequest);

        String signature = sign1(canonicalRequest, skey);
      //  System.out.println("signature = " + signature);

        String url = "https://" + host + requestPath+"?"+queryString;
        System.out.println("url = " + url);

        HttpClient httpClient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(url);
        httpPost.setHeader("Date", timestamp);
        httpPost.setHeader("Authorization", "Basic " + Base64.getEncoder().encodeToString((ikey + ":" + signature).getBytes()));
        httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded");


        httpPost.setEntity(new StringEntity(params.toString()));
        // Make the request
        HttpResponse response = httpClient.execute(httpPost);
        HttpEntity entity = response.getEntity();
        String responseContent = entity != null ? EntityUtils.toString(entity) : "";

        String rs =  "Response Status Code: " + response.getStatusLine().getStatusCode() + "\nResponse Content:\n" + responseContent;
        System.out.println("rs = " + rs);

    }


    public static String canonQueryString()
            throws UnsupportedEncodingException {
        ArrayList<String> args = new ArrayList<String>();

        for (String key : params.keySet()) {
            String name = URLEncoder
                    .encode(key, "UTF-8")
                    .replace("+", "%20")
                    .replace("*", "%2A")
                    .replace("%7E", "~");
            String value = URLEncoder
                    .encode(params.get(key).toString(), "UTF-8")
                    .replace("+", "%20")
                    .replace("*", "%2A")
                    .replace("%7E", "~");
            args.add(name + "=" + value);
        }

        return com.duosecurity.client.Util.join(args.toArray(), "&");
    }

    private static String sign1(String data, String secretKey) {
        try {

            // Create an HMAC-SHA1 key from the secret key
            SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA1");

            // Initialize the HMAC-SHA1 algorithm
            Mac mac = Mac.getInstance("HmacSHA1");
            mac.init(secretKeySpec);

            // Calculate the HMAC-SHA1 hash
            byte[] hmacSha1Bytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));

            // Convert the result to a hexadecimal string
            String hmacSha1Hex = bytesToHex(hmacSha1Bytes);

            // Print the HMAC-SHA1 hash
            System.out.println("HMAC-SHA1: " + hmacSha1Hex);

            return hmacSha1Hex;
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
        }

        return null;
    }

    // Helper method to convert bytes to a hexadecimal string
    private static String bytesToHex(byte[] bytes) {
        StringBuilder hexStringBuilder = new StringBuilder();
        for (byte b : bytes) {
            hexStringBuilder.append(String.format("%02x", b));
        }
        return hexStringBuilder.toString();
    }
}

代码问题排查

  • Timestamp格式错误:Duo API要求签名用的timestamp是Unix时间戳(以秒为单位的十进制字符串),你当前使用RFC1123格式的日期字符串,导致签名计算的时间基准与服务器验证逻辑不匹配,直接触发签名无效。
  • Authorization头格式错误:Duo API的认证头格式为DUO <integration_key>:<signature>,你误用了HTTP Basic认证格式(Basic ...),服务器无法正确解析签名信息。
  • POST参数位置错误:/admin/v1/users是POST接口,参数需通过请求体以application/x-www-form-urlencoded形式发送,你同时将参数放到URL query和请求体中,导致签名计算的内容与实际请求参数不一致。
  • 签名计算遗漏请求体:POST请求的canonical request需包含URL编码后的请求体参数,你当前用URL query的内容参与签名,但实际参数在请求体,二者不匹配导致签名验证失败。
  • 请求体格式错误:params.toString()生成的是Map的字符串表示,并非符合要求的application/x-www-form-urlencoded格式键值对,服务器无法正确解析参数。

修正后的关键代码片段

public static void main(String[] args) throws IOException {
    String ikey = "x";
    String skey = "x";
    String host = "api-d221a358.duosecurity.com";
    String httpMethod = "POST";
    String requestPath = "/admin/v1/users";
    // 修正为Unix时间戳(秒)
    String timestamp = String.valueOf(System.currentTimeMillis() / 1000);

    String username = "newuser";
    String email = "abc@example.com";

    params.put("username",username);
    params.put("email",email);

    // POST请求无URL query,设为空字符串
    String queryString = "";
    // 生成符合要求的表单请求体内容
    String formBody = canonQueryString();

    // 修正canonicalRequest:使用请求体内容参与签名
    String canonicalRequest = timestamp + "\n" + httpMethod + "\n" + requestPath +  "\n" + formBody;
    System.out.println("canonicalRequest = " + canonicalRequest);

    String signature = sign1(canonicalRequest, skey);

    // URL无需拼接query参数
    String url = "https://" + host + requestPath;
    System.out.println("url = " + url);

    HttpClient httpClient = HttpClients.createDefault();
    HttpPost httpPost = new HttpPost(url);
    httpPost.setHeader("Date", timestamp);
    // 修正Authorization头格式
    httpPost.setHeader("Authorization", "DUO " + ikey + ":" + signature);
    httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded");

    // 使用正确的表单内容作为请求体
    httpPost.setEntity(new StringEntity(formBody, StandardCharsets.UTF_8));
    
    // 后续请求执行代码不变
    HttpResponse response = httpClient.execute(httpPost);
    HttpEntity entity = response.getEntity();
    String responseContent = entity != null ? EntityUtils.toString(entity) : "";

    String rs =  "Response Status Code: " + response.getStatusLine().getStatusCode() + "\nResponse Content:\n" + responseContent;
    System.out.println("rs = " + rs);
}

内容的提问来源于stack exchange,提问作者Enamul Haque

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 00:40:55