Spring RestClient调用GCS签名URL报403错误,RestTemplate等可正常工作
问题描述
尝试使用Spring Boot 3.4.4的RestClient通过预签名URL从Google Cloud Storage(GCS)下载文件,同一URL在RestTemplate和原生HttpURLConnection中均可正常工作,但使用RestClient时返回403权限错误。
错误信息
403 Forbidden: <?xml version='1.0' encoding='UTF-8'?><Error> <Code>SignatureDoesNotMatch</Code> <Message>Access denied.</Message> <Details>The request signature we calculated does not match the signature you provided</Details> </Error>
当前使用的代码
var client = RestClient.builder() .defaultHeader("Host", "storage.googleapis.com") .defaultHeader("Accept-Encoding", "identity") .build(); Resource data = client.get() .uri(signedUrlString) // 已验证可用的URL .header(HttpHeaders.CONTENT_LENGTH, "0") .retrieve() .body(Resource.class);
已尝试添加或移除Host、Accept-Encoding、Content-Length等请求头,但问题未解决,询问RestClient是否需要特定配置。
解决方案
GCS预签名URL的签名是严格绑定请求头、请求参数等内容生成的,RestClient默认的一些隐式行为会修改请求结构,导致签名校验失败,以下是关键修复点:
- 禁用自动重定向
RestClient默认会自动处理3xx重定向,但GCS预签名URL仅对原始请求有效,重定向后的请求会触发签名不匹配。关闭自动重定向:
var client = RestClient.builder() .requestFactory(() -> { SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setFollowRedirects(false); return factory; }) .build();
- 移除手动添加的冗余请求头
如果预签名URL生成时未包含Content-Length、Host这类头,手动添加会破坏签名校验的一致性。直接去掉这些自定义头,RestClient会自动从URL解析正确的Host头:
var client = RestClient.builder() .requestFactory(() -> { SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setFollowRedirects(false); return factory; }) .build(); Resource data = client.get() .uri(signedUrlString) .retrieve() .body(Resource.class);
- 明确禁用请求压缩
RestClient可能默认添加Accept-Encoding: gzip,若预签名URL生成时未包含该头,会导致签名不匹配。可以强制设置不使用压缩:
var client = RestClient.builder() .requestFactory(() -> { SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setFollowRedirects(false); return factory; }) .defaultHeader(HttpHeaders.ACCEPT_ENCODING, "identity") .build();
- 避免URL二次编码
确保传递给RestClient的URL没有被二次编码,可先将字符串URL转为URI对象再传入:
URI signedUri = URI.create(signedUrlString); Resource data = client.get() .uri(signedUri) .retrieve() .body(Resource.class);
内容的提问来源于stack exchange,提问作者tschi
相关产品推荐
相关产品推荐

