使用AWS对Sign Spring WebClient的HTTP请求进行签名

8
我想要在Spring的响应式WebClient发出的HTTP请求中AWS sign。为了签名请求,我需要访问以下内容: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());
    }
}

在早期的Spring版本中,我们使用RestTemplateClientHttpRequestInterceptor。在这种情况下,正文的字节被公开,因此可以进行签名。
我看到在WebClient的情况下,Spring将正文处理为Publisher,所以我不确定ExchangeFilterFunction是否是一个好的起点。
我应该如何签署HTTP请求?

你不能通过request.body().toString().getBytes()来获取字节吗? - Toerktumlare
现在是否可能? - Snigdhajyoti
2
据我所知没有这种方法。在发送请求之前,我通过对请求体进行序列化并计算签名标头来解决了这个问题。 - Martin Tarjányi
2
你能发一段代码片段吗? - Snigdhajyoti
我知道这个问题已经过时了,但是有人在这个问题上有突破吗? - bsferreira
显示剩余2条评论
1个回答

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()

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

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

这太棒了,正是我想要的!AWS文档根本没法用,但你帮了我们大忙。非常感谢你。 - smlgbl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接