【问题标题】:Generating an AWS Signature v4 signature for uploading to s3 (migration from v2)生成 AWS Signature v4 签名以上传到 s3(从 v2 迁移)
【发布时间】:2018-03-03 07:56:59
【问题描述】:

我目前有一个工作实现如下:

UI 选择一个文件 => 点击上传 => 调用我的后端 API 来请求签名,因为我不想公开我的访问权限 + 密钥 => 返回签名 + 策略 => 上传到 s3。

这适用于 v2。

String base64Policy = (new BASE64Encoder()).encode(policy.toString().getBytes("UTF-8")).replaceAll("\n", "").replaceAll("\r", "");

        Mac hmac = Mac.getInstance("HmacSHA1");
        hmac.init(new SecretKeySpec(secretKey.getBytes("UTF-8"), "HmacSHA1"));
        String signature = (new BASE64Encoder()).encode(hmac.doFinal(base64Policy.getBytes("UTF-8"))).replaceAll("\n", "");

现在我开始有趣了,我的新存储桶位于不支持 v2 的区域。

我一直在关注 AWS 文档,但我认为我对有效负载有点误解。我真的需要让我的 UI 传入整个文件的 sha256 哈希吗?因为这似乎有点痛苦,特别是因为我的文件可能 > 1 gig。

我尝试使用的代码:

        byte[] signatureKey = getSignatureKey(secretKey, LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")),  bucketRegion, "s3");
        StringBuilder sb = new StringBuilder();
        for (byte b : signatureKey) {
            sb.append(String.format("%02X", b));
        }

private static byte[] getSignatureKey(String key, String dateStamp, String regionName, String serviceName) throws Exception {
        byte[] kSecret = ("AWS4" + key).getBytes("UTF8");
        byte[] kDate = HmacSHA256(dateStamp, kSecret);
        byte[] kRegion = HmacSHA256(regionName, kDate);
        byte[] kService = HmacSHA256(serviceName, kRegion);
        byte[] kSigning = HmacSHA256("aws4_request", kService);
        return kSigning;
    }



private static byte[] HmacSHA256(String data, byte[] key) throws Exception {
        String algorithm="HmacSHA256";
        Mac mac = Mac.getInstance(algorithm);
        mac.init(new SecretKeySpec(key, algorithm));
        return mac.doFinal(data.getBytes("UTF8"));
    }

但是当我尝试使用我的其余代码时,这会给出无效的签名响应。

我是不是这么难听,只是误会:https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html

任何帮助都将不胜感激,因为我一直在反对这种方式太久了,我不想进行太多的大修。

【问题讨论】:

标签: java amazon-web-services amazon-s3


【解决方案1】:

您可以使用标准 SDK 方法将文件上传到 S3 而无需生成签名,请参阅documentation。 但是如果您出于某种原因需要签名,我认为,生成签名的最简单方法是使用 AWS SDK 中的方法,请参阅以下扩展 AWS4Signer 的类:

public class AwsAuthUtil extends AWS4Signer {
    private String serviceName;
    private AWSCredentials credentials;
    private String region;

    public AwsAuthUtil(AWSCredentials credentials, String region, String serviceName) {
        this.credentials = credentials;
        this.region = region;
        this.serviceName = serviceName;
    }

    public String getSignature(String policy, LocalDateTime dateTime) {
        try {
            String dateStamp = dateTime.format(ofPattern("yyyyMMdd"));
            return Hex.encodeHexString(hmacSha256(newSigningKey(credentials, dateStamp, region, serviceName), policy));
        } catch (Exception e) {
            throw new RuntimeException("Error", e);
        }
    }

    private byte[] hmacSha256(byte[] key, String data) throws Exception {
        Mac mac = Mac.getInstance(SigningAlgorithm.HmacSHA256.name());
        mac.init(new SecretKeySpec(key, SigningAlgorithm.HmacSHA256.name()));
        return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
    }
}

AWS4Signer 来自哪里

<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk-s3</artifactId>
    <version>1.11.213</version>
</dependency>

AWSCredentials 可以构建为

AWSCredentials awsCredentials = new BasicAWSCredentials(s3AccessKey, s3SecretKey);

另外你应该在使用多部分数据时考虑http headers,例如,请参见下面的构建HttpEntity的方法

public HttpEntity buildPostMultipartDataEntity(String objectKey, byte[] data, String signature, LocalDateTime dateTime) {

    String dateTimeStr = dateTime.format(ofPattern("yyyyMMdd'T'HHmmss'Z'"));
    String date = dateTime.format(ofPattern("yyyyMMdd"));

    return MultipartEntityBuilder
        .create()
        .addTextBody("key", objectKey)
        .addTextBody("Policy", policy)
        .addTextBody("X-Amz-Signature", signature)
        .addTextBody("X-Amz-Algorithm", algorithm)
        .addTextBody("X-Amz-Date", dateTimeStr)
        .addTextBody("X-Amz-Credential", String.format("%s/%s/%s/s3/aws4_request", accessKey, date, region))
        .addBinaryBody("file", data)
        .build();
}

【讨论】:

  • 这给了我:我们计算的请求签名与您提供的签名不匹配。检查您的密钥和签名方法。在这种情况下,输入的策略是什么?在我的旧代码中是:JsonObject policy = Json.createObjectBuilder()... 然后是 base64 位。
  • 在我的例子中,它是一个带有 { "expiration": "2099-12-30T12:00:00.000Z", "conditions": [ {"bucket": "bucket-name" }, ["starts-with", "$key", ""], ["starts-with", "$x-amz-date", ""], ["starts-with", "$x-amz -credential", ""], {"x-amz-algorithm": "AWS4-HMAC-SHA256"} ] } 我把它读作 String policy = StreamUtils.copyToString((new ClassPathResource("policy.txt")) .getInputStream(), UTF_8); Base64.getEncoder().encodeToString(policy.getBytes("UTF8"));
  • @status 这对你有用吗?是否可以将您的项目代码示例(相关位)邮寄给我/将代码复制到类文件并将其上传到某个地方以便我自己测试它?会省去我很多头痛。谢谢
  • @KristofPlennings 我用所需的 http 标头更新了答案,以后可以提供一些测试文件。
  • @Status 抱歉回复晚了。互联网死了。我生成签名的原因是我想做 UI => S3 存储桶。否则我会遇到整个 30 年代的连接断开。所以 java 位所做的就是生成我的前端使用的签名+策略。
猜你喜欢
  • 1970-01-01
  • 2021-07-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-30
  • 1970-01-01
  • 2019-03-11
  • 1970-01-01
相关资源
最近更新 更多