You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AWS CloudFront接口Postman调用正常,Spring WebClient返回400错误

问题描述

我有一个部署在AWS CloudFront上的API,使用Postman测试时调用正常,但通过Spring WebClient调用该API时,返回400 Bad Request错误。

Postman请求情况

Postman请求正常,请求截图如下:
请求截图

WebClient调用返回的错误响应

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>
<H1>400 ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">
Bad request.
We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
<BR clear="all">
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
<BR clear="all">
<HR noshade size="1px">
<PRE>
Generated by cloudfront (CloudFront)
Request ID: pGx0NHPSaU3H2EHJwtQrYEdLDjL_UPxO90esPoH3d9efZX_bvzjFQw==
</PRE>
<ADDRESS>
</ADDRESS>
</BODY></HTML>

我的WebClient代码

@Component
public class SyncTranscriberClient {

    private final WebClient webClient;

    @Value("${transcriber.sync.username}")
    private String username;

    @Value("${transcriber.sync.password}")
    private String password;

    public SyncTranscriberClient(@Value("${transcriber.sync.base-url}") String baseUrl) throws SSLException {
        var sslContext = SslContextBuilder
                .forClient()
                .trustManager(InsecureTrustManagerFactory.INSTANCE)
                .build();

        var client = HttpClient.create().secure(t -> t.sslContext(sslContext));

        webClient = WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(client))
                .baseUrl(baseUrl)
                .filter(logRequest())
                .build();
    }

    private static ExchangeFilterFunction logRequest() {
        return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
            log.info("Request: {} {}", clientRequest.method(), clientRequest.url());
            clientRequest.headers().forEach((name, values) -> values.forEach(value -> log.info("{}={}", name, value)));
            return Mono.just(clientRequest);
        });
    }

    public Map<String, String> getCredentials() {
        return webClient.post()
                .uri("my-url-hidden-for-security-reasons")
                .headers(headers -> headers.setBasicAuth(username, password))
                .headers(headers -> headers.set("Accept", "*/*"))
                .headers(headers -> headers.set("Host", "hidden-for-security-reasons"))
                .retrieve()
                .onStatus(HttpStatus::isError, response -> response.bodyToMono(String.class) // error body as String or other class
                        .flatMap(error -> Mono.error(new RuntimeException(error)))
                )
                .toBodilessEntity()
                .map(HttpEntity::getHeaders)
                .map(headers -> Map.of(
                        "Authorization", Objects.requireNonNull(headers.getFirst("Authorization")),
                        "JSESSIONID", Objects.requireNonNull(headers.getFirst("JSESSIONID"))
                ))
                .block();
    }

}

请问我哪里操作出错了?


可能的问题及解决方法

1. 请求Body缺失或格式不匹配

Postman调用时可能自动添加了默认Body(比如空Body),但你的WebClient调用使用.toBodilessEntity(),未发送任何请求Body。如果后端API要求POST请求必须包含Body(哪怕是空的),CloudFront或源服务器会返回400。

解决方法:
添加空Body或匹配Postman的Body内容,示例:

.webClient.post()
    .uri("your-uri")
    // ...其他配置
    .bodyValue("") // 或根据实际需求传对应格式的Body
    .retrieve()
    // ...后续逻辑

2. Host头设置问题

手动设置的Host头可能与CloudFront分发的域名不匹配,或者和Postman实际发送的Host头不一致,导致CloudFront校验失败。

解决方法:

  • 对比Postman中的Host头值,确保代码里的Host头完全一致;
  • 尝试移除手动设置的Host头,让WebClient根据请求URL自动生成:
// 删除这一行手动设置Host的代码
// .headers(headers -> headers.set("Host", "hidden-for-security-reasons"))

3. Basic Auth编码不一致

WebClient的setBasicAuth会自动对用户名密码做Base64编码,但如果Postman中你是手动输入编码后的Authorization值(而非用Postman自动生成),可能存在字符集或编码逻辑差异。

解决方法:

  • 对比WebClient日志输出的Authorization头和Postman中的对应值是否完全一致;
  • 若不一致,手动构造Basic Auth头:
import java.util.Base64;
import java.nio.charset.StandardCharsets;

// ...
String auth = Base64.getEncoder().encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8));
.headers(headers -> headers.set("Authorization", "Basic " + auth))

4. 请求头遗漏或重复设置

对照Postman的请求头和WebClient日志输出的头,检查是否有遗漏(比如Content-Type)或重复设置的头导致冲突。

解决方法:

  • 补充缺失的请求头;
  • 将所有头设置合并到一个.headers()调用中,避免重复覆盖:
.headers(headers -> {
    headers.setBasicAuth(username, password);
    headers.set("Accept", "*/*");
    // 其他需要的头统一在这里添加
})

5. CloudFront WAF或缓存拦截

CloudFront关联的WAF规则可能将WebClient请求识别为异常流量拦截,或者旧缓存配置导致错误。

解决方法:

  • 检查CloudFront的WAF规则,确认是否有拦截该请求的规则;
  • 尝试Invalidate CloudFront缓存,或临时关闭WAF测试是否能正常调用。

内容的提问来源于stack exchange,提问作者Drunken Daddy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 05:20:32