Spring Boot Amazon AWS S3 存储桶文件下载 - 拒绝访问

Posted

技术标签:

【中文标题】Spring Boot Amazon AWS S3 存储桶文件下载 - 拒绝访问【英文标题】:Spring Boot Amazon AWS S3 Bucket File Download - Access Denied 【发布时间】:2016-07-25 23:34:49 【问题描述】:

我有一个自动配置的 AWS、Spring Boot 应用程序,并且我正在尝试设置一个端点,该端点将简单地从 Amazon S3 中的给定存储桶下载特定文件。我使用 AWS 控制台将 JPEG 文件从我的计算机上传到存储桶中 - 现在我正在尝试使用我的 Spring Boot API 下载该文件。

我收到以下错误:com.amazonaws.services.s3.model.AmazonS3Exception: Access Denied (Service: Amazon S3; Status Code: 403; Error Code: AccessDenied;

我在 AWS 控制台上创建了一个用户和一个组(用户在组中);用户/组具有 S3 的完全访问权限以及管理员访问权限。我下载了访问密钥/秘密密钥对,出于测试目的,将密钥按字面意思粘贴到了我的 application.properties 文件中,如下所示(显然,这里没有显示密钥:))。

我很困惑为什么我仍然被拒绝访问。我一直在搜索和研究这个问题。我似乎无法找到特定于 Spring Boot 的此问题的解决方案。任何帮助将不胜感激。

application.properties:

cloud.aws.credentials.accessKey=myaccesskey
cloud.aws.credentials.secretKey=mysecretkey
cloud.aws.credentials.instanceProfile=false
cloud.aws.stack.auto=false

cloud.aws.region.auto=true
cloud.aws.region.static=myregion

SimpleResourceLoadingBean.java:

@RestController
public class SimpleResourceLoadingBean 

    private static Logger log = LoggerFactory.getLogger(HealthMonitorApplication.class);

    @Autowired
    private ResourceLoader resourceLoader;


    @RequestMapping("/getresource")
    public String resourceLoadingMethod() throws IOException 
        log.info("IN RESOURCE LOADER");

        Resource resource = this.resourceLoader.getResource("s3://s3.amazonaws.com/mybucket/myfile.ext");

        InputStream inputStream = resource.getInputStream();

        return inputStream.toString();
    

pom.xml(只是与问题相关的依赖项)

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-aws</artifactId>
            <version>1.1.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-aws-autoconfigure</artifactId>
            <version>1.1.0.RELEASE</version>
        </dependency>

【问题讨论】:

将 cloud.aws.credentials.instanceProfile=false 更改为 true 并检查它是否有效? 尝试并失败:1 上面的Spring应用是在本地还是在EC2实例上? 应用程序是本地的。 您能否在没有本地凭据的情况下粘贴您在此文件 ~/.aws/credentials 中的内容。 【参考方案1】:

想出了解决办法。除了 application.properties 配置之外,我还必须创建一个配置类,当提供适当的凭证时,它可以让我访问 AmazonS3Client 对象。我在 GitHub 上遵循了这个示例:

https://github.com/brant-hwang/spring-cloud-aws-example/blob/master/src/main/java/com/axisj/spring/cloud/aws/AWSConfiguration.java

AWSConfiguration.java:

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3Client;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AWSConfiguration 

    @Value("$cloud.aws.credentials.accessKey")
    private String accessKey;

    @Value("$cloud.aws.credentials.secretKey")
    private String secretKey;

    @Value("$cloud.aws.region")
    private String region;

    @Bean
    public BasicAWSCredentials basicAWSCredentials() 
        return new BasicAWSCredentials(accessKey, secretKey);
    

    @Bean
    public AmazonS3Client amazonS3Client(AWSCredentials awsCredentials) 
        AmazonS3Client amazonS3Client = new AmazonS3Client(awsCredentials);
        amazonS3Client.setRegion(Region.getRegion(Regions.fromName(region)));
        return amazonS3Client;
    

配置完成后,您可以在其他类中创建 AmazonS3Client 对象(自动装配),并使用客户端向您的 S3 云发出请求。该示例使用包装类作为服务,以简化其他控制器类的实现。

S3Wrapper.java:

