1.加密工具类
package com.ota.V7;import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;/*** AWS V4 签名处理工具** 参考链接:https://docs.aws.amazon.com/zh_cn/general/latest/gr/sigv4_signing.html*/
public class AWSV4Auth {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();}/*** 任务 1:针对签名版本 4 创建规范请求** @return*/private String prepareCanonicalRequest() {StringBuilder canonicalURL = new StringBuilder("");/* Step 1.1 以HTTP方法(GET, PUT, POST, etc.)开头, 然后换行. */canonicalURL.append(httpMethodName).append("\n");/* Step 1.2 添加URI参数,换行. */canonicalURI = canonicalURI == null || canonicalURI.trim().isEmpty() ? "/" : canonicalURI;canonicalURL.append(canonicalURI).append("\n");/* Step 1.3 添加查询参数,换行. */StringBuilder queryString = new StringBuilder("");if (queryParametes != null && !queryParametes.isEmpty()) {for (Map.Entry<String, String> entrySet : queryParametes.entrySet()) {String key = entrySet.getKey();String value = entrySet.getValue();queryString.append(key).append("=").append(encodeParameter(value)).append("&");}queryString.deleteCharAt(queryString.lastIndexOf("&"));queryString.append("\n");} else {queryString.append("\n");}canonicalURL.append(queryString);/* Step 1.4 添加headers, 每个header都需要换行. */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");}canonicalURL.append("\n");} else {canonicalURL.append("\n");}/* Step 1.5 添加签名的headers并换行. */strSignedHeader = signedHeaders.substring(0, signedHeaders.length() - 1); // 删掉最后的 ";"canonicalURL.append(strSignedHeader).append("\n");/* Step 1.6 对HTTP或HTTPS的body进行SHA256处理. */payload = payload == null ? "" : payload;canonicalURL.append(generateHex(payload));if (debug) {System.out.println("##Canonical Request:\n" + canonicalURL.toString());}return canonicalURL.toString();}/*** 任务 2:创建签名版本 4 的待签字符串** @param canonicalURL* @return*/private String prepareStringToSign(String canonicalURL) {String stringToSign = "";/* Step 2.1 以算法名称开头,并换行. */stringToSign = HMACAlgorithm + "\n";/* Step 2.2 添加日期,并换行. */stringToSign += xAmzDate + "\n";/* Step 2.3 添加认证范围,并换行. */stringToSign += currentDate + "/" + regionName + "/" + serviceName + "/" + aws4Request + "\n";/* Step 2.4 添加任务1返回的规范URL哈希处理结果,然后换行. */stringToSign += generateHex(canonicalURL);if (debug) {System.out.println("##String to sign:\n" + stringToSign);}return stringToSign;}/*** 任务 3:为 AWS Signature 版本 4 计算签名** @param stringToSign* @return*/private String calculateSignature(String stringToSign) {try {/* Step 3.1 生成签名的key */byte[] signatureKey = getSignatureKey(secretAccessKey, currentDate, regionName, serviceName);/* Step 3.2 计算签名. */byte[] signature = HmacSHA256(signatureKey, stringToSign);/* Step 3.2.1 对签名编码处理 */String strHexSignature = bytesToHex(signature);return strHexSignature;} catch (Exception ex) {ex.printStackTrace();}return null;}/***任务 4:将签名信息添加到请求并返回headers** @return*/public Map<String, String> getHeaders() {awsHeaders.put("x-amz-date", xAmzDate);/* 执行任务 1: 创建aws v4签名的规范请求字符串. */String canonicalURL = prepareCanonicalRequest();/* 执行任务 2: 创建用来认证的字符串 4. */String stringToSign = prepareStringToSign(canonicalURL);/* 执行任务 3: 计算签名. */String signature = calculateSignature(stringToSign);if (signature != null) {Map<String, String> header = new HashMap<String, String>(0);header.put("x-amz-date", xAmzDate);header.put("Authorization", 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("================================");}return header;} else {if (debug) {System.out.println("##Signature:\n" + signature);}return null;}}/*** 连接前几步处理的字符串生成Authorization header值.** @param strSignature* @return*/private String buildAuthorizationString(String strSignature) {return HMACAlgorithm + " "+ "Credential=" + accessKeyID + "/" + getDate() + "/" + regionName + "/" + serviceName + "/" + aws4Request + ", "+ "SignedHeaders=" + strSignedHeader + ", "+ "Signature=" + strSignature;}public static Map<String, String> getauth(String uri, String method, TreeMap<String, String> params, String data,String session_id,String access_id,String secret_key,String region,String service){TreeMap<String, String> awsHeaders = new TreeMap<String, String>();awsHeaders.put("cache-control", "no-cache");
// awsHeaders.put("content-type", "application/json; charset=utf-8");awsHeaders.put("host", "api.volotea.com");awsHeaders.put("x-api-key", "9797bcb6b2f5439ba68d021328-mobile");awsHeaders.put("x-backend-version","6.33.1");//awsHeaders.put("x-app-version","3.91.1");awsHeaders.put("x-client-session", session_id);awsHeaders.put("x-client-type", "xamarin-android");return new Builder(access_id, secret_key).regionName(region).serviceName(service).httpMethodName(method).canonicalURI(uri).queryParametes(params).awsHeaders(awsHeaders)
// .payload(data)
// .debug().build().getHeaders();}public static Map<String, String> webgetauth(String uri, String method, TreeMap<String, String> params, String data,String session_id,String access_id,String secret_key,String region,String service){TreeMap<String, String> awsHeaders = new TreeMap<String, String>();awsHeaders.put("accept", "application/json");awsHeaders.put("host","api.volotea.com");
// awsHeaders.put("content-type", "application/json; charset=utf-8");awsHeaders.put("x-api-key", "e94ab0cb5c614fc3b1ce49d89f6a-spa");return new Builder(access_id, secret_key).regionName(region).serviceName(service).httpMethodName(method).canonicalURI(uri).queryParametes(params).awsHeaders(awsHeaders)
// .payload(data)
// .debug().build().getHeaders();}public static Map<String, String> webauth(String uri, String method, TreeMap<String, String> params, String data,String session_id,String access_id,String secret_key,String region,String service){TreeMap<String, String> awsHeaders = new TreeMap<String, String>();awsHeaders.put("accept", "application/json");awsHeaders.put("x-api-key", "e94ab0cb5c614fc3b1ce49d89f6a-spa");awsHeaders.put("host","api.volotea.com");
// awsHeaders.put("Content-Length",data.length()+"");
// awsHeaders.put("User-Agent","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36");
// awsHeaders.put("content-Type","application/json");
// awsHeaders.put("Origin","https://book.volotea.com");
// awsHeaders.put("Referer","https://book.volotea.com/");
// awsHeaders.put("Accept-Encoding","gzip, deflate, br");
// awsHeaders.put("Accept-Language","zh-CN,zh;q=0.9");// awsHeaders.put("x-backend-version", "5.126.19");
// awsHeaders.put("x-client-session", session_id);
// awsHeaders.put("x-client-type", "xamarin-iphone");return new Builder(access_id, secret_key).regionName(region).serviceName(service).httpMethodName(method).canonicalURI(uri).queryParametes(params).awsHeaders(awsHeaders).payload(data)
// .debug().build().getHeaders();}public static Map<String, String> auth(String uri, String method, TreeMap<String, String> params, String data,String session_id,String access_id,String secret_key,String region,String service){TreeMap<String, String> awsHeaders = new TreeMap<String, String>();
// awsHeaders.put("cache-control", "no-cache");
// awsHeaders.put("content-length", data.length()+"");
// awsHeaders.put("content-type", "application/json");String amzDate = ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'"));awsHeaders.put("accept", "application/json");awsHeaders.put("host", "api.volotea.com");awsHeaders.put("x-amz-date", amzDate);awsHeaders.put("x-api-key", "e94ab0cb5c614fc3b1ce49d89f6a-spa");System.out.println(amzDate);
// awsHeaders.put("x-backend-version","6.33.1");
// awsHeaders.put("x-app-version","3.91.1");
// awsHeaders.put("x-client-session", session_id);// awsHeaders.put("x-client-type","xamarin-android");return new Builder(access_id, secret_key).regionName(region).serviceName(service).httpMethodName(method).canonicalURI(uri).queryParametes(params).awsHeaders(awsHeaders).payload(data)
// .debug().build().getHeaders();}/*** 将字符串16进制化.** @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;}/*** 以给定的key应用HmacSHA256算法处理数据.** @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"));}/*** 生成AWS 签名** @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();/*** 将字节数组转换为16进制字符串** @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();}/*** 获取yyyyMMdd'T'HHmmss'Z'格式的当前时间** @return*/public static String getTimeStamp() {DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));//server timezonereturn dateFormat.format(new Date());}/*** 获取yyyyMMdd格式的当前日期** @return*/public static String getDate() {DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));//server timezonereturn dateFormat.format(new Date());}/*** UTF-8编码* @param param* @return*/private String encodeParameter(String param){try {return URLEncoder.encode(param, "UTF-8");} catch (Exception e) {return URLEncoder.encode(param);}}
}
2.使用
这里我们使用一个post
请求来做示例,首先分析Authorization
的结构,
AWS4-HMAC-SHA256 Credential=AKIATLNGK7P7OIQKOYM4/20240919/eu-west-1/execute-api/aws4_request, SignedHeaders=accept;host;x-amz-date;x-api-key, Signature=b4ecf8deb72cbf54acabfd072f4e614adb3469e253d8ca7200c129b70372e072
AKIATLNGK7P7OIQKOYM4
代表access_ideu-west-1
代表区域execute-api
代表serviceSignedHeaders
代表签名请求头Signature
是通过 请求路径、方法、请求头、请求体、密钥等参数计算出来的
SignedHeaders的顺序要保持一致,如下图,我对accept;``host;``x-amz-date;``x-api-key
进行签名
测试
String entity="[{\"passengerKey\":\"MCFBRFQ-\",\"name\":{\"first\":\"SHUAIYIN\",\"last\":\"LAI\",\"title\":\"MR\"},\"passengerType\":\"ADT\",\"addAsFriend\":false,\"contactDetails\":{\"email\":\"flyoneu2@163.com\",\"phone\":{\"prefix\":\"+44\",\"nationalNumber\":\"13525659648\"}},\"hasPRM\":false}]";Map<String, String> auth = AWSV4Auth.auth("/api/spa/voe/v1/passengers", "POST", null, entity, "19202f7517c.4d619202f7517c.315-19202f7517b.27d19202f7517e.112", "AKIATLNGK7P7OIQKOYM4", "Aen88bM77IeAx2Llv/vsb+qdt8fm6eSm5aAv4SUF", "eu-west-1", "execute-api");System.out.println(auth.get("Authorization"));