All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.darkatra.feign.sdkv1.AwsSignatureV4RequestInterceptor.kt Maven / Gradle / Ivy

package de.darkatra.feign.sdkv1

import com.amazonaws.DefaultRequest
import com.amazonaws.auth.AWS4Signer
import com.amazonaws.auth.AWSCredentialsProvider
import com.amazonaws.http.HttpMethodName
import com.amazonaws.regions.Region
import de.darkatra.feign.common.AwsSignatureV4Constants
import feign.RequestInterceptor
import feign.RequestTemplate
import java.io.ByteArrayInputStream
import java.net.URI
import java.util.stream.Collectors

/**
 * Feign [RequestInterceptor] that signs http requests with Signature V4. Uses the AWS SDK V1.
 *
 * @param awsCredentialsProvider the [AWSCredentialsProvider] used to sign the request
 * @param service the service to sign the requests for. Use 'execute-api' if you're issuing requests against AWS API Gateway.
 * @param region the region
 */
class AwsSignatureV4RequestInterceptor(
    private val awsCredentialsProvider: AWSCredentialsProvider,
    private val service: String,
    private val region: Region
) : RequestInterceptor {

    private val aws4Signer: AWS4Signer = AWS4Signer().apply {
        serviceName = service
        regionName = region.name
    }

    override fun apply(template: RequestTemplate) {

        val body = template.body()
        val url = template.feignTarget().url()
        val path = template.path()

        // convert the RequestTemplate to an SdkHttpFullRequest to delegate the signing process to the AWS SDK
        val request = DefaultRequest(service).apply {
            endpoint = URI.create(
                when {
                    url.endsWith("/") -> url
                    else -> "$url/"
                }
            )
            resourcePath = when {
                path.startsWith("/") -> path.removePrefix("/")
                else -> path
            }
            parameters = convertQueryParameters(template.queries())
            httpMethod = HttpMethodName.fromValue(template.method())
            headers = convertHeaders(template.headers())
            content = body?.let { ByteArrayInputStream(it) }
        }

        aws4Signer.sign(request, awsCredentialsProvider.credentials)

        // copy amazon specific headers over to the request template
        request.headers.entries.stream()
            .filter { header -> AwsSignatureV4Constants.HEADERS_TO_COPY.contains(header.key) }
            .forEach { header -> template.header(header.key, header.value) }
    }

    private fun convertQueryParameters(queryParameters: Map>): MutableMap> {
        return queryParameters.entries.stream()
            .map { header -> header.key to header.value.toList() }
            .collect(Collectors.toMap(Pair>::first, Pair>::second))
    }

    private fun convertHeaders(headers: Map>): Map {
        return headers.entries.stream()
            .map { header -> header.key to header.value.toList() }
            .collect(Collectors.toMap(Pair>::first) { header ->
                header.second.joinToString(",")
            })
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy