Spring boot S3AsyncClient-删除授权标头

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的日

我正在尝试使用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;
    }
}