gpt4 book ai didi

java - 如何从 Java 进行 Amazon AWS API 调用?

转载 作者:行者123 更新时间:2023-12-01 16:26:07 26 4
gpt4 key购买 nike

如果我想从 Java 调用 Amazon AWS Rest API,我有哪些选择。

在实现我自己的请求时,生成 AWS4-HMAC-SHA256 授权 header 将是最困难的。

本质上,这是我需要生成的 header :

Authorization: AWS4-HMAC-SHA256 Credential=AKIAJTOUYS27JPVRDUYQ/20200602/us-east-1/route53/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=ba85affa19fa4a8735ce952e50d41c8c93406a11d22b88cc98b109b529bcc15e

最佳答案

并不是说这是一个完整的列表,但我会考虑使用已建立的库,例如:

  • 官方AWS SDK v1 ,或v2 - 当前且全面,但取决于 netty.io和许多其他 jar 。
  • Apache JClouds - 依赖于 JAXB,它不再是 JDK 的一部分,但现在可以在 Maven 中心单独使用。

但有时,您只想进行一个简单的调用,并且不想将许多依赖项带入您的应用程序。您可能想自己实现其余的调用。生成正确的 AWS 授权 header 是最难实现的部分。

下面是在纯 Java OpenJDK 中执行此操作的代码,没有外部依赖项。

它实现了Amazon AWS API 签名版本 4 签名流程。

AmazonRequestSignatureV4Utils.java
package com.frusal.amazonsig4;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

/**
* Copyright 2020 Alex Vasiliev, licensed under the Apache 2.0 license: https://opensource.org/licenses/Apache-2.0
*/
public class AmazonRequestSignatureV4Utils {

/**
* Generates signing headers for HTTP request in accordance with Amazon AWS API Signature version 4 process.
* <p>
* Following steps outlined here: <a href="https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html">docs.aws.amazon.com</a>
* <p>
* Simple usage example is here: {@link AmazonRequestSignatureV4Example}
* <p>
* This method takes many arguments as read-only, but adds necessary headers to @{code headers} argument, which is a map.
* The caller should make sure those parameters are copied to the actual request object.
* <p>
* The ISO8601 date parameter can be created by making a call to:<br>
* - {@code java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'").format(ZonedDateTime.now(ZoneOffset.UTC))}<br>
* or, if you prefer joda:<br>
* - {@code org.joda.time.format.ISODateTimeFormat.basicDateTimeNoMillis().print(DateTime.now().withZone(DateTimeZone.UTC))}
*
* @param method - HTTP request method, (GET|POST|DELETE|PUT|...), e.g., {@link java.net.HttpURLConnection#getRequestMethod()}
* @param host - URL host, e.g., {@link java.net.URL#getHost()}.
* @param path - URL path, e.g., {@link java.net.URL#getPath()}.
* @param query - URL query, (parameters in sorted order, see the AWS spec) e.g., {@link java.net.URL#getQuery()}.
* @param headers - HTTP request header map. This map is going to have entries added to it by this method. Initially populated with
* headers to be included in the signature. Like often compulsory 'Host' header. e.g., {@link java.net.HttpURLConnection#getRequestProperties()}.
* @param body - The binary request body, for requests like POST.
* @param isoDateTime - The time and date of the request in ISO8601 basic format, see comment above.
* @param awsIdentity - AWS Identity, e.g., "AKIAJTOUYS27JPVRDUYQ"
* @param awsSecret - AWS Secret Key, e.g., "I8Q2hY819e+7KzBnkXj66n1GI9piV+0p3dHglAzQ"
* @param awsRegion - AWS Region, e.g., "us-east-1"
* @param awsService - AWS Service, e.g., "route53"
*/
public static void calculateAuthorizationHeaders(
String method, String host, String path, String query, Map<String, String> headers,
byte[] body,
String isoDateTime,
String awsIdentity, String awsSecret, String awsRegion, String awsService
) {
try {
String bodySha256 = hex(sha256(body));
String isoJustDate = isoDateTime.substring(0, 8); // Cut the date portion of a string like '20150830T123600Z';

headers.put("Host", host);
headers.put("X-Amz-Content-Sha256", bodySha256);
headers.put("X-Amz-Date", isoDateTime);

// (1) https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
List<String> canonicalRequestLines = new ArrayList<>();
canonicalRequestLines.add(method);
canonicalRequestLines.add(path);
canonicalRequestLines.add(query);
List<String> hashedHeaders = new ArrayList<>();
List<String> headerKeysSorted = headers.keySet().stream().sorted(Comparator.comparing(e -> e.toLowerCase(Locale.US))).collect(Collectors.toList());
for (String key : headerKeysSorted) {
hashedHeaders.add(key.toLowerCase(Locale.US));
canonicalRequestLines.add(key.toLowerCase(Locale.US) + ":" + normalizeSpaces(headers.get(key)));
}
canonicalRequestLines.add(null); // new line required after headers
String signedHeaders = hashedHeaders.stream().collect(Collectors.joining(";"));
canonicalRequestLines.add(signedHeaders);
canonicalRequestLines.add(bodySha256);
String canonicalRequestBody = canonicalRequestLines.stream().map(line -> line == null ? "" : line).collect(Collectors.joining("\n"));
String canonicalRequestHash = hex(sha256(canonicalRequestBody.getBytes(StandardCharsets.UTF_8)));

// (2) https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
List<String> stringToSignLines = new ArrayList<>();
stringToSignLines.add("AWS4-HMAC-SHA256");
stringToSignLines.add(isoDateTime);
String credentialScope = isoJustDate + "/" + awsRegion + "/" + awsService + "/aws4_request";
stringToSignLines.add(credentialScope);
stringToSignLines.add(canonicalRequestHash);
String stringToSign = stringToSignLines.stream().collect(Collectors.joining("\n"));

// (3) https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
byte[] kDate = hmac(("AWS4" + awsSecret).getBytes(StandardCharsets.UTF_8), isoJustDate);
byte[] kRegion = hmac(kDate, awsRegion);
byte[] kService = hmac(kRegion, awsService);
byte[] kSigning = hmac(kService, "aws4_request");
String signature = hex(hmac(kSigning, stringToSign));

String authParameter = "AWS4-HMAC-SHA256 Credential=" + awsIdentity + "/" + credentialScope + ", SignedHeaders=" + signedHeaders + ", Signature=" + signature;
headers.put("Authorization", authParameter);

} catch (Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
} else {
throw new IllegalStateException(e);
}
}
}

