问题表现
使用AWS SDK for Java 1.x访问S3,已经确认文件存在,且具有权限,仍然出现403 Forbidden应答。
解决方法
升级到AWS SDK for Java 2.x。
问题原因
AWS签名机制严格依赖请求的精确路径格式,任何URI的差异(如 //
与 /%2F
)都会导致签名校验失败。AWS SDK for Java 1.x版本中,当资源路径 resourcePath
以斜杠开头时(如 /foo/...
),与 endpoint
拼接后会产生双斜杠 //
。SDK内部会将其转义为 /%2F
,导致实际请求路径与签名计算的路径不一致,触发 SignatureDoesNotMatch
错误。
关键代码分析
在AWS SDK for Java 1.x版本中,当调用 httpRequestFactory.create(request, options)
方法时,URL的生成过程涉及路径拼接逻辑与双斜杠转义机制,具体流程如下:
@Override
public HttpRequestBase create(final Request<?> request,
final HttpClientSettings settings)
throws
FakeIOException {
URI endpoint = request.getEndpoint();
String uri;
// skipAppendUriPath is set for APIs making requests with presigned urls. Otherwise
// a slash will be appended at the end and the request will fail
if (request.getOriginalRequest().getRequestClientOptions().isSkipAppendUriPath()) {
uri = endpoint.toString();
} else {
/*
* HttpClient cannot handle url in pattern of "http://host//path", so we
* have to escape the double-slash between endpoint and resource-path
* into "/%2F"
*/
uri = SdkHttpUtils.appendUri(endpoint.toString(), request.getResourcePath(), true);
}
String encodedParams = SdkHttpUtils.encodeParameters(request);
/*
* For all non-POST requests, and any POST requests that already have a
* payload, we put the encoded params directly in the URI, otherwise,
* we'll put them in the POST request's payload.
*/
boolean requestHasNoPayload = request.getContent() != null;
boolean requestIsPost = request.getHttpMethod() == HttpMethodName.POST;
boolean putParamsInUri = !requestIsPost || requestHasNoPayload;
if (encodedParams != null && putParamsInUri) {
uri += "?" + encodedParams;
}
final HttpRequestBase base = createApacheRequest(request, uri, encodedParams);
addHeadersToRequest(base, request);
addRequestConfig(base, request, settings);
return base;
}
假设原始请求参数为
Endpoint: http://127.0.0.1/mybucket
ResourcePath: /foo/bar/... (以斜杠开头)
SdkHttpUtils.appendUri()
将 endpoint
与 resourcePath
拼接为:
http://127.0.0.1/mybucket//foo/bar/...
注意中间的 //
双斜杠。由于第三个参数 escapeDoubleSlash=true
,SDK会将双斜杠转义为 /%2F
:
http://172.24.152.73:80/mybucket/%2Ffoo/bar/...
生成的URI变为转义后的路径,而计算签名时使用的路径是未经转义的原始路径 /mybucket//foo/bar/...
,导致签名不匹配。