Springboot + mySQL을 이용해 식재료 구매 기능이 담긴 앱을 협업하면서 만들고 있다가 이미지 데이터를 다루어야 함을 깨닫고 AWS S3 버킷을 구축하면서 겪은 여러 내용을 정리하고자 한다.
우리의 목표는 식재료 테이블의 컬럼인 image(String)에 대응하는 사진 데이터를 AWS S3의 버킷을 활용하여 저장/관리하는 것이다.
클라이언트가 내 사진을 볼 수 있어야, 즉 접근할 수 있어야 하므로 퍼블릭 액세스 차단을 비활성화한다. 이후 버킷을 생성하고 accessKey와 secretKey를 발급받는다.
dependency에 위 내용을 추가한다.
cloud.aws.credentials.accessKey=[S3에서 발급받은 accessKey]
cloud.aws.credentials.secretKey=[S3에서 발급받은 secretKey]
cloud.aws.region.static=ap-northeast-2
cloud.aws.stack.auto=false
cloud.aws.s3.bucket=[버킷 이름]
참고로 두 Key는 대소문자를 구분하고, 다시 저장할 수 없기에 csv 파일로 다운로드해두는 게 좋다.
또한 두 Key는 유출되면 치명적이기에, 암호화한 후 커밋하거나 별도의 파일로 민감한 내용을 옮겨두는 게 좋다.
민감한 내용을 숨기는 방법은 아래를 참고하면 된다.
https://velog.io/@win-luck/Springboot-application.properties%EC%97%90%EC%84%9C-%EB%AF%BC%EA%B0%90%ED%95%9C-%EC%A0%95%EB%B3%B4-%EC%88%A8%EA%B8%B0%EA%B8%B0
이제 REST API를 통해 요청/응답을 시도해보고 imageUrl이 잘 저장되었는지 검증하자.
@Service
public class ImageUploadService {
@Value("${cloud.aws.s3.bucket}")
private String bucketName;
private final AmazonS3 amazonS3;
public ImageUploadService(AmazonS3 amazonS3) {
this.amazonS3 = amazonS3;
}
public String uploadImage(MultipartFile file) throws IOException {
String fileName = UUID.randomUUID().toString(); // Generate a unique filename
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(file.getSize());
amazonS3.putObject(bucketName, fileName, file.getInputStream(), metadata);
return amazonS3.getUrl(bucketName, fileName).toString();
}
}
@Configuration
public class S3Config {
@Value("${cloud.aws.credentials.accessKey}")
private String accessKey;
@Value("${cloud.aws.credentials.secretKey}")
private String secretKey;
@Value("${cloud.aws.region.static}")
private String region;
@Bean
public AmazonS3 amazonS3Client() {
AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
return AmazonS3ClientBuilder
.standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion(region)
.build();
}
}
이름, 카테고리, 이미지파일, 설명, 가격 총 5개의 데이터가 필요하다. 이때 이미지 데이터는 MultiPartFile로 한다.
Postman을 통해 요청과 응답이 잘 이루어지는지 확인해보자!
내가 올릴 "치킨" 데이터이다. 근데 치킨이 식재료인가요? 라는 질문에는 답변하지 않겠다.
CreateIngredientDto에 맞추어 form-data 형식으로 데이터를 입력하고 POST 상태에서 요청을 보내보자!
200에 이 식재료의 id인 정수 2가 반환되었다.
Controller의 반환값에 부합하게 반환된 것을 확인할 수 있다. (1이 아닌 이유는 1에 해당하는 데이터를 아까 지워서..)
이제 GET으로 AWS S3가 저장한 이미지URL에 접근해보자!
정상적으로 조회 기능이 작동되는 것을 확인할 수 있다! Chrome에서 저 imageUrl을 입력해보면...
Access Denied 화면이 뜬다.
이유는 간단하다.
말 그대로 Access Denied인데,, S3 버킷으로 가서 [버킷 정책] 란에 아래 코드를 입력한다.
"Version": "2012-10-17",
"Id": "Policy1500123456789",
"Statement": [
{
"Sid": "Stmt1500123456789",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::[버킷 이름]/*"
}
]
}
사실상 누구에게나 (Principal이 "*") 내 버킷의 S3 모든 객체를 검색(GetObject)할 수 있는 권한을 부여하는 JSON 코드이다. 다만 IAM은 최소 권한 원칙을 준수하는 것을 강력하게 권하기에, 어떤 객체를 어떻게 개방할지는 상황에 따라 유동적이어야 한다.
이제 다시 그 Chrome에 링크를 입력하고 엔터를 누르면 사진 데이터가 다운로드되고, 열면 치킨 화면을 마주할 수 있다!