Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Java/Groovy example of using Amazon AWS AWS4Signer class to sign requests (in our case elasticsearch calls)
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
}
}
// 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