Java中的AWS授权

时间:2017-05-18 21:03:42

标签: java amazon-web-services authorization

在我的Java应用程序中,我想在URL上创建GET,这需要我使用AWS签名进行授权(AccessKey和SecretKey)。基本上,我希望在Postman中做相当于GET的授权类型是AWS签名

是否有关于如何执行此操作的Java特定教程?

我在网上看到了这个课程:

public class AWSV4Auth {

private final static Logger logger = LogManager.getLogger(AWSV4Auth.class);

private AWSV4Auth() {
}

public static class Builder {

    private String accessKeyID;
    private String secretAccessKey;
    private String regionName;
    private String serviceName;
    private String httpMethodName;
    private String canonicalURI;
    private TreeMap<String, String> queryParametes;
    private TreeMap<String, String> awsHeaders;
    private String payload;
    private boolean debug = false;

    public Builder(String accessKeyID, String secretAccessKey) {
        this.accessKeyID = accessKeyID;
        this.secretAccessKey = secretAccessKey;
    }

    public Builder regionName(String regionName) {
        this.regionName = regionName;
        return this;
    }

    public Builder serviceName(String serviceName) {
        this.serviceName = serviceName;
        return this;
    }

    public Builder httpMethodName(String httpMethodName) {
        this.httpMethodName = httpMethodName;
        return this;
    }

    public Builder canonicalURI(String canonicalURI) {
        this.canonicalURI = canonicalURI;
        return this;
    }

    public Builder queryParametes(TreeMap<String, String> queryParametes) {
        this.queryParametes = queryParametes;
        return this;
    }

    public Builder awsHeaders(TreeMap<String, String> awsHeaders) {
        this.awsHeaders = awsHeaders;
        return this;
    }

    public Builder payload(String payload) {
        this.payload = payload;
        return this;
    }

    public Builder debug() {
        this.debug = true;
        return this;
    }

    public AWSV4Auth build() {
        return new AWSV4Auth(this);
    }
}

private String accessKeyID;
private String secretAccessKey;
private String regionName;
private String serviceName;
private String httpMethodName;
private String canonicalURI;
private TreeMap<String, String> queryParametes;
private TreeMap<String, String> awsHeaders;
private String payload;
private boolean debug = false;

/* Other variables */
private final String HMACAlgorithm = "AWS4-HMAC-SHA256";
private final String aws4Request = "aws4_request";
private String strSignedHeader;
private String xAmzDate;
private String currentDate;

private AWSV4Auth(Builder builder) {
    accessKeyID = builder.accessKeyID;
    secretAccessKey = builder.secretAccessKey;
    regionName = builder.regionName;
    serviceName = builder.serviceName;
    httpMethodName = builder.httpMethodName;
    canonicalURI = builder.canonicalURI;
    queryParametes = builder.queryParametes;
    awsHeaders = builder.awsHeaders;
    payload = builder.payload;
    debug = builder.debug;

    /* Get current timestamp value.(UTC) */
    xAmzDate = getTimeStamp();
    currentDate = getDate();
}

/**
 * Task 1: Create a Canonical Request for Signature Version 4.
 *
 * @return
 */
private String prepareCanonicalRequest() {
    StringBuilder canonicalURL = new StringBuilder("");

    /* Step 1.1 Start with the HTTP request method (GET, PUT, POST, etc.), followed by a newline character. */
    canonicalURL.append(httpMethodName).append("\n");

    /* Step 1.2 Add the canonical URI parameter, followed by a newline character. */
    canonicalURI = canonicalURI == null || canonicalURI.trim().isEmpty() ? "/" : canonicalURI;
    canonicalURL.append(canonicalURI).append("\n");

    /* Step 1.3 Add the canonical query string, followed by a newline character. */
    StringBuilder queryString = new StringBuilder("");
    if (queryParametes != null && !queryParametes.isEmpty()) {
        for (Map.Entry<String, String> entrySet : queryParametes.entrySet()) {
            String key = entrySet.getKey();
            logger.debug("this key=" + key);
            String value = entrySet.getValue();
            logger.debug("this value=" + value);
            if(key.equals("a%20cl"))
            {
                queryString.append(key).append("=").append(URLEncoder.encode(value)).append("&");
            }
            else
            {
                queryString.append(URLEncoder.encode(key)).append("=").append(URLEncoder.encode(value)).append("&");
            }
        }
        queryString.setLength(Math.max(queryString.length() - 1, 0));

        queryString.append("\n");
    } else {
        queryString.append("\n");
    }
    canonicalURL.append(queryString);

    /* Step 1.4 Add the canonical headers, followed by a newline character. */
    StringBuilder signedHeaders = new StringBuilder("");
    if (awsHeaders != null && !awsHeaders.isEmpty()) {
        for (Map.Entry<String, String> entrySet : awsHeaders.entrySet()) {
            String key = entrySet.getKey();
            String value = entrySet.getValue();
            signedHeaders.append(key).append(";");
            canonicalURL.append(key).append(":").append(value).append("\n");
        }

        /* Note: Each individual header is followed by a newline character, meaning the complete list ends with a newline character. */
        canonicalURL.append("\n");
    } else {
        canonicalURL.append("\n");
    }

    /* Step 1.5 Add the signed headers, followed by a newline character. */
    strSignedHeader = signedHeaders.substring(0, signedHeaders.length() - 1); // Remove last ";"
    canonicalURL.append(strSignedHeader).append("\n");

    /* Step 1.6 Use a hash (digest) function like SHA256 to create a hashed value from the payload in the body of the HTTP or HTTPS. */
    payload = payload == null ? "" : payload;
    canonicalURL.append(generateHex(payload));

    if (debug) {
        System.out.println("##Canonical Request:\n" + canonicalURL.toString());
    }

    return canonicalURL.toString();
}

/**
 * Task 2: Create a String to Sign for Signature Version 4.
 *
 * @param canonicalURL
 * @return
 */
private String prepareStringToSign(String canonicalURL) {
    String stringToSign = "";

    /* Step 2.1 Start with the algorithm designation, followed by a newline character. */
    stringToSign = HMACAlgorithm + "\n";

    /* Step 2.2 Append the request date value, followed by a newline character. */
    stringToSign += xAmzDate + "\n";

    /* Step 2.3 Append the credential scope value, followed by a newline character. */
    stringToSign += currentDate + "/" + regionName + "/" + serviceName + "/" + aws4Request + "\n";

    /* Step 2.4 Append the hash of the canonical request that you created in Task 1: Create a Canonical Request for Signature Version 4. */
    stringToSign += generateHex(canonicalURL);

    if (debug) {
        System.out.println("##String to sign:\n" + stringToSign);
    }

    return stringToSign;
}

/**
 * Task 3: Calculate the AWS Signature Version 4.
 *
 * @param stringToSign
 * @return
 */
private String calculateSignature(String stringToSign) {
    try {
        /* Step 3.1 Derive your signing key */
        byte[] signatureKey = getSignatureKey(secretAccessKey, currentDate, regionName, serviceName);

        /* Step 3.2 Calculate the signature. */
        byte[] signature = HmacSHA256(signatureKey, stringToSign);

        /* Step 3.2.1 Encode signature (byte[]) to Hex */
        String strHexSignature = bytesToHex(signature);
        return strHexSignature;
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    return null;
}

/**
 * Task 4: Add the Signing Information to the Request. We'll return Map of
 * all headers put this headers in your request.
 *
 * @return
 */
public Map<String, String> getHeaders() {

    payload = payload == null ? "" : payload;

    //awsHeaders.put("Content-Type", "application/x-www-form-urlencoded");
    awsHeaders.put("x-amz-content-sha256",generateHex(payload));
    //awsHeaders.put("x-amz-date", xAmzDate);

    /* Execute Task 1: Create a Canonical Request for Signature Version 4. */
    String canonicalURL = prepareCanonicalRequest();

    /* Execute Task 2: Create a String to Sign for Signature Version 4. */
    String stringToSign = prepareStringToSign(canonicalURL);

    /* Execute Task 3: Calculate the AWS Signature Version 4. */
    String signature = calculateSignature(stringToSign);

    if (signature != null) {
        Map<String, String> header = new HashMap<String, String>(0);
        header.put("Content-Type", "application/x-www-form-urlencoded");
        header.put("x-amz-content-sha256",generateHex(payload));
        header.put("x-amz-date", xAmzDate);
        header.put("Authorization", buildAuthorizationString(signature));

        logger.debug("String to Sign=" + buildAuthorizationString(signature));

        if (debug) {
            System.out.println("##Signature:\n" + signature);
            System.out.println("##Header:");
            for (Map.Entry<String, String> entrySet : header.entrySet()) {
                System.out.println(entrySet.getKey() + " = " + entrySet.getValue());
            }
            System.out.println("================================");
        }
        logger.debug("The HDR: " + header);
        return header;
    } else {
        if (debug) { 
            System.out.println("##Signature:\n" + signature);
        }
        return null;
    }
}

/**
 * Build string for Authorization header.
 *
 * @param strSignature
 * @return
 */
private String buildAuthorizationString(String strSignature) {
    return HMACAlgorithm + " "
            + "Credential=" + accessKeyID + "/" + getDate() + "/" + regionName + "/" + serviceName + "/" + aws4Request + ","
            + " SignedHeaders=content-type;host;" + strSignedHeader + ","
            + " Signature=" + strSignature;
    }

/**
 * Generate Hex code of String.
 *
 * @param data
 * @return
 */
private String generateHex(String data) {
    MessageDigest messageDigest;
    try {
        messageDigest = MessageDigest.getInstance("SHA-256");
        messageDigest.update(data.getBytes("UTF-8"));
        byte[] digest = messageDigest.digest();
        return String.format("%064x", new java.math.BigInteger(1, digest));
    } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    return null;
}

/**
 * Apply HmacSHA256 on data using given key.
 *
 * @param data
 * @param key
 * @return
 * @throws Exception
 * @reference:
 * http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-java
 */
private byte[] HmacSHA256(byte[] key, String data) throws Exception {
    String algorithm = "HmacSHA256";
    Mac mac = Mac.getInstance(algorithm);
    mac.init(new SecretKeySpec(key, algorithm));
    return mac.doFinal(data.getBytes("UTF8"));
}

/**
 * Generate AWS signature key.
 *
 * @param key
 * @param date
 * @param regionName
 * @param serviceName
 * @return
 * @throws Exception
 * @reference
 * http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-java
 */
private byte[] getSignatureKey(String key, String date, String regionName, String serviceName) throws Exception {
    byte[] kSecret = ("AWS4" + key).getBytes("UTF8");
    byte[] kDate = HmacSHA256(kSecret, date);
    byte[] kRegion = HmacSHA256(kDate, regionName);
    byte[] kService = HmacSHA256(kRegion, serviceName);
    byte[] kSigning = HmacSHA256(kService, aws4Request);
    return kSigning;
}

final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();

/**
 * Convert byte array to Hex
 *
 * @param bytes
 * @return
 */
private String bytesToHex(byte[] bytes) {
    char[] hexChars = new char[bytes.length * 2];
    for (int j = 0; j < bytes.length; j++) {
        int v = bytes[j] & 0xFF;
        hexChars[j * 2] = hexArray[v >>> 4];
        hexChars[j * 2 + 1] = hexArray[v & 0x0F];
    }
    return new String(hexChars).toLowerCase();
}

/**
 * Get timestamp. yyyyMMdd'T'HHmmss'Z'
 *
 * @return
 */
private String getTimeStamp() {
    DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
    dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));//server timezone
    return dateFormat.format(new Date());
}

