使用 AWS 签署 Spring WebClient HTTP 请求

Mar*_*nyi 8 java spring reactive-programming amazon-web-services spring-webflux

我想AWS对由 Spring 的响应式WebClient触发的 HTTP 请求进行签名。要签署请求,我需要访问以下内容:URL、HTTP 方法、查询参数、标头和请求正文字节。

我从编写ExchangeFilterFunction开始。由于ClientRequest接口,我可以访问我需要的所有内容,除了请求正文:

@Component
public class AwsSigningInterceptor implements ExchangeFilterFunction
{
    private final AwsHeaderSigner awsHeaderSigner;

    public AwsSigningInterceptor(AwsHeaderSigner awsHeaderSigner)
    {
        this.awsHeaderSigner = awsHeaderSigner;
    }

    @Override
    public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next)
    {
        Map<String, List<String>> signingHeaders = awsHeaderSigner.createSigningHeaders(request, new byte[]{}, "es", "us-west-2"); // should pass request body bytes in place of new byte[]{}

        ClientRequest.Builder requestBuilder = ClientRequest.from(request);

        signingHeaders.forEach((key, value) -> requestBuilder.header(key, value.toArray(new String[0])));

        return next.exchange(requestBuilder.build());
    }
}
Run Code Online (Sandbox Code Playgroud)

在旧的 Spring 版本中,我们将RestTemplateClientHttpRequestInterceptor一起使用。在这种情况下,主体的字节被暴露,因此可以进行签名。

正如我所看到的,在WebClient Spring 将主体作为Publisher处理时,所以我不确定ExchangeFilterFunction是否是一个好的起点。

我应该如何签署 HTTP 请求?

pix*_*xel 1

我提出了以下解决方案:

import org.springframework.util.MultiValueMapAdapter
import org.springframework.web.reactive.function.client.ClientRequest
import reactor.core.publisher.Mono
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider
import software.amazon.awssdk.auth.signer.Aws4Signer
import software.amazon.awssdk.auth.signer.AwsSignerExecutionAttribute
import software.amazon.awssdk.core.interceptor.ExecutionAttributes
import software.amazon.awssdk.http.SdkHttpFullRequest
import software.amazon.awssdk.http.SdkHttpMethod
import software.amazon.awssdk.regions.Region
import java.util.function.Function

class WebClientAwsSigner(
    private val body: String? = null,
    private val signer: Aws4Signer,
    private val awsCredentialsProvider: AwsCredentialsProvider,
    private val serviceName: String,
    private val region: Region,
) : Function<ClientRequest, Mono<ClientRequest>> {

    override fun apply(request: ClientRequest): Mono<ClientRequest> {
        val requestBuilder = SdkHttpFullRequest.builder()
            .method(SdkHttpMethod.fromValue(request.method().name()))
            .uri(request.url())
            .apply {
                if (body != null) {
                    contentStreamProvider { body.byteInputStream() }
                }
            }
            .headers(request.headers())

        val attributes = ExecutionAttributes()
        attributes.putAttribute(
            AwsSignerExecutionAttribute.AWS_CREDENTIALS,
            awsCredentialsProvider.resolveCredentials(),
        )
        attributes.putAttribute(AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME, serviceName)
        attributes.putAttribute(AwsSignerExecutionAttribute.SIGNING_REGION, region)

        val signedAwsRequest = signer.sign(requestBuilder.build(), attributes)
        val signedClientRequest =
            ClientRequest.from(request)
                .headers {
                    it.clear()
                    it.addAll(MultiValueMapAdapter(signedAwsRequest.headers()))
                }
                .build()

        return Mono.just(signedClientRequest)
    }
}

    private fun signedAwsWebClient(body: String? = null): WebClient = webclient
        .mutate().filter(
            ExchangeFilterFunction.ofRequestProcessor(
                WebClientAwsSigner(body, Aws4Signer.create(), DefaultCredentialsProvider.create(), "es", Region.US_EAST_1),
            ),
        )
        .build()
Run Code Online (Sandbox Code Playgroud)

GitHub: https: //github.com/kkocel/webclient-signed-request-to-aws