-
-
Save janoulle/9ea19a1601a93aeb4097b2e7d16abff4 to your computer and use it in GitHub Desktop.
Java/Groovy example of using Amazon AWS AWS4Signer class to sign requests (in our case elasticsearch calls)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.clario.aws | |
import com.amazonaws.DefaultRequest | |
import com.amazonaws.SignableRequest | |
import com.amazonaws.auth.AWS4Signer | |
import com.amazonaws.auth.AWSCredentialsProvider | |
import com.amazonaws.http.HttpMethodName | |
import groovy.util.logging.Slf4j | |
import org.apache.http.client.utils.URLEncodedUtils | |
import org.springframework.http.HttpHeaders | |
import org.springframework.http.HttpRequest | |
/** | |
* Sign a url using Amazon {@link AWS4Signer}. | |
* | |
* Note 1: if you get a 403 signature error you can put a breakpoint on Amazon's AWS4Signer class (around line 95) | |
* (or turn on debug level on com.amazonaws.auth package ) to capture it's generated canonicalRequest String and compare | |
* it to Amazon's expected string that is returned with the error. | |
* | |
* Note 2: We use Spring so this class signs Spring's HttpHeaders classes but it should be trivial to convert this class | |
* to use whatever REST library you use. | |
* | |
* Note 3: This class is groovy but it would be trivial to make it standard java (or any other JVM-based language) | |
* | |
* @author George Coller | |
*/ | |
@Slf4j | |
public class V4RequestSigner { | |
private final String regionName; | |
private final String serviceName; | |
private final AWSCredentialsProvider awsCredentialsProvider; | |
/** | |
* Test constructor with overridden date | |
*/ | |
V4RequestSigner(AWSCredentialsProvider awsCredentialsProvider, String regionName, String serviceName) { | |
this.regionName = regionName; | |
this.serviceName = serviceName; | |
this.awsCredentialsProvider = awsCredentialsProvider; | |
} | |
public void signRequest(HttpRequest request, byte[] body) { | |
// For some reason AWS signature didn't like 'Content-Length:0' so remove the header | |
if (body == null || body.length == 0) { | |
def contentLengthKey = request.headers.keySet().find { it.equalsIgnoreCase('Content-Length') } | |
request.headers.remove(contentLengthKey) | |
} | |
def signableRequest = makeSignableRequest(request, body) | |
request.headers.clear() | |
request.headers.putAll(signHeaders(signableRequest)) | |
} | |
HttpHeaders signHeaders(SignableRequest<String> signableRequest) { | |
AWS4Signer signer = new AWS4Signer(false) | |
signer.regionName = regionName | |
signer.serviceName = serviceName | |
signer.sign(signableRequest, awsCredentialsProvider.credentials) | |
def headers = new HttpHeaders() | |
signableRequest.headers.each { k, v -> | |
headers.add(k, v) | |
} | |
return headers | |
} | |
SignableRequest<String> makeSignableRequest(HttpRequest httpRequest, byte[] bytes) { | |
def request = new DefaultRequest<String>(serviceName) | |
// Separate URI base and resource path | |
def uri = httpRequest.URI | |
request.setEndpoint(new URI(uri.scheme, null, uri.host, uri.port, '', '', '')) | |
request.setResourcePath(uri.rawPath) | |
URLEncodedUtils.parse(uri, 'UTF-8').each { nameValue -> | |
request.addParameter(nameValue.name, nameValue.value) | |
} | |
request.setHttpMethod(HttpMethodName.valueOf(httpRequest.method.toString())) | |
request.setHeaders(httpRequest.headers.collectEntries { k, v -> [k, v.join(',')] } as Map<String, String>) | |
request.setContent(new ByteArrayInputStream(bytes)) | |
return request | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Snippit of how to inject the AWS4Signer class into a Spring RestTemplate so it signs every REST call: | |
@Bean | |
RestTemplate restTemplate() { | |
def requestFactory = new HttpComponentsClientHttpRequestFactory() | |
requestFactory.setReadTimeout(60_000) | |
requestFactory.setConnectTimeout(5_000) | |
def template = new RestTemplate(requestFactory) | |
template.interceptors.add(new ClientHttpRequestInterceptor() { | |
// In our case we're using us east 1 region and are going against the AWS elasticsearch endpoint | |
final signer = new V4RequestSigner(awsCredentials(), 'us-east-1', 'es') | |
@Override | |
ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { | |
signer.signRequest(request, body) | |
return execution.execute(request, body); | |
} | |
}) | |
return template | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment