有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

如何从Java调用AmazonAWSAPI?

如果我想从Java调用Amazon AWS Rest API,我有什么选择

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

基本上,这是我需要生成的标题:

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

共 (1) 个答案

  1. # 1 楼答案

    <>不是说这是一个完整的列表,但是我会考虑使用诸如“

    ”这样的已建立的库。
    • 官方的AWS SDK v1v2是最新的和全面的,但取决于netty.io和许多其他罐子
    • Apache JClouds依赖于JAXB,JAXB不再是JDK的一部分,但现在可以在maven central单独使用

    但有时,您只需要进行一个简单的调用,而不想在应用程序中引入许多依赖项。你可能想实现剩下的,你自己说吧。生成正确的AWS授权头是最难实现的

    下面是在没有外部依赖的纯java OpenJDK中实现这一点的代码

    它实现了亚马逊AWS API签名版本4的签名过程

    AmazonRequestSignatureV4Utils。爪哇

    package com.frusal.amazonsig4;
    
    import java.nio.charset.StandardCharsets;
    import java.security.MessageDigest;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.stream.Collectors;
    
    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    
    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<>();
                for (Entry<String, String> e : headers.entrySet()) {
                    hashedHeaders.add(e.getKey().toLowerCase());
                    canonicalRequestLines.add(e.getKey().toLowerCase() + ":" + normalizeSpaces(e.getValue().toString()));
                }
                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> strignToSignLines = new ArrayList<>();
                strignToSignLines.add("AWS4-HMAC-SHA256");
                strignToSignLines.add(isoDateTime);
                String credentialScope = isoJustDate + "/" + awsRegion + "/" + awsService + "/aws4_request";
                strignToSignLines.add(credentialScope);
                strignToSignLines.add(canonicalRequestHash);
                String stringToSign = strignToSignLines.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。爪哇

    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>
    

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