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
相关产品推荐
相关产品推荐

