AWS Java SDK - S3 File upload #1
<개요>
- 기본적인 AWS Java SDK S3 사용예제
- 사용시 주의사항
<내용>
현재 버전(2022.05.19기준) 샘플 소스이며, 공식 가이드문서를 참고하는 것이 정확합니다.
1. Configuration
@Configuration
public class AmazonS3Config {
@Value("${aws.credentials.access-key}")
private String accessKey;
@Value("${aws.credentials.secret-key}")
private String secretKey;
@Value("${aws.region.static}")
private String region;
public BasicAWSCredentials awsCredentials(){
BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey);
return awsCreds;
}
@Bean
public AmazonS3 amazonS3Client(){
AmazonS3 amazonS3 = AmazonS3ClientBuilder.standard()
.withRegion(this.region)
.withCredentials(new AWSStaticCredentialsProvider(this.awsCredentials()))
.build();
return amazonS3;
}
}
- 이와 같이 accessKey, secretKey, region정보를 세팅하여 기본적인 인증을 생성하고 이를 기반으로 S3 ClientBuilder를 통해서 객체를 생성 후 Bean으로 사용한다.
- dev / stg / prod 환경에 따라서 달라질 수 있는 정보들은 외부에 관리한다.
- 주의 : 해당 키값이 탈취당할 경우 매우 위험하니 (보안,비용 등등), Public 공간에 올리는것은 주의하고 올라가게 된다면 반드시 암호화를 한다!
2. Repository
@Repository
public class AwsFileRepository {
private AmazonS3 amazonS3;
@Value("${aws.s3.bucket}")
private String bucket;
@Autowired
public AwsFileRepository(AmazonS3 amazonS3){
this.amazonS3 = amazonS3;
}
public FileDto upload(MultipartFile file, String prefix) throws IOException {
SimpleDateFormat date = new SimpleDateFormat("yyyyMMddHHmmss");
String fileName = prefix +"-"+date.format(new Date())+"-"+file.getOriginalFilename();
String s3location = bucket +"/"+ prefix;
amazonS3.putObject(new PutObjectRequest(s3location, fileName, file.getInputStream(), null));
FileDto fileDto = new FileDto();
fileDto.setS3Location(amazonS3.getUrl(s3location,fileName).toString());
fileDto.setS3Key(prefix +"/" + fileName);
return fileDto;
}
public String delete(String s3Key){
amazonS3.deleteObject(bucket, s3Key);
return s3Key;
}
}
- S3 역시 외부의 저장소에 접근하는 것이므로 Repository Layer로 정의하는 것을 추천한다.
- S3 는 버킷단위로 정책을 정의할 수 있으며, 내부에 별도 폴더로 분리 저장이 가능하다. (예제 소스에서 prefix부분)
- 저장할 때의 값 bucketName "/" 이하 부분을 해당 파일에 대한 Key로 관리하면 삭제 시 편리하게 활용할 수 있다.
- 예제에서는 ObjectMetadata를 null로 사용하였으며 필요에 따라서 공식가이드에 제공되는 값을 세팅할 수 있다.
https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/UsingMetadata.html#object-key-guidelines
<주의사항>
- 대부분의 Public Cloud는 Endpoint와 HTTP API를 제공하고 있으며, SDK는 이것을 감싸는 구조로 되어있다.
- 따라서 url 인코딩을 주의하여 사용해야 한다.
- 예를 들어서 얼마전에 테스트하다가 파일명에 특이한 문자들을 넣게 되었는데 아래와 같은 오류가 발생하였다.
2022-05-19 10:15:15.447 DEBUG 30235 --- [nio-8900-exec-6] com.amazonaws.request : Received error response: com.amazonaws.services.s3.model.AmazonS3Exception: The request signature we calculated does not match the signature you provided. Check your key and signing method. (Service: Amazon S3; Status Code: 403; Error Code: SignatureDoesNotMatch; Request ID: S80BNXP50DTW9SJM; S3 Extended Request ID: , S3 Extended Request ID:
2022-05-19 10:15:15.449 DEBUG 30235 --- [nio-8900-exec-6] com.amazonaws.retry.ClockSkewAdjuster : Reported server date (from 'Date' header): Thu, 19 May 2022 01:15:15 GMT
.
.
.
at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5054) [aws-java-sdk-s3-1.11.762.jar:na]
at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5000) [aws-java-sdk-s3-1.11.762.jar:na]
at com.amazonaws.services.s3.AmazonS3Client.access$300(AmazonS3Client.java:394) [aws-java-sdk-s3-1.11.762.jar:na]
at com.amazonaws.services.s3.AmazonS3Client$PutObjectStrategy.invokeServiceCall(AmazonS3Client.java:5942) [aws-java-sdk-s3-1.11.762.jar:na]
at com.amazonaws.services.s3.AmazonS3Client.uploadObject(AmazonS3Client.java:1808) [aws-java-sdk-s3-1.11.762.jar:na]
at com.amazonaws.services.s3.AmazonS3Client.putObject(AmazonS3Client.java:1768) [aws-java-sdk-s3-1.11.762.jar:na]
- 인증토큰이나 이러한 부분에 문제인줄 알았는데 유사한 문제를 겪은 사람들이 상당히 많았고
- 결국 encoding과정의 문제로 발생한 것이었다.(에러메시지를 잘 주었다면 덜 고생했을텐데;)
https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/object-keys.html