private static String normalizeSpaces(String value) {
return value.replaceAll("\\s+", " ").trim();
}

public static String hex(byte[] a) {
StringBuilder sb = new StringBuilder(a.length * 2);
for(byte b: a) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}

private static byte[] sha256(byte[] bytes) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(bytes);
return digest.digest();
}

public static byte[] hmac(byte[] key, String msg) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key, "HmacSHA256"));
return mac.doFinal(msg.getBytes(StandardCharsets.UTF_8));
}

}

以及使用示例:

AmazonRequestSignatureV4Utils.java
package com.frusal.amazonsig4;

import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;

public class AmazonRequestSignatureV4Example {

public static void main(String[] args) throws Exception {
String route53HostedZoneId = "Z08118721NNU878C4PBNA";
String awsIdentity = "AKIAJTOUYS27JPVRDUYQ";
String awsSecret = "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";
String awsRegion = "us-east-1";
String awsService = "route53";

URL url = new URL("https://route53.amazonaws.com/2013-04-01/hostedzone/" + route53HostedZoneId + "/rrset");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
System.out.println(connection.getRequestMethod() + " " + url);

String body = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<ChangeResourceRecordSetsRequest xmlns=\"https://route53.amazonaws.com/doc/2013-04-01/\">\n" +
"<ChangeBatch>\n" +
// " <Comment>optional comment about the changes in this change batch request</Comment>\n" +
" <Changes>\n" +
" <Change>\n" +
" <Action>UPSERT</Action>\n" +
" <ResourceRecordSet>\n" +
" <Name>c001cxxx.frusal.com.</Name>\n" +
" <Type>A</Type>\n" +
" <TTL>300</TTL>\n" +
" <ResourceRecords>\n" +
" <ResourceRecord>\n" +
" <Value>157.245.232.185</Value>\n" +
" </ResourceRecord>\n" +
" </ResourceRecords>\n" +
// " <HealthCheckId>optional ID of a Route 53 health check</HealthCheckId>\n" +
" </ResourceRecordSet>\n" +
" </Change>\n" +
" </Changes>\n" +
"</ChangeBatch>\n" +
"</ChangeResourceRecordSetsRequest>";
byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8);

Map<String, String> headers = new LinkedHashMap<>();
String isoDate = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'").format(ZonedDateTime.now(ZoneOffset.UTC));
AmazonRequestSignatureV4Utils.calculateAuthorizationHeaders(
connection.getRequestMethod(),
connection.getURL().getHost(),
connection.getURL().getPath(),
connection.getURL().getQuery(),
headers,
bodyBytes,
isoDate,
awsIdentity,
awsSecret,
awsRegion,
awsService);

// Unsigned headers
headers.put("Content-Type", "text/xml; charset=utf-8"); // I guess it get modified somewhere on the way... Let's just leave it out of the signature.

// Log headers and body
System.out.println(headers.entrySet().stream().map(e -> e.getKey() + ": " + e.getValue()).collect(Collectors.joining("\n")));
System.out.println(body);

// Send
headers.forEach((key, val) -> connection.setRequestProperty(key, val));
connection.setDoOutput(true);
connection.getOutputStream().write(bodyBytes);
connection.getOutputStream().flush();

int responseCode = connection.getResponseCode();
System.out.println("connection.getResponseCode()=" + responseCode);

String responseContentType = connection.getHeaderField("Content-Type");
System.out.println("responseContentType=" + responseContentType);

System.out.println("Response BODY:");
if (connection.getErrorStream() != null) {
System.out.println(new String(connection.getErrorStream().readAllBytes(), StandardCharsets.UTF_8));
} else {
System.out.println(new String(connection.getInputStream().readAllBytes(), StandardCharsets.UTF_8));
}
}
}
以及它会生成的跟踪:
POST https://route53.amazonaws.com/2013-04-01/hostedzone/Z08118721NNU878C4PBNA/rrset
Host: route53.amazonaws.com
X-Amz-Content-Sha256: 46c7521da55bcf9e99fa6e12ec83997fab53128b5df0fb12018a6b76fb2bf891
X-Amz-Date: 20200602T035618Z
Authorization: AWS4-HMAC-SHA256 Credential=AKIAJTOUYS27JPVRDUYQ/20200602/us-east-1/route53/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=6a59090f837cf71fa228d2650e9b82e9769e0ec13e9864e40bd2f81c682ef8cb
Content-Type: text/xml; charset=utf-8
<?xml version="1.0" encoding="UTF-8"?>
<ChangeResourceRecordSetsRequest xmlns="https://route53.amazonaws.com/doc/2013-04-01/">
<ChangeBatch>
<Changes>
<Change>
<Action>UPSERT</Action>
<ResourceRecordSet>
<Name>c001cxxx.frusal.com.</Name>
<Type>A</Type>
<TTL>300</TTL>
<ResourceRecords>
<ResourceRecord>
<Value>157.245.232.185</Value>
</ResourceRecord>
</ResourceRecords>
</ResourceRecordSet>
</Change>
</Changes>
</ChangeBatch>
</ChangeResourceRecordSetsRequest>
connection.getResponseCode()=200
responseContentType=text/xml
Response BODY:
<?xml version="1.0"?>
<ChangeResourceRecordSetsResponse xmlns="https://route53.amazonaws.com/doc/2013-04-01/"><ChangeInfo><Id>/change/C011827119UYGF04GVIP6</Id><Status>PENDING</Status><SubmittedAt>2020-06-02T03:56:25.822Z</SubmittedAt></ChangeInfo></ChangeResourceRecordSetsResponse>

编辑:更新以对标题进行排序。感谢@Gray 的发现!

有关此代码的最新版本,请参阅java-amazon-request-signature-v4 GitHub 上的存储库。

关于java - 如何从 Java 进行 Amazon AWS API 调用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62144379/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com