Java-Beispiel für Absicherung eines Requests mittels STS Token

Inzwischen ist der letzte Artikel meiner kleinen Serie auf JAXenter erschienen. Dieser beschäftigt sich mit den Themen Authentisierung und Autorisierung und schließt mit einem kleinen Fazit. In diesem Artikel habe ich auch den Java-Quellcode für das Absichern eines Requests unter Verwendung von Amazon STS versprochen. Hier ist er:

 

package com.axxessio.axx2sls.integration;

import static org.junit.Assert.*;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.security.MessageDigest;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import com.axxessio.axx2sls.integration.to.CredentialsTO;
import com.fasterxml.jackson.databind.ObjectMapper;

public class GetAreasWithLogin {
	private static ObjectMapper mapper = new ObjectMapper();

	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
	}

	@AfterClass
	public static void tearDownAfterClass() throws Exception {
	}

	@Before
	public void setUp() throws Exception {
	}

	@After
	public void tearDown() throws Exception {
	}

	@Test
	public void test() throws Exception {
		byte[] bodyHash = null;
		byte[] requestHash = null;
		byte[] signatureKey = null;
		
		String authHeader = null;
		String body = null;
		String request = null;
		String stringToSign = null;
		
		CredentialsTO cto = null;
		Date now;
		DateFormat dfDateTime = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
		DateFormat dfDate = new SimpleDateFormat("yyyyMMdd");

		CloseableHttpClient httpclient = HttpClients.createDefault();
		CloseableHttpResponse response = null;
		HttpPost httpPost = null;
		HttpEntity entity = null;

		dfDateTime.setTimeZone(TimeZone.getTimeZone("GMT"));
		dfDate.setTimeZone(TimeZone.getTimeZone("GMT"));

		body = "{\"name\" : \"admin\",\"password\" : \"#######################\"}";
		
		httpPost = new HttpPost("https://a4sbwzghm9.execute-api.eu-central-1.amazonaws.com/prd/login/account");
		entity = new ByteArrayEntity(body.getBytes("UTF-8"));
		httpPost.setEntity(entity);

		httpPost.addHeader("Content-Type", "application/json");

		response = httpclient.execute(httpPost);
		
		cto = mapper.readValue(readResponse(response), CredentialsTO.class);

		body = "{\"atos\":[{\"id\":null, \"name\":\"axxessio\", \"folder\":null, \"number\":0, \"size\":0, \"date\":null, \"version\":null}]}";

		httpPost = new HttpPost("https://a4sbwzghm9.execute-api.eu-central-1.amazonaws.com/prd/content/area");
		entity = new ByteArrayEntity(body.getBytes("UTF-8"));
		httpPost.setEntity(entity);
        
		now = new Date(System.currentTimeMillis());
		
		MessageDigest md = MessageDigest.getInstance("SHA-256");
		
		md.update(body.getBytes("UTF-8"));

		bodyHash = md.digest();
		
		request = "POST\n" +
				  "/prd/content/area\n" +
				  "\n" +
				  "content-type:application/json; charset=utf-8\n" +
				  "host:a4sbwzghm9.execute-api.eu-central-1.amazonaws.com\n" + 
				  "x-amz-date:" + dfDateTime.format(now) + "\n" +
				  "\n" +
				  "content-type;host;x-amz-date\n" +
				  byteToHex(bodyHash);
		
		md.update(request.getBytes("UTF-8"));
		
		requestHash = md.digest();

		stringToSign = "AWS4-HMAC-SHA256\n" + dfDateTime.format(now) + "\n" + dfDate.format(now) + "/eu-central-1/execute-api/aws4_request\n" + byteToHex(requestHash);

		signatureKey = getSignatureKey(cto.getSecretAccessKey(), dfDate.format(now), "eu-central-1", "execute-api");
		
		System.out.println("Signature Key: " + byteToHex(signatureKey));
		
		authHeader = "AWS4-HMAC-SHA256 Credential=" + cto.getAccessKeyID() + 
				 	 "/" + dfDate.format(now) + 
				 	 "/eu-central-1/execute-api/aws4_request, " + 
				 	 "SignedHeaders=content-type;host;x-amz-date, Signature=" + 
				 	 byteToHex(HmacSHA256(stringToSign.getBytes("UTF-8"), signatureKey));

System.out.println("Sample data: " + byteToHex(HmacSHA256("AWS4-HMAC-SHA256\n20150830T123600Z\n20150830/us-east-1/service/aws4_request\n816cd5b414d056048ba4f7c5386d6e0533120fb1fcfa93762cf0fc39e2cf19e0","wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY")));
		
		System.out.println("REQUEST\n" + request + "\n");
		System.out.println("STRING_TO_SIGN\n" + stringToSign + "\n");
		System.out.println("SIGNATURE_KEY\n" + byteToHex(signatureKey) + "\n");
		System.out.println("AUTH_HEADER\n" + authHeader + "\n");
		
		httpPost.addHeader("authorization", authHeader);
		httpPost.addHeader("content-type", "application/json; charset=utf-8");
		httpPost.addHeader("host", "a4sbwzghm9.execute-api.eu-central-1.amazonaws.com");
		httpPost.addHeader("x-amz-date", dfDateTime.format(now));
		//httpPost.addHeader("x-amz-security-token", gstResult.getCredentials().getSessionToken());
		httpPost.addHeader("x-amz-security-token", cto.getSessionToken());
		
		response = httpclient.execute(httpPost);
		
		readResponse(response);

		System.out.println("Request Zeitstempel [" + dfDateTime.format(now) + "]");
		
	}

	private String readResponse (CloseableHttpResponse response) throws Exception {
		HttpEntity entity = null;
    	StringBuffer result = new StringBuffer();
		
		try {
		    System.out.println(response.getStatusLine());
		    entity = response.getEntity();
		    // do something useful with the response body
		    
		    BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));

	    	String line = "";
	    	while ((line = rd.readLine()) != null) {
	    		result.append(line);
	    	}		    
		    EntityUtils.consume(entity);
		    
		    System.out.println(result);
		} finally {
		    response.close();
		}
		
		return result.toString();
	}
	
	private byte[] HmacSHA256(String data, String key) throws Exception {
	    return HmacSHA256(data.getBytes("UTF8"), key.getBytes("UTF8"));
	}
	
	private byte[] HmacSHA256(byte[] data, String key) throws Exception {
	    return HmacSHA256(data, key.getBytes("UTF8"));
	}
	
	private byte[] HmacSHA256(String data, byte[] key) throws Exception {
	    return HmacSHA256(data.getBytes("UTF8"), key);
	}
	
	private byte[] HmacSHA256(byte[] data, byte[] key) throws Exception {
	    String algorithm="HmacSHA256";
	    Mac mac = Mac.getInstance(algorithm);
	    mac.init(new SecretKeySpec(key, algorithm));
	    return mac.doFinal(data);
	}

	/**
	 * Wurde erfolgreich validiert mit folgenden Testdaten
	 * key = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"
	 * dateStamp = "20120215"
	 * regionName = "us-east-1"
	 * serviceName = "iam"
	 * @param key
	 * @param dateStamp
	 * @param regionName
	 * @param serviceName
	 * @return
	 * das erwartete Ergebis ist
	 * kSecret  = "41575334774a616c725855746e46454d492f4b374d44454e472b62507852666943594558414d504c454b4559"
	 * kDate    = "969fbb94feb542b71ede6f87fe4d5fa29c789342b0f407474670f0c2489e0a0d"
	 * kRegion  = "69daa0209cd9c5ff5c8ced464a696fd4252e981430b10e3d3fd8e2f197d7a70c"
	 * kService = "f72cfd46f26bc4643f06a11eabb6c0ba18780c19a8da0c31ace671265e3c87fa"
	 * kSigning = "f4780e2d9f65fa895f9c67b32ce1baf0b0d8a43505a000a1a9e090d414db404d"
	 * @throws Exception
	 */
	private 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);

	    System.out.println("Secret:" + byteToHex(kSecret));
	    System.out.println("Date: " + byteToHex(kDate));
	    System.out.println("Region: " + byteToHex(kRegion));
	    System.out.println("Service: " + byteToHex(kService));
	    System.out.println("Signing: " + byteToHex(kSigning));
	    
	    return kSigning;
	}

	public static String uriEncode(CharSequence input, boolean encodeSlash) {
		StringBuilder result = new StringBuilder();
		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 == '/') { result.append(encodeSlash ? "%2F" : ch); } else { result.append(charToHex(ch)); } } return result.toString(); } public static String byteToHex(byte b) { // Returns hex String representation of byte b char hexDigit[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; char[] array = { hexDigit[(b >> 4) & 0x0f], hexDigit[b & 0x0f] };
		return new String(array);
	}

	public static String byteToHex(byte[] b) {
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < b.length; i++) { sb.append(byteToHex(b[i])); } return sb.toString(); } public static String charToHex(char c) { // Returns hex String representation of char c byte hi = (byte) (c >>> 8);
		byte lo = (byte) (c & 0xff);
		return byteToHex(hi) + byteToHex(lo);
	}
}

Autor: Oliver Wronka

Oliver Wronka befasst sich mit Java seit der Version 1.0. Schwerpunkt sind javabasierte Backendsysteme in seiner Funktion als Principal Softwarearchitekt bei der axxessio GmbH mit Sitz in Bonn.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.