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

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规范逐一核对每一步:

  1. 规范请求(Canonical Request):确认HTTP方法、URI、规范头、SignedHeaders、请求体哈希均正确。
  2. 待签名字符串(String to Sign):格式为AWS4-HMAC-SHA256\n{X-Amz-Date}\n{日期戳}/{区域}/bedrock-runtime/aws4_request\n{规范请求的SHA256哈希}。
  3. 签名密钥生成:使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 20:48:13