private String timestamp() {

    String fmt = "EEE, dd MMM yyyy HH:mm:ss ";
    SimpleDateFormat df = new SimpleDateFormat(fmt, Locale.US);
    df.setTimeZone(TimeZone.getTimeZone("GMT"));

    // Data needed for signature
    String date = df.format(new Date()) + "GMT";

    return date;
}

/**
 * Get date. yyyyMMdd
 *
 * @return
 */
private String getDate() {
    DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
    dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));//server timezone
    return dateFormat.format(new Date());
}
}

在我做GET的课程中,我添加了这个:

write.connectionReqeuestTimeout(60000);
    write.connectionTimeout(60000);      

    //Create headers tree map collection - can specify headers to pass into auth class but we have set mandatory headers in class
    TreeMap<String, String> awsHeaders = new TreeMap<String, String>();

     //Create V4 Auth Headers from Auth Class 
    AWSV4Auth aWSV4Auth = new AWSV4Auth.Builder(helper.getAccessorLogsS3Accesskey(), helper.getAccessorLogsS3Secretkey())
            .regionName("us-east-1")
            .serviceName("s3") // es - elastic search. use your service name
            .httpMethodName("GET") //GET, PUT, POST, DELETE, etc...
            .canonicalURI("s3-api.xxxx.xxxx.xxxx.net") //end point
            .queryParametes(null) //query parameters if anyåç
            .awsHeaders(awsHeaders) //aws header parameters
            .payload(null) // payload if any
            .debug() // turn on the debug mode
            .build();

    /* Get headers calculated for request */
    Map<String, String> header = aWSV4Auth.getHeaders();
    for (Map.Entry<String, String> entrySet : header.entrySet()) {
        String key = entrySet.getKey();
        String value = entrySet.getValue();

        write.header(key, value);
        logger.debug("Header is: " + write.header(key, value));
    }

