L'API AWS peut tirer parti de l'authentification IAM en signant la demande avec AWS Signature V4. Normalement, vous n'avez pas besoin d'être très conscient des spécifications de Signature V4 en utilisant le kit AWS SDK, mais pour diverses raisons, j'ai eu l'occasion d'implémenter Signature V4 par moi-même, je vais donc laisser une note.
Implémenté dans Java 11, le client HTTP utilise le client Apache HttpComponents (https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient).
Le code est copié-collé et traité, il est donc un peu douteux qu'il fonctionnera correctement.
Par souci de clarté, voici un exemple de demande lorsque vous ne signez pas. Ceci est un exemple de demande de recherche utilisant l'API Elasticsearch. Étant donné que cet exemple utilise la méthode POST, les paramètres de requête sont écrits dans le corps HTTP.
Nous signerons cette demande avec AWS Signature V4 et la modifierons afin qu'elle ne reçoive que les API des utilisateurs authentifiés.
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
void reqFunc(){
//Demande de génération
URI uri = new URI(AWS_ELASTICSEARCH_URL + "/index/hoge/_search?");
HttpPost post = new HttpPost(uri);
post.setHeader("Content-Type", "application/json");
post.setEntity(new StringEntity( "Chaîne de requête(réduction)", "UTF-8"));
//Exécuter une requête avec un client HTTP
HttpClient client = HttpClientBuilder.create().build();
CloseableHttpResponse res = (CloseableHttpResponse) client.execute(post);
// ...Traitement pour réponse...
}
AWS Signature V4 est disponible sur le site Web d'Amazon. J'ai obtenu les informations en suivant l'URL ci-dessous. https://docs.aws.amazon.com/ja_jp/general/latest/gr/signature-version-4.html
En outre, des informations sur la façon d'obtenir l'ID de clé d'accès et la clé d'accès secrète requises pour l'authentification sont obtenues à partir de l'URL suivante. https://docs.aws.amazon.com/ja_jp/general/latest/gr/aws-sec-cred-types.html
AWS Signature V4 génère un hachage à l'aide des informations de demande elles-mêmes et de l'ID de clé d'accès / clé d'accès secrète émise à l'avance, et l'attache à l'en-tête de la demande. J'ai fait du hachage encore et encore, et je ne savais pas jusqu'où se trouvait la cible du hachage, alors j'ai fait un essai et une erreur.
Tout d'abord, afin d'obtenir les informations de la requête à envoyer, préparez la classe HttpRequestInterceptor et le client HTTP qui la prend en sandwich. En insérant cette classe lors de la génération d'un client HTTP, vous pouvez insérer un traitement juste avant d'envoyer une requête au serveur.
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.protocol.HttpContext;
public class AmazonizeInterceptor implements HttpRequestInterceptor {
@Override
public void process(HttpRequest request, HttpContext context) throws Exception{
AwsSigner4(request);
}
private void AwsSigner4(HttpRequest request) throws Exception {
/*Implémentez AWS Signature V4 dans cette implémentation de fonction.*/
}
}
--Modification de l'appelant du client HTTP
//...Abréviation...
void reqFunc(){
//...Abréviation...
//Exécuter une requête avec un client HTTP
HttpClient client = HttpClientBuilder.create()
.addInterceptorLast(new AmazonizeInterceptor()) //← Ajouter ceci
.build();
CloseableHttpResponse res = (CloseableHttpResponse) client.execute(post);
//...Traitement pour réponse...
}
Désormais, lorsque vous effectuez une requête avec un client HTTP prenant en sandwich RequestInterceptor, la fonction AwsSigner4 définie précédemment sera exécutée avant d'être envoyée au serveur.
Ensuite, nous implémenterons la fonction AwsSigner4 qui effectue le processus de signature réel. Tout ce que vous avez à faire est d'ajouter "X-Amz-Date" et "Authorization" à l'en-tête de la requête, mais il faut plusieurs étapes pour trouver la valeur de Authorization.
Pour référence, host, x-amz-date est requis pour les informations d'en-tête incluses dans le canonicalRequest, mais il semble que d'autres informations d'en-tête puissent être ajoutées.
import org.apache.http.util.EntityUtils;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.text.SimpleDateFormat;
private void AwsSigner4(HttpRequest request) throws Exception {
/* X-Amz-Génération d'en-tête de date*/
SimpleDateFormat xAmzDateFormatter = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
xAmzDateFormatter.setTimeZone(Calendar.getInstance(TimeZone.getTimeZone("UTC"))); //La date d'expiration du signataire est déterminée par UTC
String xAmzDate = xAmzDateFormatter.format(new Date()).trim();
request.setHeader("X-Amz-Date", xAmzDate);
/*Génération de chaîne d'en-tête d'autorisation*/
/* 1.Chaîne de demande régulière(canonicalRequest)Génération*/
String path = getPath(request);
String xAmzContentSha = getBodyHash(request);
String canonicalRequest = "POST\n"
+ path + "\n"
+ "\n"
+ "host:" + request.getFirstHeader("Host").getValue() + "\n"
+ "x-amz-date:" + xAmzDate + "\n"
+ "\n"
+ "host;x-amz-date\n"
+ xAmzContentSha;
/* 2.Chaîne de signe(StringToSign)Génération*/
String awsRegion = "ap-northeast-1" ; //Informations sur la région de la destination de la demande d'API AWS
String awsNameSpace = "es" ; //Espace de nom du service AWS demandeur
String StringToSign = "AWS4-HMAC-SHA256\n"
+ xAmzDate + "\n"
+ xAmzDate.substring(0, 8) + "/" + awsRegion
+ "/" + awsNameSpace +"/aws4_request\n"
+ DigestUtils.sha256Hex(canonicalRequest);
/* 3.Clé de signature(SigningKey)Génération*/
String awsSecretAccessKey = "Clé d'accès secrète AWS" ;
// X-Amz-Date → Région → Espace de noms du service AWS → Caractère fixe(aws4_request)Hash dans l'ordre de
String hashStr = getHmacSha256ByStrKey("AWS4" + awsSecretAccessKey, xAmzDate.substring(0, 8));
hashStr = getHmacSha256ByHexKey(hashStr, awsRegion);
hashStr = getHmacSha256ByHexKey(hashStr, awsNameSpace);
String SigningKey = getHmacSha256ByHexKey(hashStr, "aws4_request");
/* 4.Génération de chaîne d'en-tête d'autorisation*/
String awsAccessKeyId = "ID de clé d'accès AWS" ;
String sig = getHmacSha256ByHexKey(SigningKey, StringToSign);
String authorization = "AWS4-HMAC-SHA256 Credential="
+ awsAccessKeyId
+ "/" + xAmzDate.substring(0, 8)
+ "/" + awsRegion
+ "/" + awsNameSpace
+ "/aws4_request,"
+ "SignedHeaders=host;x-amz-date,"
+ "Signature=" + sig;
request.setHeader("Authorization", authorization);
}
/***Obtenir le chemin de la demande*/
private String getPath(HttpRequest req) throws Exception {
String uri = req.getRequestLine().getUri();
//"Séparateur avec chaîne de requête URL"?Ne pas inclure dans le chemin
if (uri.endsWith("?")) uri = uri.substring(0, uri.length()-1);
//Il semble qu'il existe différents types de codage d'URL, et il encode avec la logique spécifiée par Amazon.
return awsUriEncode(uri,true);
}
/***Obtenez du hasch de lix et du corps*/
private String getBodyHash(HttpRequest req) throws Exception{
HttpEntityEnclosingRequest ereq = (HttpEntityEnclosingRequest) req;
String body = EntityUtils.toString(ereq.getEntity());
return DigestUtils.sha256Hex(body);
}
/***
*Encodeur d'URL de spécification AWS
* @param input
* @param encodeSlash
* @return
* @throws UnsupportedEncodingException
*/
private String awsUriEncode(CharSequence input, boolean encodeSlash) throws UnsupportedEncodingException {
StringBuilder result = new StringBuilder();
boolean queryIn = false;
for (int i = 0; i < input.length(); i++) {
char ch = input.charAt(i);
if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' || ch == '~' || ch == '.') {
result.append(ch);
} else if (ch == '/') {
if (queryIn) result.append(encodeSlash ? "%2F" : ch);
else result.append(ch);
} else {
if(!queryIn && ch=='?') {
queryIn = true;
result.append(ch);
}else {
byte[] bytes = new String(new char[] {ch}).getBytes("UTF-8");
result.append("%" + Hex.encodeHexString(bytes,false));
}
}
}
return result.toString();
}
private String getHmacSha256ByStrKey(String strkey, String target) throws Exception {
return getHmacSha256(strkey.getBytes(), target);
}
private String getHmacSha256ByHexKey(String hexkey, String target) throws Exception {
return getHmacSha256(Hex.decodeHex(hexkey), target);
}
/***
*HMAC utilisant la clé comme chaîne cible-SHA-Hash à 256
* @Param target Hash target
* @touche clé param
* @retourne la valeur de hachage au format hexadécimal
*/
private String getHmacSha256(byte[] key, String target) throws Exception {
final Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key, "HmacSHA256"));
return String.valueOf(Hex.encodeHex(mac.doFinal(target.getBytes()), true));
}
Le résumé final est le suivant. Un formateur est généré pour chaque requête et des constantes sont intégrées, mais veuillez le corriger.
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.codec.binary.Hex;
import java.text.SimpleDateFormat;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class AmazonizeInterceptor implements HttpRequestInterceptor {
@Override
public void process(HttpRequest request, HttpContext context) throws Exception{
AwsSigner4(request);
}
private void AwsSigner4(HttpRequest request) throws Exception {
/* X-Amz-Génération d'en-tête de date*/
SimpleDateFormat xAmzDateFormatter = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
xAmzDateFormatter.setTimeZone(Calendar.getInstance(TimeZone.getTimeZone("UTC"))); //La date d'expiration du signataire est déterminée par UTC
String xAmzDate = xAmzDateFormatter.format(new Date()).trim();
request.setHeader("X-Amz-Date", xAmzDate);
/*Génération de chaîne d'en-tête d'autorisation*/
/* 1.Chaîne de demande régulière(canonicalRequest)Génération*/
String path = getPath(request);
String xAmzContentSha = getBodyHash(request);
String canonicalRequest = "POST\n"
+ path + "\n"
+ "\n"
+ "host:" + request.getFirstHeader("Host").getValue() + "\n"
+ "x-amz-date:" + xAmzDate + "\n"
+ "\n"
+ "host;x-amz-date\n"
+ xAmzContentSha;
/* 2.Chaîne de signe(StringToSign)Génération*/
String awsRegion = "ap-northeast-1" ; //Informations sur la région de la destination de la demande d'API AWS
String awsNameSpace = "es" ; //Espace de nom du service AWS demandeur
String StringToSign = "AWS4-HMAC-SHA256\n"
+ xAmzDate + "\n"
+ xAmzDate.substring(0, 8) + "/" + awsRegion
+ "/" + awsNameSpace +"/aws4_request\n"
+ DigestUtils.sha256Hex(canonicalRequest);
/* 3.Clé de signature(SigningKey)Génération*/
String awsSecretAccessKey = "Clé d'accès secrète AWS" ;
// X-Amz-Date → Région → Espace de noms du service AWS → Caractère fixe(aws4_request)Hash dans l'ordre de
String hashStr = getHmacSha256ByStrKey("AWS4" + awsSecretAccessKey, xAmzDate.substring(0, 8));
hashStr = getHmacSha256ByHexKey(hashStr, awsRegion);
hashStr = getHmacSha256ByHexKey(hashStr, awsNameSpace);
String SigningKey = getHmacSha256ByHexKey(hashStr, "aws4_request");
/* 4.Génération de chaîne d'en-tête d'autorisation*/
String awsAccessKeyId = "ID de clé d'accès AWS" ;
String sig = getHmacSha256ByHexKey(SigningKey, StringToSign);
String authorization = "AWS4-HMAC-SHA256 Credential="
+ awsAccessKeyId
+ "/" + xAmzDate.substring(0, 8)
+ "/" + awsRegion
+ "/" + awsNameSpace
+ "/aws4_request,"
+ "SignedHeaders=host;x-amz-date,"
+ "Signature=" + sig;
request.setHeader("Authorization", authorization);
}
/***Obtenir le chemin de la demande*/
private String getPath(HttpRequest req) throws Exception {
String uri = req.getRequestLine().getUri();
//"Séparateur avec chaîne de requête URL"?Ne pas inclure dans le chemin
if (uri.endsWith("?")) uri = uri.substring(0, uri.length()-1);
//Il semble qu'il existe différents types de codage d'URL, et il encode avec la logique spécifiée par Amazon.
return awsUriEncode(uri,true);
}
/***Obtenez du hasch de lix et du corps*/
private String getBodyHash(HttpRequest req) throws Exception{
HttpEntityEnclosingRequest ereq = (HttpEntityEnclosingRequest) req;
String body = EntityUtils.toString(ereq.getEntity());
return DigestUtils.sha256Hex(body);
}
/***
*Encodeur d'URL de spécification AWS
* @param input
* @param encodeSlash
* @return
* @throws UnsupportedEncodingException
*/
private String awsUriEncode(CharSequence input, boolean encodeSlash) throws UnsupportedEncodingException {
StringBuilder result = new StringBuilder();
boolean queryIn = false;
for (int i = 0; i < input.length(); i++) {
char ch = input.charAt(i);
if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' || ch == '-' || ch == '~' || ch == '.') {
result.append(ch);
} else if (ch == '/') {
if (queryIn) result.append(encodeSlash ? "%2F" : ch);
else result.append(ch);
} else {
if(!queryIn && ch=='?') {
queryIn = true;
result.append(ch);
}else {
byte[] bytes = new String(new char[] {ch}).getBytes("UTF-8");
result.append("%" + Hex.encodeHexString(bytes,false));
}
}
}
return result.toString();
}
private String getHmacSha256ByStrKey(String strkey, String target) throws Exception {
return getHmacSha256(strkey.getBytes(), target);
}
private String getHmacSha256ByHexKey(String hexkey, String target) throws Exception {
return getHmacSha256(Hex.decodeHex(hexkey), target);
}
/***
*HMAC utilisant la clé comme chaîne cible-SHA-Hash à 256
* @Param target Hash target
* @touche clé param
* @retourne la valeur de hachage au format hexadécimal
*/
private String getHmacSha256(byte[] key, String target) throws Exception {
final Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key, "HmacSHA256"));
return String.valueOf(Hex.encodeHex(mac.doFinal(target.getBytes()), true));
}
}
Cette fois, je n'ai confirmé que le fonctionnement de l'API Elasticsearch, mais si les autorisations IAM sont définies correctement, je pense que d'autres API peuvent être utilisées de la même manière. S'il existe un service AWS dont le fonctionnement a été confirmé, je vous serais reconnaissant de bien vouloir l'écrire dans les commentaires.
J'espère que tu trouves cela utile.
Recommended Posts