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

WebClient调用预签名S3 URL遇400错误:参数被二次编码

解决WebClient调用S3预签名URL时的二次编码问题

问题原因

WebClient的UriBuilder默认会对所有Query参数执行URL编码操作。而S3预签名URL中的X-Amz-Security-Token、X-Amz-Credential等参数本身已经是URL编码后的结果(比如/被编码为%2F),UriBuilder再次编码时会把%字符转换成%25,导致原有的%2F变成%252F,这种二次编码后的参数无法被S3服务器识别,最终返回400 Bad Request。

解决方案

方案1:直接使用完整预签名URL

预签名URL本身就是合法的完整请求地址,无需拆分baseUrl和Query参数,直接传入WebClient的uri()方法即可避免二次编码:

@RestController
public class Demo {

    private final WebClient client;

    @Autowired
    public Demo(WebClient.Builder webClientBuilder) {
        this.client = webClientBuilder.build();
    }

    @GetMapping("/data")
    public String processS3URL() {
        // 替换为实际的完整预签名URL
        String presignedUrl = "https://<base_url>.s3.amazonaws.com/<urlPath>.json?X-Amz-Security-Token=HYVG6%2F202309&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20230913T103914Z&X-Amz-SignedHeaders=host&X-Amz-Expires=172800&X-Amz-Credential=<credential>&X-Amz-Signature=<signature>";
        
        return client.get()
                .uri(presignedUrl)
                .retrieve()
                .bodyToMono(String.class)
                .block();
    }
}

方案2:关闭指定参数的自动编码

如果必须拆分baseUrl和参数,可以使用queryParam的重载方法,指定对已编码的参数不执行二次编码(适用于Spring 5.3及以上版本):

@RestController
public class Demo {

    private final WebClient client;

    @Autowired
    public Demo(WebClient.Builder webClientBuilder) {
        this.client = webClientBuilder
                .baseUrl("https://<base_url>.s3.amazonaws.com")
                .build();
    }

    @GetMapping("/data")
    public String processS3URL() {
        return client.get()
                .uri(uriBuilder -> uriBuilder
                        .path("/<urlPath>.json")
                        // 对已编码的参数设置NEVER编码策略
                        .queryParam("X-Amz-Security-Token", "<securityToken>", UriUtils.EncodingHint.NEVER)
                        .queryParam("X-Amz-Algorithm", "AWS4-HMAC-SHA256")
                        .queryParam("X-Amz-Date", "20230913T103914Z")
                        .queryParam("X-Amz-SignedHeaders", "host")
                        .queryParam("X-Amz-Expires", "172800")
                        .queryParam("X-Amz-Credential", "<credential>", UriUtils.EncodingHint.NEVER)
                        .queryParam("X-Amz-Signature", "<signature>", UriUtils.EncodingHint.NEVER)
                        .build())
                .retrieve()
                .bodyToMono(String.class)
                .block();
    }
}

其他调用方式

  • RestTemplate:如果项目不依赖响应式编程,可以使用RestTemplate,同样需要注意直接传入完整预签名URL,避免二次编码问题。
  • Java原生HttpURLConnection:直接构建HTTP请求连接,手动处理输入输出流,适合简单场景,但代码相对繁琐。
  • AWS SDK for Java:如果需要生成或调用S3资源,也可以使用官方SDK的S3Presigner类生成预签名URL,并通过SDK的客户端直接请求,无需手动处理URL编码。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 21:30:14