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) + 分隔符 + 原始请求体 - 签名生成步骤:
- 按规则拼接好签名源字符串
- 使用SecretKey作为密钥,通过HMACSHA256算法对源字符串生成签名
- 将签名、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
相关产品推荐
相关产品推荐

