RESTクライアントからAPIエンドポイントを呼び出すと、署名に関するエラーが発生しました。
リクエスト:
Host: https://xxx.execute-api.ap-southeast-1.amazonaws.com/latest/api/name
Authorization:AWS4-HMAC-SHA256 Credential =
{AWSKEY}
/20160314/ap-southeast-1/execute-api/aws4_request、SignedHeaders = Host; range; x-amz-date、Signature ={signature}
X-Amz-Date:20160314T102915Z
応答:
{
"message": "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details. The Canonical String for this request should have been 'xxx' "
}
Javaコードから、署名を生成する方法のAWSリファレンスに従いました。
String secretKey = "{mysecretkey}";
String dateStamp = "20160314";
String regionName = "ap-southeast-1";
String serviceName = "execute-api";
byte[] signature = getSignatureKey(secretKey, dateStamp, regionName, serviceName);
System.out.println("Signature : " + Hex.encodeHexString(signature));
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"));
}
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;
}
署名を生成しているときに何が問題だったかわかりますか?
署名の生成方法の参照: http://docs.aws.Amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-Java
Aws-Java-sdk-coreのクラスを使用できます: https://github.com/aws/aws-sdk-Java/tree/master/aws-Java-sdk-core
より具体的には、Request、Aws4Signer、およびその他のいくつかのもの:
//Instantiate the request
Request<Void> request = new DefaultRequest<Void>("es"); //Request to ElasticSearch
request.setHttpMethod(HttpMethodName.GET);
request.setEndpoint(URI.create("http://..."));
//Sign it...
AWS4Signer signer = new AWS4Signer();
signer.setRegionName("...");
signer.setServiceName(request.getServiceName());
signer.sign(request, new AwsCredentialsFromSystem());
//Execute it and get the response...
Response<String> rsp = new AmazonHttpClient(new ClientConfiguration())
.requestExecutionBuilder()
.executionContext(new ExecutionContext(true))
.request(request)
.errorResponseHandler(new SimpleAwsErrorHandler())
.execute(new SimpleResponseHandler<String>());
よりすっきりとしたデザインが必要な場合は、Decoratorパターンを使用していくつかのエレガントなクラスを作成し、上記の混乱を隠すことができます。その例: http://www.amihaiemil.com/2017/02/18/decorators-with-tunnels.html
上記のコード例から、正規のリクエストを作成しておらず、署名された文字列にそれを含めていないようです http://docs.aws.Amazon.com/general/latest/gr/sigv4- create-canonical-request.html
これを自分で実装する代わりに、サードパーティのライブラリを使用することを検討しました。
aws-v4-signer-Java は、軽量で依存関係がなく、AWS V4署名を簡単に生成できるライブラリです。
String contentSha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
HttpRequest request = new HttpRequest("GET", new URI("https://examplebucket.s3.amazonaws.com?max-keys=2&prefix=J"));
String signature = Signer.builder()
.awsCredentials(new AwsCredentials(ACCESS_KEY, SECRET_KEY))
.header("Host", "examplebucket.s3.amazonaws.com")
.header("x-amz-date", "20130524T000000Z")
.header("x-amz-content-sha256", contentSha256)
.buildS3(request, contentSha256)
.getSignature();
免責事項:私は図書館の著者です。
これは100%を使用して可能ですJava追加の依存関係のないライブラリ、ここで生成された クエリパラメータ を使用するだけです:
import Java.security.InvalidKeyException;
import Java.security.NoSuchAlgorithmException;
import Java.security.SignatureException;
import Java.util.Formatter;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import Java.util.Base64;
...
private static final String ACCESS_KEY = "...";
private static final String SECRET_KEY = "...";
private static final int expiresTime = 1 * 24 * 60 * 60;
private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
public void sign(String protocol, String bucketName, String contentPath) throws Exception {
Calendar cal = Calendar.getInstance();
cal.add(Calendar.HOUR_OF_DAY, 24);
String Host = bucketName + ".s3-us-west-2.amazonaws.com";
long expireTime = cal.getTimeInMillis() / 1000;
String signString = "GET\n" +
"\n" +
"\n" +
expireTime + "\n" +
"/" + bucketName + contentPath;
SecretKeySpec signingKey = new SecretKeySpec(SECRET_KEY.getBytes(), HMAC_SHA1_ALGORITHM);
Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
mac.init(signingKey);
String signature = URLEncoder.encode(new String(Base64.getEncoder().encode(mac.doFinal(signString.getBytes()))));
System.out.println(signature);
String fullPayload = "?AWSAccessKeyId=" + ACCESS_KEY +
"&Expires=" + expireTime +
"&Signature=" + signature;
System.out.println(protocol + "://" + Host + contentPath + fullPayload);
}
...
最も簡単な方法は、AmazonのSDKのメソッドとhttp-clientを使用することです。以下の3つのステップに従います。
ステップ1:基本的なAWS認証情報を作成します。
BasicAWSCredentials awsCreds = new BasicAWSCredentials(ACCESS_KEY,AWS_DATASHOP_SECRET_KEY);
ステップ2:signableRequestを作成します。
DefaultRequest<?> signableRequest = new DefaultRequest<>("aws-service-name");
signableRequest.setHttpMethod(HttpMethodName.GET);
signableRequest.setResourcePath("fooo");
signableRequest.setEndpoint(URI.create(baar));
signableRequest.addParameter("execution_id", executionId);
signableRequest.addHeader("Content-Type", "application/json");
signer.sign(signableRequest, awsCreds);
ステップ3:AmazonHttpClientを使用してリクエストを実行します。
new AmazonHttpClient(new ClientConfiguration())
.requestExecutionBuilder()
.executionContext(new ExecutionContext(true))
.request(signableRequest)
.errorResponseHandler((new SimpleAwsErrorHandler()))
.execute(new MyResponseHandler());
HttpResponseHandler
とSimpleAwsErrorHandler
には必ずMyResponseHandler
を実装してください
通常のhttpクライアントを使用する場合は、正規リクエストを作成し、ほとんどの場合一致しない署名を計算する必要があります。
署名プロセスは長く、エラーが発生しやすいので、ここにいくつかのヒントがあります