Spring boot -> aws s3 업로드
- 아래 이미지와 같은 단계로 이미지를 업로드 함
1. Connection
@Configuration
public class AwsConfig {
@Value("${cloud.aws.credentials.access-key}")
private String accessKey;
@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;
@Value("${cloud.aws.region.static}")
private String region;
@Bean
public AmazonS3 amazonS3() {
AWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
return AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.build();
}
}
- AWS S3에 Connection을 하고 S3Client를 생성하는 과정
- AccessKey와 SecretKey는 각별히 주의해야 한다. 잘못해서 Github에 올리다간 과금 폭탄을 맞을 수 있음.
- Region은 사용되는 지역으로 아시아의 서울 Region을 사용함
AWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
- AWS에 우리가 생성한 S3에 접근할 수 있는 AccessKey와 SecretKey의 정보가 담겨있는 객체로 AWSCredentials는 인터페이스 구현체로는 BasicAWSCredentials을 사용한다.
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
- 위에 만들어 놓은 S3에 접근할 수 있는 자격 증명이 담겨있는 awsCredentials를 AWSStaticCredentialsProvider 생성자에 담아주는데 AWSStaticCredentialsProvider는 private final 필드로 AWSCredentials를 가지고 있다.
2. 파일 업로드
@RequiredArgsConstructor
@Component
public class S3FileUtil {
private final AmazonS3 amazonS3;
@Value("${cloud.aws.bucket}")
private String bucket;
public String upload(MultipartFile file) throws IOException {
String fileName = UUID.randomUUID() + file.getOriginalFilename();
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(file.getInputStream().available());
amazonS3.putObject(bucket, fileName, file.getInputStream(), objectMetadata);
return amazonS3.getUrl(bucket, fileName).toString();
}
}
- bucket은 s3에서 사용하는 bucket의 이름이고 파일을 업로드할 때 겹치지 않기 위해 UUID를 추가로 파일 이름에 붙여서 업로드하도록 했음
private final AmazonS3 amazonS3;
- 1번에서 connection한 AwsS3Client 사용하기 위한 주입
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(file.getInputStream().available());
- ObjectMetadata는 업로드할 객체에 대한 설정 값을 넣어줄 수 있음 우리는 간단하게 업로드 할 파일이 몇 byte인지 알려주도록 했다. 이 설정을 안하면 out of memory error가 발생할 수 있음! 참고로 Content-type을 설정해주지 않을 경우 application/octet-stream 으로 설정된다. 더 자세한 것은 -> AwsDocs
amazonS3.putObject(bucket, fileName, file.getInputStream(), objectMetadata);
- s3에 파일을 업로드 하는 부분, file.getInputStream() 부분에서 IOException 예외가 발생할 수 있으니 서비스 로직에서 catch해서 따로 처리하면 될 듯하다.
3. 업로드된 파일 Url 가져오기
return amazonS3.getUrl(bucket, fileName).toString();
- bucket과 UUID 및 날짜 값을 추가했던 fileName을 넣어주면 URL 자체를 반환하는데 AmazonS3를 상속받은 AmazonS3Client가 구현한 getUrl(); 메서드를 통해 가져오게 된다.
- 이때 AWS S3에서 설정한 객체들에 대한 접근 설정에 따라 접근이 불가능 할 수도 있음
추가
1. Yml 파일 설정 및 dependencies
cloud:
aws:
bucket:
credentials:
access-key:
secret-key:
region:
static: ap-northeast-2
stack:
auto: false
- stack.auto: 설정 부분은 프로젝트 실행 시 CloudFormation을 생성하는데 우리는 이것을 사용하지 않을 것이므로 false로 지정해야 함 만약 false로 지정해주지 않을 경우 아래와 같은 예외가 발생한다.
Caused by: java.lang.IllegalArgumentException: No valid instance id defined
at org.springframework.util.Assert.notNull(Assert.java:201) ~[spring-core-5.3.9.jar:5.3.9]
at org.springframework.cloud.aws.core.env.stack.config.AutoDetectingStackNameProvider.autoDetectStackName(AutoDetectingStackNameProvider.java:85) ~[spring-cloud-aws-core-2.2.6.RELEASE.jar:2.2.6.RELEASE]
at org.springframework.cloud.aws.core.env.stack.config.AutoDetectingStackNameProvider.afterPropertiesSet(AutoDetectingStackNameProvider.java:70) ~[spring-cloud-aws-core-2.2.6.RELEASE.jar:2.2.6.RELEASE]
implementation "com.amazonaws:aws-java-sdk-s3:1.12.281"
2. Aws S3 bucket 정책 설정
{
"Version": "2012-10-17",
"Id": "Policy1665469475203",
"Statement": [
{
"Sid": "Stmt1665469468407",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:DeleteObject",
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::버킷이름/*"
}
]
}
- 버킷 정책을 위처럼 퍼블릭으로 열어야만 누구나 객체에 접근이 가능하다.
"Principal": "*"
"Action": [
"s3:DeleteObject",
"s3:GetObject",
"s3:PutObject"
],
- s3 버킷에 대한 퍼블릭으로 열린 액션을 지정
"Resource": "arn:aws:s3:::버킷이름/*"
- 특정 버킷에 있는 모든 객체에 접근할 수 있도록 설정
- 참고로 S3버킷 정책 편집에서 정책생성기를 이용해 간편하게 생성할 수 있다.