Java调用Amazon Bedrock REST API遇403签名不匹配问题求助
Java调用Amazon Bedrock REST API时403签名不匹配问题解决
当直接通过Java实现SigV4签名调用Amazon Bedrock Runtime REST API时,若返回403 Forbidden且提示签名不匹配,可从以下核心维度排查修复:
1. 核对签名核心参数
- 日期与时间一致性:
X-Amz-Date必须严格遵循YYYYMMDD'T'HHMMSS'Z'格式(UTC时区),签名计算时使用的时间需与请求头中的值完全一致,不能存在时间偏差。 - 服务名正确性:Bedrock Runtime的SigV4服务名是
bedrock-runtime,而非bedrock。凭证字符串(Credential)需确保服务部分为bedrock-runtime,例如:AKIAXXX/20250121/us-east-1/bedrock-runtime/aws4_request。 - 请求体哈希匹配:
X-Amz-Content-Sha256必须是请求体字节流的SHA256十六进制哈希值。若请求体存在任何改动(如多余空格、JSON转义差异),都会导致哈希不匹配,需确保计算哈希与发送的请求体完全一致。
2. 验证SignedHeaders的完整性与顺序
- SignedHeaders需包含所有参与签名的请求头,且统一使用小写字母,分号分隔的顺序要与签名计算时的顺序完全一致。例如参考curl中的
accept;content-length;content-type;host;x-amz-content-sha256;x-amz-date;x-amzn-bedrock-save,Java代码中必须覆盖所有这些头,顺序不能出错。 - 不可省略
host头,需设置为请求的完整域名(如bedrock-runtime.us-east-1.amazonaws.com)。
3. 逐步骤校验SigV4签名计算流程
按照AWS SigV4规范逐一核对每一步:
- 规范请求(Canonical Request):确认HTTP方法、URI、规范头、SignedHeaders、请求体哈希均正确。
- 待签名字符串(String to Sign):格式为
AWS4-HMAC-SHA256\n{X-Amz-Date}\n{日期戳}/{区域}/bedrock-runtime/aws4_request\n{规范请求的SHA256哈希}。 - 签名密钥生成:使用Secret Key依次与日期戳、区域、服务名、
aws4_request进行HMAC-SHA256加密,每一步的输出作为下一步的输入密钥。
可借助参考curl中的参数手动计算签名,对比Java代码的输出,定位差异点。
4. 确保请求体完全一致
Java代码中发送的请求体字节流必须与计算X-Amz-Content-Sha256时使用的字节流完全相同,包括空格、换行、JSON转义等细节。例如参考请求体:
{"anthropic_version": "bedrock-2023-05-31","max_tokens": 512,"messages": [{"role": "user","content": [{"type": "text","text": "What is java"}]}]}
5. 验证密钥与权限
确认AWS访问密钥具备bedrock:InvokeModel权限,且密钥未过期或禁用。可通过AWS CLI测试权限:
aws bedrock-runtime invoke-model --model-id anthropic.claude-3-haiku-20240307-v1:0 --body file://request.json --region us-east-1
若CLI调用成功,说明权限与密钥无问题,问题集中在签名计算环节。
修正后的Java示例代码
import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.text.SimpleDateFormat; import java.util.*; public class BedrockSigV4Example { private static final String AWS_ACCESS_KEY = "你的AWS访问密钥"; private static final String AWS_SECRET_KEY = "你的AWS秘密密钥"; private static final String REGION = "us-east-1"; private static final String SERVICE_NAME = "bedrock-runtime"; private static final String ENDPOINT = "https://bedrock-runtime.us-east-1.amazonaws.com/model/anthropic.claude-3-haiku-20240307-v1:0/invoke"; public static void main(String[] args) throws Exception { String requestBody = "{\"anthropic_version\": \"bedrock-2023-05-31\",\"max_tokens\": 512,\"messages\": [{\"role\": \"user\",\"content\": [{\"type\": \"text\",\"text\": \"What is java\"}]}]}"; byte[] requestBodyBytes = requestBody.getBytes(StandardCharsets.UTF_8); // 生成UTC格式的请求日期 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); String amzDate = dateFormat.format(new Date()); String dateStamp = amzDate.substring(0, 8); // 计算请求体的SHA256哈希 MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] contentSha256Bytes = md.digest(requestBodyBytes); String contentSha256 = bytesToHex(contentSha256Bytes); // 构建规范请求所需的头信息(自动排序保证一致性) Map<String, String> headers = new TreeMap<>(); headers.put("accept", "application/json"); headers.put("content-type", "application/json"); headers.put("host", "bedrock-runtime.us-east-1.amazonaws.com"); headers.put("x-amz-content-sha256", contentSha256); headers.put("x-amz-date", amzDate); // 若需要保存对话,添加以下头 // headers.put("x-amzn-bedrock-save", "true"); // 拼接规范头和签名头字符串 StringBuilder canonicalHeaders = new StringBuilder(); StringBuilder signedHeaders = new StringBuilder(); for (Map.Entry<String, String> entry : headers.entrySet()) { canonicalHeaders.append(entry.getKey().toLowerCase()).append(":").append(entry.getValue().trim()).append("\n"); signedHeaders.append(entry.getKey().toLowerCase()).append(";"); } signedHeaders.setLength(signedHeaders.length() - 1); // 移除末尾多余分号 // 构建规范请求 String canonicalRequest = "POST\n" + "/model/anthropic.claude-3-haiku-20240307-v1:0/invoke\n" + "\n" + // 无查询参数 canonicalHeaders + "\n" + signedHeaders + "\n" + contentSha256; // 构建待签名字符串 String algorithm = "AWS4-HMAC-SHA256"; String credentialScope = dateStamp + "/" + REGION + "/" + SERVICE_NAME + "/aws4_request"; byte[] canonicalRequestSha256 = md.digest(canonicalRequest.getBytes(StandardCharsets.UTF_8)); String stringToSign = algorithm + "\n" + amzDate + "\n" + credentialScope + "\n" + bytesToHex(canonicalRequestSha256); // 生成签名密钥 byte[] signingKey = getSignatureKey(AWS_SECRET_KEY, dateStamp, REGION, SERVICE_NAME); // 计算最终签名 Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(signingKey, "HmacSHA256")); String signature = bytesToHex(mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8))); // 构建Authorization头 String authorizationHeader = algorithm + " " + "Credential=" + AWS_ACCESS_KEY + "/" + credentialScope + ", " + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature; // 发送HTTP请求 URL url = new URL(ENDPOINT); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setDoOutput(true); // 设置所有请求头 for (Map.Entry<String, String> entry : headers.entrySet()) { conn.setRequestProperty(entry.getKey(), entry.getValue()); } conn.setRequestProperty("Authorization", authorizationHeader); // 写入请求体 try (OutputStream os = conn.getOutputStream()) { os.write(requestBodyBytes); } // 打印响应结果 System.out.println("响应状态码: " + conn.getResponseCode()); // 可继续读取响应内容... } // 生成SigV4签名密钥 private static byte[] getSignatureKey(String key, String dateStamp, String regionName, String serviceName) throws Exception { byte[] kSecret = ("AWS4" + key).getBytes(StandardCharsets.UTF_8); byte[] kDate = HmacSHA256(kSecret, dateStamp); byte[] kRegion = HmacSHA256(kDate, regionName); byte[] kService = HmacSHA256(kRegion, serviceName); return HmacSHA256(kService, "aws4_request"); } // HMAC-SHA256加密工具方法 private static byte[] HmacSHA256(byte[] key, String data) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(key, "HmacSHA256")); return mac.doFinal(data.getBytes(StandardCharsets.UTF_8)); } // 字节数组转十六进制字符串 private static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format("%02x", b)); } return sb.toString(); } }
内容的提问来源于stack exchange,提问作者Mitesh Suthar
相关产品推荐
相关产品推荐