import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.*;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Service
public class S3Wrapper 

    @Autowired
    private AmazonS3Client amazonS3Client;

    @Value("$cloud.aws.s3.bucket")
    private String bucket;

    private PutObjectResult upload(String filePath, String uploadKey) throws FileNotFoundException 
        return upload(new FileInputStream(filePath), uploadKey);
    

    private PutObjectResult upload(InputStream inputStream, String uploadKey) 
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucket, uploadKey, inputStream, new ObjectMetadata());

        putObjectRequest.setCannedAcl(CannedAccessControlList.PublicRead);

        PutObjectResult putObjectResult = amazonS3Client.putObject(putObjectRequest);

        IOUtils.closeQuietly(inputStream);

        return putObjectResult;
    

    public List<PutObjectResult> upload(MultipartFile[] multipartFiles) 
        List<PutObjectResult> putObjectResults = new ArrayList<>();

        Arrays.stream(multipartFiles)
                .filter(multipartFile -> !StringUtils.isEmpty(multipartFile.getOriginalFilename()))
                .forEach(multipartFile -> 
                    try 
                        putObjectResults.add(upload(multipartFile.getInputStream(), multipartFile.getOriginalFilename()));
                     catch (IOException e) 
                        e.printStackTrace();
                    
                );

        return putObjectResults;
    

    public ResponseEntity<byte[]> download(String key) throws IOException 
        GetObjectRequest getObjectRequest = new GetObjectRequest(bucket, key);

        S3Object s3Object = amazonS3Client.getObject(getObjectRequest);

        S3ObjectInputStream objectInputStream = s3Object.getObjectContent();

        byte[] bytes = IOUtils.toByteArray(objectInputStream);

        String fileName = URLEncoder.encode(key, "UTF-8").replaceAll("\\+", "%20");

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        httpHeaders.setContentLength(bytes.length);
        httpHeaders.setContentDispositionFormData("attachment", fileName);

        return new ResponseEntity<>(bytes, httpHeaders, HttpStatus.OK);
    

    public List<S3ObjectSummary> list() 
        ObjectListing objectListing = amazonS3Client.listObjects(new ListObjectsRequest().withBucketName(bucket));

        List<S3ObjectSummary> s3ObjectSummaries = objectListing.getObjectSummaries();

        return s3ObjectSummaries;
    

注意:需要将以下依赖项添加到 pom.xml 才能使用 Apache Commons IO 库。

pom.xml:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-io</artifactId>
    <version>1.3.2</version>
</dependency>

【讨论】:

但是 spring-boot 通过将 EnableAutoConfiguration 注释添加到您的配置类之一来提供自动配置。那么为什么不配置 aws。 当我从这里下载文件时,它是一个二进制文件,而不是我使用的 png 这是很久以前的事了,但如果你想在你的 java 代码中读取它,你可能不得不使用图像阅读器或类似的工具将文件作为 png 读取。如果您只是想打开文件系统上的文件,那我没问题;它直接将文件原样下载到您的文件系统(在您指定的任何目录中)。 我无法在我的系统上打开文件。这就是确切的问题。这是一个奇怪的二进制文件,我无能为力。 嗯,这绝对是奇怪的......希望我能提供更多帮助,但不幸的是我不太确定问题出在哪里,因为我从来没有遇到过类似的问题。请记住 - 只要我能够下载文件,它们就与我的 S3 存储桶中的文件完全相同。我建议针对您遇到的问题专门针对 SO 提出一个新问题,以便其他人可以帮助您。【参考方案2】:

接受的答案是使用已弃用的 API。这是一个更新的版本。

首先,更新你的maven依赖:

 <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-java-sdk</artifactId>
        <version>1.11.274</version>
    </dependency>

AWSConfiguration.java

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AWSConfiguration 

    @Value("$cloud.aws.credentials.accessKey")
    private String accessKey;

    @Value("$cloud.aws.credentials.secretKey")
    private String secretKey;

    @Value("$cloud.aws.region")
    private String region;

    @Bean
    public BasicAWSCredentials basicAWSCredentials() 
        return new BasicAWSCredentials(accessKey, secretKey);
    

    @Bean
    public AmazonS3 amazonS3Client(AWSCredentials awsCredentials) 
        AmazonS3ClientBuilder builder = AmazonS3ClientBuilder.standard();
        builder.withCredentials(new AWSStaticCredentialsProvider(awsCredentials));
        builder.setRegion(region);
        AmazonS3 amazonS3 = builder.build();
        return amazonS3;
    

