Spring boot S3AsyncClient-删除授权标头
我正在尝试使用S3AsyncClient下载带有预签名URL的S3对象。但是,请求仍然带有一个授权头,其中包括一个签名,该签名由一个与此特定S3实例不兼容的密码生成(这是一个模拟AWS S3对象存储的企业S3实例)。这会导致SSLException。当我在浏览器中或通过邮递员在未经授权的情况下发送请求时,URL会起作用 我是否可以在未经授权的情况下通过S3AsyncClient、x-amz-content-sha256和x-amz-Date头发送这种带有预签名URL的GET请求 预签名URL的日志:Spring boot S3AsyncClient-删除授权标头,spring-boot,amazon-s3,aws-java-sdk,Spring Boot,Amazon S3,Aws Java Sdk,我正在尝试使用S3AsyncClient下载带有预签名URL的S3对象。但是,请求仍然带有一个授权头,其中包括一个签名,该签名由一个与此特定S3实例不兼容的密码生成(这是一个模拟AWS S3对象存储的企业S3实例)。这会导致SSLException。当我在浏览器中或通过邮递员在未经授权的情况下发送请求时,URL会起作用 我是否可以在未经授权的情况下通过S3AsyncClient、x-amz-content-sha256和x-amz-Date头发送这种带有预签名URL的GET请求 预签名URL的日
Generating pre-signed URL.
Pre-Signed URL: https://<namespace>.<endpoint>/<bucket>/<key>?AWSAccessKeyId=<access-id>01&Expires=1600801209&Signature=......../..................=
常规AmazonS3客户端的客户端(用于GenerateDesign DurlRequest):
为什么需要S3AsyncClient来下载文件?url包含所有内容,您可以使用任何非aws特定工具下载it@TamásSallai,好问题-我可以使用WebClient作为InputStreamSource下载它,但我不确定这是否是最佳做法,并且希望能够使用Mono类型下载它。为什么需要S3AsyncClient下载该文件?url包含所有内容,您可以使用任何非aws特定工具下载it@TamásSallai,好问题-我可以使用WebClient作为InputStreamSource下载它,但我不确定这是否是最佳实践,我希望能够使用Mono类型下载它。
GET /<bucket>/VADRUserGuide.docx?Signature=........%2F..................%3D&AWSAccessKeyId=<access-id>&Expires=1600801209 HTTP/1.1
Host: <namespace>.<endpoint>
amz-sdk-invocation-id: ........-....-....-....-............
amz-sdk-retry: 3/152/440
Authorization: AWS4-HMAC-SHA256 Credential=access-id>/20200922/us-east-1/s3/aws4_request, SignedHeaders=amz-sdk-invocation-id;amz-sdk-retry;authorization;host;x-amz-content-sha256;x-amz-date, Signature=.................................................
User-Agent: aws-sdk-java/2.10.86 Windows_10/10.0 Java_HotSpot_TM__64-Bit_Server_VM/25.191-b12 Java/1.8.0_191 vendor/Oracle_Corporation io/async http/UNKNOWN
x-amz-content-sha256: UNSIGNED-PAYLOAD
X-Amz-Date: 20200922T213232Z)
/**
* @author Philippe
*
*/
@RestController
@RequestMapping("/inbox")
@Slf4j
public class DownloadResource {
private final GeneratePresignedURL generatePresignedURL;
private final S3AsyncClient s3client;
private final S3ClientConfigurationProperties s3config;
public DownloadResource(S3AsyncClient s3client, S3ClientConfigurationProperties s3config, GeneratePresignedURL generatePresignedURL) {
this.s3client = s3client;
this.s3config = s3config;
this.generatePresignedURL = generatePresignedURL;
}
@GetMapping(path="/{filekey}")
public Mono<ResponseEntity<Flux<ByteBuffer>>> downloadFile(@PathVariable("filekey") String filekey) {
//generate pre-signed URL
final String url = generatePresignedURL.getPresignedUrl(filekey);
//extract signed URL params
final String pattern = "(\\?|\\&)([^=]+)\\=([^&]+)";
List<String> params = new ArrayList<>();
Matcher m = Pattern.compile(pattern)
.matcher(url);
while (m.find()) {
params.add(m.group());
}
final String[] p0 = params.get(0).substring(1).split("=",2);
final String[] p1 = params.get(1).substring(1).split("=",2);
final String[] p2 = params.get(2).substring(1).split("=",2);
//attach signed URL params via AwsRequestOverrideConfiguration
AwsRequestOverrideConfiguration overrideConfiguration = AwsRequestOverrideConfiguration.builder()
.putRawQueryParameter(p0[0], p0[1])
.putRawQueryParameter(p1[0], p1[1])
.putRawQueryParameter(p2[0], p2[1])
.build();
GetObjectRequest request = GetObjectRequest.builder()
.key(filekey)
.overrideConfiguration(overrideConfiguration)
.bucket(s3config.getBucket())
.build();
return Mono.fromFuture(s3client.getObject(request,new FluxResponseProvider()))
.map( (response) -> {
checkResult(response.sdkResponse);
String filename = getMetadataItem(response.sdkResponse,"filename",filekey);
log.info("[I65] filename={}, length={}",filename, response.sdkResponse.contentLength() );
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_TYPE, response.sdkResponse.contentType())
.header(HttpHeaders.CONTENT_LENGTH, Long.toString(response.sdkResponse.contentLength()))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
.body(response.flux);
});
}
private void printRequestFields(GetObjectRequest request){
System.out.println("request fields:");
String[] parValues = new String[6];
Field[] fields = request.getClass().getDeclaredFields();
//print field names paired with their values
for ( Field field : fields ) {
try {
field.setAccessible(true);
System.out.println( field.getName() +": ");
//requires access to private field:
System.out.println( field.get(request) );
} catch ( IllegalAccessException ex ) {
System.out.println(ex);
}
}
}
/**
* Lookup a metadata key in a case-insensitive way.
* @param sdkResponse
* @param key
* @param defaultValue
* @return
*/
private String getMetadataItem(GetObjectResponse sdkResponse, String key, String defaultValue) {
for( Entry<String, String> entry : sdkResponse.metadata().entrySet()) {
if ( entry.getKey().equalsIgnoreCase(key)) {
return entry.getValue();
}
}
return defaultValue;
}
// Helper used to check return codes from an API call
private static void checkResult(GetObjectResponse response) {
SdkHttpResponse sdkResponse = response.sdkHttpResponse();
if ( sdkResponse != null && sdkResponse.isSuccessful()) {
return;
}
throw new DownloadFailedException(response);
}
static class FluxResponseProvider implements AsyncResponseTransformer<GetObjectResponse,FluxResponse> {
private FluxResponse response;
@Override
public CompletableFuture<FluxResponse> prepare() {
response = new FluxResponse();
return response.cf;
}
@Override
public void onResponse(GetObjectResponse sdkResponse) {
this.response.sdkResponse = sdkResponse;
}
@Override
public void onStream(SdkPublisher<ByteBuffer> publisher) {
response.flux = Flux.from(publisher);
response.cf.complete(response);
}
@Override
public void exceptionOccurred(Throwable error) {
response.cf.completeExceptionally(error);
}
}
/**
* Holds the API response and stream
* @author Philippe
*/
static class FluxResponse {
final CompletableFuture<FluxResponse> cf = new CompletableFuture<>();
GetObjectResponse sdkResponse;
Flux<ByteBuffer> flux;
}
}
@Configuration
@EnableConfigurationProperties(S3ClientConfigurationProperties.class)
public class S3ClientConfiguration {
@Bean
public S3AsyncClient s3client(S3ClientConfigurationProperties s3props) {
SdkAsyncHttpClient httpClient = NettyNioAsyncHttpClient.builder()
.writeTimeout(Duration.ZERO)
.maxConcurrency(64)
.build();
S3Configuration serviceConfiguration = S3Configuration.builder()
.checksumValidationEnabled(false)
.chunkedEncodingEnabled(true)
.build();
S3AsyncClientBuilder b = S3AsyncClient.builder()
.httpClient(httpClient)
.region(s3props.getRegion())
// credentials provider commented out
.serviceConfiguration(serviceConfiguration);
if (s3props.getEndpoint() != null) {
b = b.endpointOverride(s3props.getEndpoint());
}
return b.build();
}
// credentials provider Bean commented out
}
@Configuration
public class S3Configuration {
@Bean
public S3Storage s3Storage(S3ServiceInfo s3ServiceInfo) {
final ClientConfiguration httpsClientConfig = new ClientConfiguration().withProtocol(Protocol.HTTPS).withSignerOverride("S3SignerType");
SSLContext sslContext = SSLContexts.createSystemDefault();
httpsClientConfig.getApacheHttpClientConfig().setSslSocketFactory(new SSLConnectionSocketFactory(sslContext));
AmazonS3 client = AmazonS3ClientBuilder.standard()
.withPathStyleAccessEnabled(true)
.withForceGlobalBucketAccessEnabled(true)
.withClientConfiguration(httpsClientConfig)
.withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(s3ServiceInfo.getAccessKey(), s3ServiceInfo.getSecretKey())))
.withEndpointConfiguration(new EndpointConfiguration(endpointUrlWithNamespace(s3ServiceInfo.getEndpoint(), s3ServiceInfo.getAccessKey()), null))
.build();
return new S3Storage(client, s3ServiceInfo.getBucket(), s3ServiceInfo.getEndpoint());
}
protected String endpointUrlWithNamespace(String endpoint, String accessKey) {
//extract namespace from access-key (verify format)
Matcher matcher = Pattern.compile("((.+?-){3}ns\\d\\d)-").matcher(accessKey);
if (!matcher.find()) return endpoint;
String namespace = matcher.group(1);
return endpoint.contains("://s3") ? endpoint.replace("://", "://" + namespace + ".") : endpoint;
}
@Configuration
public class S3LocalConfiguration {
@Bean
public S3ServiceInfo s3ServiceInfo(
@Value("${s3.accessKey}") String accessKey,
@Value("${s3.secretKey}") String secretKey,
@Value("${s3.bucket}") String bucket,
@Value("${s3.endpoint}") String endpoint) {
return new S3ServiceInfo(null, accessKey, secretKey, endpoint, bucket);
}
}
@Data
@AllArgsConstructor
public static class S3Storage {
private AmazonS3 client;
private String bucket;
private String endpoint;
}
}