当我运行它时,我收到了403响应:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. For more information, see REST Authentication and SOAP Authentication for details.</Message><Resource>/metrics-staging-cr-test/</Resource><RequestId>22933b89-23c2-4341-b573-1794bd40c748</RequestId><StringToSign>AWS4-HMAC-SHA256[\n]"
"20170524T141126Z[\n]"
"20170524/us-east-1/s3/aws4_request[\n]"
"9c9315c9a35cad7c1faf753fe9f17d54e8740885f04b8144d14f9d51acb151c0</StringToSign><StringToSignBytes>65 87 83 52 45 72 77 65 67 45 83 72 65 50 53 54 10 50 48 49 55 48 53 50 52 84 49 52 49 49 50 54 90 10 50 48 49 55 48 53 50 52 47 117 115 45 101 97 115 116 45 49 47 115 51 47 97 119 115 52 95 114 101 113 117 101 115 116 10 57 99 57 51 49 53 99 57 97 51 53 99 97 100 55 99 49 102 97 102 55 53 51 102 101 57 102 49 55 100 53 52 101 56 55 52 48 56 56 53 102 48 52 98 56 49 52 52 100 49 52 102 57 100 53 49 97 99 98 49 53 49 99 48</StringToSignBytes><SignatureProvided>303dd3b942db4681d1e0b6786501c41e28a731e716ad58779b3e7e51f3e409ca</SignatureProvided><AWSAccessKeyId>yuKKCFUA6RTEtJGueWK6</AWSAccessKeyId><httpStatusCode>403</httpStatusCode></Error>"

通过与Postman的比较,我的标题似乎设置正确,但在Postman中没有任何问题时被拒绝。

我已经浏览了大量关于这个主题的aws网页,但没有任何运气。请问有人可以提供建议吗?

1 个答案:

答案 0 :(得分:0)

我在标题中没有主持人。当我添加它时,我能够进行身份验证。

这就是现在的样子:

write.connectionReqeuestTimeout(60000);
write.connectionTimeout(60000);      

//Create headers tree map collection - can specify headers to pass into auth class but we have set mandatory headers in class
TreeMap<String, String> awsHeaders = new TreeMap<String, String>();

 //Create V4 Auth Headers from Auth Class 
awsHeaders.put("host", helper.getSoftlayerEndpoint());
AWSV4Auth aWSV4Auth = new AWSV4Auth.Builder(helper.getAccessorLogsS3Accesskey(), helper.getAccessorLogsS3Secretkey())
        .regionName("us-east-1")
        .serviceName("s3") // es - elastic search. use your service name
        .httpMethodName("GET") //GET, PUT, POST, DELETE, etc...
        .canonicalURI("s3-api.xxxx.xxxx.xxxx.net") //end point
        .queryParametes(null) //query parameters if anyåç
        .awsHeaders(awsHeaders) //aws header parameters
        .payload(null) // payload if any
        .debug() // turn on the debug mode
        .build();

/* Get headers calculated for request */
Map<String, String> header = aWSV4Auth.getHeaders();
for (Map.Entry<String, String> entrySet : header.entrySet()) {
    String key = entrySet.getKey();
    String value = entrySet.getValue();

    write.header(key, value);
    logger.debug("Header is: " + write.header(key, value));
}