S3Service.java

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.*;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Service
public class S3Service 

    @Autowired
    private AmazonS3 amazonS3;

    @Value("$cloud.aws.s3.bucket")
    private String bucket;

    private PutObjectResult upload(String filePath, String uploadKey) throws FileNotFoundException 
        return upload(new FileInputStream(filePath), uploadKey);
    

    private PutObjectResult upload(InputStream inputStream, String uploadKey) 
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucket, uploadKey, inputStream, new ObjectMetadata());

        putObjectRequest.setCannedAcl(CannedAccessControlList.PublicRead);

        PutObjectResult putObjectResult = amazonS3.putObject(putObjectRequest);

        IOUtils.closeQuietly(inputStream);

        return putObjectResult;
    

    public List<PutObjectResult> upload(MultipartFile[] multipartFiles) 
        List<PutObjectResult> putObjectResults = new ArrayList<>();

        Arrays.stream(multipartFiles)
                .filter(multipartFile -> !StringUtils.isEmpty(multipartFile.getOriginalFilename()))
                .forEach(multipartFile -> 
                    try 
                        putObjectResults.add(upload(multipartFile.getInputStream(), multipartFile.getOriginalFilename()));
                     catch (IOException e) 
                        e.printStackTrace();
                    
                );

        return putObjectResults;
    

    public ResponseEntity<byte[]> download(String key) throws IOException 
        GetObjectRequest getObjectRequest = new GetObjectRequest(bucket, key);

        S3Object s3Object = amazonS3.getObject(getObjectRequest);

        S3ObjectInputStream objectInputStream = s3Object.getObjectContent();

        byte[] bytes = IOUtils.toByteArray(objectInputStream);

        String fileName = URLEncoder.encode(key, "UTF-8").replaceAll("\\+", "%20");

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        httpHeaders.setContentLength(bytes.length);
        httpHeaders.setContentDispositionFormData("attachment", fileName);

        return new ResponseEntity<>(bytes, httpHeaders, HttpStatus.OK);
    

    public List<S3ObjectSummary> list() 
        ObjectListing objectListing = amazonS3.listObjects(new ListObjectsRequest().withBucketName(bucket));

        List<S3ObjectSummary> s3ObjectSummaries = objectListing.getObjectSummaries();

        return s3ObjectSummaries;
    

【讨论】:

当我从这里下载文件时,它是一个二进制文件,而不是我使用的 png。 读完输入流后不用关闭s3Object吗(在download方法中)? 为什么上传后需要closeQuietly()?而且,任何流操作都需要它吗?我有两个操作,一个文件路径在那里,获取文件和上传,上传多部分文件。这两种情况我都转换为输入流,然后调用 putObject() 常用方法。那里也需要吗?【参考方案3】:

请更正网址格式

Resource resource = this.resourceLoader.getResource("s3://s3.amazonaws.com/mybucket/myfile.ext");

Resource resource = this.resourceLoader.getResource("s3://mybucket/myfile.ext");

根据文档,模式是s3://&lt;bucket&gt;/&lt;object&gt;。它在 spring boot 2.0.6.RELEASE 和 spring cloud Finchley.SR2(已验证)中工作。

参考:Spring Cloud AWS - Downloading files

【讨论】:

一个疑问为什么在 AmazonS3Client 可用时使用资源加载器。是否适合大文件、负载过大的操作? 是的,应该使用 AmazonS3Client。对于大文件,我推荐使用TransferManager

以上是关于Spring Boot Amazon AWS S3 存储桶文件下载 - 拒绝访问的主要内容,如果未能解决你的问题,请参考以下文章

将 Spring boot/cloud 与 Amazon AWS lambda 一起使用不会注入值

Amazon-Guard-Duty 用于我在 AWS 上运行的 Spring Boot 应用程序

S3 中的 Spring Boot 配置 - AWS beanstalk

运行 Amazon S3 示例时出现 Amazon AWS 403 InvalidAccesskey 错误

AWS Lambda 中的 Amazon S3 waitFor()

使用 AWS Elastic Beanstalk 在 AWS 上部署 Spring Boot 应用程序