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

Java自动化生成带请求体的HMAC签名:POST请求签名失败排查

问题描述
  • 需要了解Postman中生成HMACSHA256签名时请求体的处理逻辑
  • 希望在Java中实现该签名流程的自动化
  • 当前遇到的问题:直接发送普通POST请求的请求体无法生成有效签名;基于请求头和体生成的HMAC签名在GET请求中可行,但POST请求不生效
当前Java实现代码
Map<String, String> headerMap = new HashMap<>();
headerMap.put("SecretKey", gds.API_KEY);
headerMap.put("KeyID", gds.API_KEY_ID);
//headerMap.put("Referer", gds.API_HOST_NAME + endpoint);
headerMap.put("Referer", "https://td-api.qa1.sac.int.threatmetrix.com/api/v1/add_element_identifier/687gh7ih");
APIQuery api = new APIQuery(logger);
String authorization = api.QueryHttpPostWithHeaderAuth("END_POINT_AUTH", headerMap, JsonBody);
api.printOutputParameters();
return authorization.substring(1, authorization.length() - 1);
}


public String QueryHttpPostWithHeaderAuth( String endPoint, Map<String, String> headerMap, String reqBodyHash){
    HttpPost request;
    try {
        CloseableHttpClient httpClient = HttpClientBuilder.create().build();
        request = new HttpPost(GlobalDataStore.API_SERVER + "v1/support/generate/authheader");
        if(!headerMap.isEmpty())
            headerMap.forEach((k,v) -> request.setHeader(k, v));

        StringEntity body = new StringEntity(reqBodyHash);

        //could override default content type
        String contentType = this.contentType == null ? "application/json":this.contentType;
        request.addHeader("content-type", contentType);
        request.setEntity(body);

        //post the json req
        CloseableHttpResponse response = httpClient.execute(request);


        //read the response
        BufferedReader rd = new BufferedReader(new InputStreamReader((response.getEntity().getContent())));
        this.statusCode = response.getStatusLine().getStatusCode();

        String line;
        while ((line = rd.readLine()) != null) {
            jsonResponse=line;
        }

        rd.close();
        httpClient.close();
        data = "";
    } catch(Exception e){
        Assert.assertTrue(false, String.valueOf(e));
    }
    return jsonResponse;
}


public static String GenerateDigest(String API_KEY, String reqBody) throws NoSuchAlgorithmException {
    String bodyText = reqBody;
    MessageDigest md = MessageDigest.getInstance("SHA-256");
    md.update(bodyText.getBytes(StandardCharsets.UTF_8));
    byte[] digest = md.digest();
    return "SHA-256=" + Base64.getEncoder().encodeToString(digest);
}
问题分析与解决方案

Postman中HMACSHA256签名的请求体处理逻辑

  • 请求体必须完全一致:签名生成时使用的请求体,必须和实际发送给服务器的请求体完全相同,包括空格、换行、JSON结构、编码格式(必须为UTF-8),任何细微修改都会导致签名无效。
  • 禁止自动格式化:关闭Postman的"自动格式化JSON"功能,避免工具自动添加/删除空格、换行符,破坏原始请求体结构。
  • 签名源字符串拼接规则:通常需要按API指定的顺序拼接内容,常见规则为:
    请求方法(如POST) + 分隔符(如换行符) + 关键请求头(如Referer、Content-Type) + 分隔符 + 原始请求体
  • 签名生成步骤:
    1. 按规则拼接好签名源字符串
    2. 使用SecretKey作为密钥,通过HMACSHA256算法对源字符串生成签名
    3. 将签名、KeyID等信息按API要求格式组装成Authorization请求头

Java实现修正方案

现有代码存在两个核心问题:一是依赖外部接口生成签名,无法把控请求体处理细节;二是GenerateDigest方法仅生成SHA256摘要,并非HMACSHA256签名。以下是修正后的实现:

1. 实现HMACSHA256签名生成方法

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

public static String generateHmacSHA256Signature(String secretKey, String signatureSource) throws NoSuchAlgorithmException, InvalidKeyException {
    Mac hmacSha256 = Mac.getInstance("HmacSHA256");
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
    hmacSha256.init(secretKeySpec);
    byte[] signatureBytes = hmacSha256.doFinal(signatureSource.getBytes(StandardCharsets.UTF_8));
    return Base64.getEncoder().encodeToString(signatureBytes);
}

2. 按规则拼接签名源并生成Authorization头

假设API要求的签名源格式为:POST + 换行符 + Referer + 换行符 + 原始请求体,代码如下:

// 1. 准备请求头和原始请求体
Map<String, String> headerMap = new HashMap<>();
headerMap.put("KeyID", gds.API_KEY_ID);
headerMap.put("Referer", "https://td-api.qa1.sac.int.threatmetrix.com/api/v1/add_element_identifier/687gh7ih");
String rawRequestBody = JsonBody; // 确保此字符串和实际发送的请求体完全一致

// 2. 拼接签名源字符串(需严格遵循API要求的顺序和分隔符)
String signatureSource = String.format("POST\n%s\n%s", headerMap.get("Referer"), rawRequestBody);

// 3. 生成HMACSHA256签名
String hmacSignature = generateHmacSHA256Signature(gds.API_KEY, signatureSource);

// 4. 组装Authorization头(格式需匹配API要求)
String authorizationHeader = String.format("HMACSHA256 KeyID=%s, Signature=%s", gds.API_KEY_ID, hmacSignature);

// 5. 发送POST请求时,携带该Authorization头和原始请求体
// 省略HttpClient发送请求的代码,确保请求体使用rawRequestBody,不做任何修改

关键注意事项

  • 务必确认API官方文档中规定的签名源拼接顺序和分隔符,不同API的规则差异很大。
  • 发送POST请求时,请求体必须使用参与签名的rawRequestBody,禁止在发送过程中修改编码、格式化内容。
  • 用Postman手动验证时,需完全复现Java代码中的请求头、请求体内容,对比签名是否一致,排查问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 13:45:25