8월 16일 수요일 TIL 회고록
AWS S3 를 이용한 파일 업로드
1) S3용 IAM Access Analyzer 활성화
1. AWS Console에서 S3용 IAM Access Analyzer 탭을 눌러 밑에 이미지에 있는 IAM Access Analyzer를 누른다.
2. 누르면 나오는 화면에서 액세스 생성 버튼을 누른다. (사진을 찍지 못했다 ㅠㅠ) 누른 후 액세스를 만들면 아래 화면이 나온다.
2) 사용자 생성
1. 액세스 관리 > 사용자 > 사용자 생성 클릭
S3에 접근하기 위헤서는 IAM 사용자에게 S3 접근 권한을 주고, 액세스 키를 만들어 액세스 키, 비밀 엑세스 키를 접근해야한다.
2. 사용자 이름을 입력한다.
3. 직접 정책 연결을 클릭하고, AmazonS3FullAccess를 선택하고 다음을 클릭한다.
4. 사용자 생성 버튼을 클릭하면 사용자가 생성된다.
3) 엑세스 키 생성
외부에서 접속할 수 있도록 사용자의 액세스 키를 만들어 주어야 한다.
1. AWS Console > IAM > 엑세스 관리 > 사용자 > 생성한 사용자 이름 클릭 > 보안 자격 증명 > 액세스 키 만들기 클릭
2. 아무거나 클릭하고 다음 버튼을 클릭한다.
(클릭하면 액세스 키 사용사례와 대안을 하단에 띄워주는 기능만 하기때문에 아무거나 골라도 상관없다.)
3. (선택) 설명 태그를 입력하고 엑세스 키 만들기 버튼을 클릭한다.
4. 엑세스 키 생성 완료 화면에서 생성된 공개키와 비밀키를 확인할 수 있다.
(생성 완료 화면이 아니면 비밀 엑세스 키를 볼 수 없기 때문에 .csv 파일로 받아두는 것이 좋다.)
스프링 연동하기
1) build.gradle에 의존성 추가
// AWS S3 패키지
implementation group: 'org.springframework.cloud',
name: 'spring-cloud-starter-aws', version: '2.2.6.RELEASE'
2) application.yml에 설정 정보 추가
cloud:
aws:
s3:
bucket: <버킷이름>
stack.auto: false
region.static: ap-northeast-2
credentials:
accessKey: <발급받은 accessKey>
secretKey: <발급받은 secretKey>
accessKey와 같은 보안 설정 정보는 깃허브에 노출되면 해킹되어 과금 위험이 있으므로 별도의 설정 파일로 분리하고 깃허브에 올리면 안된다. 설정 파일에 있는 정보는 스프링이 지원하는 @Value 어노테이션을 사용하여 불러올 수 있다.
cloud.aws.stack.auto : false
EC2에서 Spring Cloud 프로젝트를 실행시키면 기본으로 CloudFormation 구성을 시작하기 때문에 설정한 CloudFormation이 없으면 프로젝트 실행이 되지 않는다. 해당 기능을 사용하지 않도록 false로 설정했다.
cloud.aws.region.static : ap-northeast-2
지역을 한국으로 고정한다.
3) AwsS3Config 작성
package com.luda.rhythmjoygo.common.aws.config;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3Client;
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 S3Config {
@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 AmazonS3Client amazonS3Client() {
BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(accessKey, secretKey);
return (AmazonS3Client) AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(basicAWSCredentials))
.build();
}
}
accessKey, secretKey, region을 @Value 어노테이션으로 받아와서 AmazonS3Client 객체를 Bean으로 등록한다.
4) AwsS3ServiceImpl 작성
package com.luda.rhythmjoygo.common.aws.Service;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.DeleteObjectRequest;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.luda.rhythmjoygo.common.exception.CustomException;
import com.luda.rhythmjoygo.common.exception.ExceptionStatus;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
@Service
@RequiredArgsConstructor
public class AwsS3ServiceImpl implements AwsS3Service {
private final AmazonS3 s3Client;
@Value("${cloud.aws.credentials.accessKey}")
private String accessKey;
@Value("${cloud.aws.credentials.secretKey}")
private String secretKey;
@Value("${cloud.aws.s3.bucket}")
private String bucket;
@Value("${cloud.aws.region.static}")
private String region;
// 다중 파일 업로드
@Override
public List<String> upload(List<MultipartFile> multipartFiles) throws IOException {
List<String> imgUrlList = new ArrayList<>();
// forEach 구문을 통해 multipartFiles로 넘어온 파일들 하나씩 fileNameList에 추가
for (MultipartFile file : multipartFiles) {
String fileName = createFileName(file.getOriginalFilename());
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(file.getSize());
objectMetadata.setContentType(file.getContentType());
try (InputStream inputStream = file.getInputStream()) {
s3Client.putObject(
new PutObjectRequest(bucket + "/board/image", fileName, inputStream, objectMetadata)
.withCannedAcl(CannedAccessControlList.PublicRead));
imgUrlList.add(s3Client.getUrl(bucket + "/board/image", fileName).toString());
} catch (IOException e) {
throw new CustomException(ExceptionStatus.IMAGE_UPLOAD_ERROR);
}
}
return imgUrlList;
}
// 단일 파일 업로드
public String saveFileOne(MultipartFile multipartFile) throws IOException {
String imgPath;
String fileName = createFileName(multipartFile.getOriginalFilename());
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(multipartFile.getSize());
metadata.setContentType(multipartFile.getContentType());
try (InputStream inputStream = multipartFile.getInputStream()) {
s3Client.putObject(
new PutObjectRequest(bucket + "/board/image", fileName, inputStream, metadata)
.withCannedAcl(CannedAccessControlList.PublicRead));
imgPath = s3Client.getUrl(bucket + "/board/image", fileName).toString();
}
return imgPath;
}
@Override
public void deleteFile(String fileName) {
try {
String imagePath = fileName.substring(54);
s3Client.deleteObject(
new DeleteObjectRequest(bucket, imagePath)
);
} catch (
AmazonServiceException e) {
throw new AmazonServiceException("에러");
}
}
// 파일 업로드시, 파일 이름을 난수화 시킨다. (이미지 파일명 중복 방지)
@Override
public String createFileName(String fileName) throws IOException {
return UUID.randomUUID().toString().concat(getFileExtension(fileName));
}
// fileValidate 리스트에 있는 형식이 아닐경우, 에러 발생 (유효성 검사)
@Override
public String getFileExtension(String fileName) throws IOException {
if (fileName.length() == 0) {
throw new IOException();
}
ArrayList<String> fileValidate = new ArrayList<>();
fileValidate.add(".jpg");
fileValidate.add(".jpeg");
fileValidate.add(".png");
fileValidate.add("JPG");
fileValidate.add("JPEG");
fileValidate.add(".PNG");
fileValidate.add(".gif");
fileValidate.add(".GIF");
String idxFileName = fileName.substring(fileName.lastIndexOf("."));
if (!fileValidate.contains(idxFileName)) {
throw new CustomException(ExceptionStatus.IS_NOT_CORRECT_FORMAT);
}
return fileName.substring(fileName.lastIndexOf("."));
}
}
내일은 실제로 S3에 이미지가 저장이 되는지 확인해봐야겠다.