Die AWS-API kann die IAM-Authentifizierung nutzen, indem sie die Anforderung mit AWS Signature V4 signiert. Normalerweise müssen Sie die Signature V4-Spezifikationen mithilfe des AWS SDK nicht genau kennen, aber aus verschiedenen Gründen hatte ich die Möglichkeit, Signature V4 selbst zu implementieren, sodass ich eine Notiz hinterlassen werde.
Der in Java 11 implementierte HTTP-Client verwendet den Apache HttpComponents Client (https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient).
Da der Code ausgeschnitten, eingefügt und verarbeitet wird, ist es ein wenig zweifelhaft, ob er ordnungsgemäß funktioniert.
Aus Gründen der Übersichtlichkeit finden Sie hier eine Beispielanforderung, wenn Sie nicht unterschreiben. Dies ist eine Beispielsuchanforderung unter Verwendung der Elasticsearch-API. Da dieses Beispiel die POST-Methode verwendet, werden die Abfrageparameter in den HTTP-Body geschrieben.
Wir werden diese Anforderung mit AWS Signature V4 signieren und so ändern, dass nur APIs von authentifizierten Benutzern empfangen werden.
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(){
//Anforderungsgenerierung
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( "Abfragezeichenfolge(Kürzung)", "UTF-8"));
//Ausführen einer Anforderung mit einem HTTP-Client
HttpClient client = HttpClientBuilder.create().build();
CloseableHttpResponse res = (CloseableHttpResponse) client.execute(post);
// ...Verarbeitung zur Antwort...
}
AWS Signature V4 ist auf der Amazon-Website verfügbar. Ich habe die Informationen erhalten, indem ich der unten stehenden URL gefolgt bin. https://docs.aws.amazon.com/ja_jp/general/latest/gr/signature-version-4.html
Darüber hinaus erhalten Sie Informationen zum Abrufen der für die Authentifizierung erforderlichen Zugriffsschlüssel-ID und des geheimen Zugriffsschlüssels unter der folgenden URL. https://docs.aws.amazon.com/ja_jp/general/latest/gr/aws-sec-cred-types.html
AWS Signature V4 generiert einen Hash unter Verwendung der Anforderungsinformationen selbst und der zuvor ausgegebenen Zugriffsschlüssel-ID / des geheimen Zugriffsschlüssels und hängt ihn an den Anforderungsheader an. Ich habe immer und immer wieder gehasht, und ich wusste nicht, wie weit das Ziel des Hashing war, also habe ich einen Versuch und Irrtum gemacht.
Bereiten Sie zunächst die HttpRequestInterceptor-Klasse und den HTTP-Client vor, der sie einschließt, um die Informationen der zu sendenden Anforderung selbst abzurufen. Durch Einfügen dieser Klasse beim Generieren eines HTTP-Clients können Sie die Verarbeitung unmittelbar vor dem Senden einer Anforderung an den Server einfügen.
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 {
/*Implementieren Sie AWS Signature V4 in dieser Funktionsimplementierung.*/
}
}
//...Abkürzung...
void reqFunc(){
//...Abkürzung...
//Ausführen einer Anforderung mit einem HTTP-Client
HttpClient client = HttpClientBuilder.create()
.addInterceptorLast(new AmazonizeInterceptor()) //← Fügen Sie dies hinzu
.build();
CloseableHttpResponse res = (CloseableHttpResponse) client.execute(post);
//...Verarbeitung zur Antwort...
}
Wenn Sie jetzt eine Anfrage mit einem HTTP-Client stellen, der RequestInterceptor einschließt, wird die zuvor definierte AwsSigner4-Funktion ausgeführt, bevor sie an den Server gesendet wird.
Als Nächstes implementieren wir die AwsSigner4-Funktion, die den eigentlichen Signaturprozess ausführt. Alles, was Sie tun müssen, ist, "X-Amz-Date" und "Authorization" zum Anforderungsheader hinzuzufügen. Es sind jedoch mehrere Schritte erforderlich, um den Wert von Authorization zu ermitteln.
Als Referenz ist Host, x-amz-Datum für die in canonicalRequest enthaltenen Header-Informationen erforderlich, aber es scheint, dass andere Header-Informationen hinzugefügt werden können.
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-Generierung des Datums-Headers*/
SimpleDateFormat xAmzDateFormatter = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
xAmzDateFormatter.setTimeZone(Calendar.getInstance(TimeZone.getTimeZone("UTC"))); //Das Ablaufdatum des Unterzeichners wird von UTC festgelegt
String xAmzDate = xAmzDateFormatter.format(new Date()).trim();
request.setHeader("X-Amz-Date", xAmzDate);
/*Generierung von Autorisierungsheaderzeichenfolgen*/
/* 1.Regelmäßige Anforderungszeichenfolge(canonicalRequest)Generation*/
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.Zeichenfolge(StringToSign)Generation*/
String awsRegion = "ap-northeast-1" ; //Regionsinformationen des AWS API-Anforderungsziels
String awsNameSpace = "es" ; //Namespace des anfordernden AWS-Service
String StringToSign = "AWS4-HMAC-SHA256\n"
+ xAmzDate + "\n"
+ xAmzDate.substring(0, 8) + "/" + awsRegion
+ "/" + awsNameSpace +"/aws4_request\n"
+ DigestUtils.sha256Hex(canonicalRequest);
/* 3.Signierschlüssel(SigningKey)Generation*/
String awsSecretAccessKey = "Geheimer AWS-Zugriffsschlüssel" ;
// X-Amz-Datum → Region → AWS Service-Namespace → Festes Zeichen(aws4_request)Hash in der Reihenfolge von
String hashStr = getHmacSha256ByStrKey("AWS4" + awsSecretAccessKey, xAmzDate.substring(0, 8));
hashStr = getHmacSha256ByHexKey(hashStr, awsRegion);
hashStr = getHmacSha256ByHexKey(hashStr, awsNameSpace);
String SigningKey = getHmacSha256ByHexKey(hashStr, "aws4_request");
/* 4.Generierung von Autorisierungsheaderzeichenfolgen*/
String awsAccessKeyId = "AWS-Zugriffsschlüssel-ID" ;
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);
}
/***Anforderungspfad abrufen*/
private String getPath(HttpRequest req) throws Exception {
String uri = req.getRequestLine().getUri();
//"Trennzeichen mit URL-Abfragezeichenfolge"?Nicht in den Pfad aufnehmen
if (uri.endsWith("?")) uri = uri.substring(0, uri.length()-1);
//Es scheint, dass es verschiedene Arten der URL-Codierung gibt, und sie codiert mit der von Amazon angegebenen Logik.
return awsUriEncode(uri,true);
}
/***Holen Sie sich Hash von Lix und Körper*/
private String getBodyHash(HttpRequest req) throws Exception{
HttpEntityEnclosingRequest ereq = (HttpEntityEnclosingRequest) req;
String body = EntityUtils.toString(ereq.getEntity());
return DigestUtils.sha256Hex(body);
}
/***
*AWS spezifizierter spezifizierter URL-Encoder
* @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 verwendet Key als Zielzeichenfolge-SHA-Hash auf 256
* @param target Hash-Ziel
* @param key key
* @Rückgabewert im Hex-Format
*/
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));
}
Die endgültige Zusammenfassung lautet wie folgt. Für jede Anforderung wird ein Formatierer generiert, und Konstanten werden eingebettet. Bitte korrigieren Sie ihn.
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-Generierung des Datums-Headers*/
SimpleDateFormat xAmzDateFormatter = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
xAmzDateFormatter.setTimeZone(Calendar.getInstance(TimeZone.getTimeZone("UTC"))); //Das Ablaufdatum des Unterzeichners wird von UTC festgelegt
String xAmzDate = xAmzDateFormatter.format(new Date()).trim();
request.setHeader("X-Amz-Date", xAmzDate);
/*Generierung von Autorisierungsheaderzeichenfolgen*/
/* 1.Regelmäßige Anforderungszeichenfolge(canonicalRequest)Generation*/
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.Zeichenfolge(StringToSign)Generation*/
String awsRegion = "ap-northeast-1" ; //Regionsinformationen des AWS API-Anforderungsziels
String awsNameSpace = "es" ; //Namespace des anfordernden AWS-Service
String StringToSign = "AWS4-HMAC-SHA256\n"
+ xAmzDate + "\n"
+ xAmzDate.substring(0, 8) + "/" + awsRegion
+ "/" + awsNameSpace +"/aws4_request\n"
+ DigestUtils.sha256Hex(canonicalRequest);
/* 3.Signierschlüssel(SigningKey)Generation*/
String awsSecretAccessKey = "Geheimer AWS-Zugriffsschlüssel" ;
// X-Amz-Datum → Region → AWS Service-Namespace → Festes Zeichen(aws4_request)Hash in der Reihenfolge von
String hashStr = getHmacSha256ByStrKey("AWS4" + awsSecretAccessKey, xAmzDate.substring(0, 8));
hashStr = getHmacSha256ByHexKey(hashStr, awsRegion);
hashStr = getHmacSha256ByHexKey(hashStr, awsNameSpace);
String SigningKey = getHmacSha256ByHexKey(hashStr, "aws4_request");
/* 4.Generierung von Autorisierungsheaderzeichenfolgen*/
String awsAccessKeyId = "AWS-Zugriffsschlüssel-ID" ;
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);
}
/***Anforderungspfad abrufen*/
private String getPath(HttpRequest req) throws Exception {
String uri = req.getRequestLine().getUri();
//"Trennzeichen mit URL-Abfragezeichenfolge"?Nicht in den Pfad aufnehmen
if (uri.endsWith("?")) uri = uri.substring(0, uri.length()-1);
//Es scheint, dass es verschiedene Arten der URL-Codierung gibt, und sie codiert mit der von Amazon angegebenen Logik.
return awsUriEncode(uri,true);
}
/***Holen Sie sich Hash von Lix und Körper*/
private String getBodyHash(HttpRequest req) throws Exception{
HttpEntityEnclosingRequest ereq = (HttpEntityEnclosingRequest) req;
String body = EntityUtils.toString(ereq.getEntity());
return DigestUtils.sha256Hex(body);
}
/***
*AWS spezifizierter spezifizierter URL-Encoder
* @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 verwendet Key als Zielzeichenfolge-SHA-Hash auf 256
* @param target Hash-Ziel
* @param key key
* @Rückgabewert im Hex-Format
*/
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));
}
}
Dieses Mal habe ich nur den Betrieb der Elasticsearch-API bestätigt. Wenn die IAM-Berechtigungen jedoch korrekt festgelegt sind, können andere APIs auf die gleiche Weise verwendet werden. Wenn es einen AWS-Service gibt, dessen Funktion bestätigt wurde, würde ich es begrüßen, wenn Sie ihn in die Kommentare schreiben könnten.
Ich hoffe, Sie finden es nützlich.
Recommended Posts