AWS 콘솔에서 CloudFront 배포 생성을 클릭한다.
Distribution options에서 'Single website or app' 선택.
Origin domain으로는 연동하고자 하는 s3 버킷을 선택한다.
Origin domain을 선택하면 이름은 자동으로 작성될 것이다.
S3 직접 url 대신 CloudFront를 통해 파일에 접근하도록 할 것이므로 원본 엑세스의 경우 공개 대신 '원본 엑세스 제어 설정'을 선택한다.
'원본 엑세스 제어 설정'을 선택하면 위와 같이 Origin access control(OAC)를 선택하라고 뜬다. Create new OAC를 클릭해 새로운 OAC를 생성하자.
위와 같이 설정되어 있는지 확인하고 Create를 클릭해 생성을 완료한다.
밑으로 좀 내리면 방화벽 설정이 나온다. 활성화를 누르면 돈을 내라고 하니 비활성화를 누르자.
나머지 설정은 기본으로 두면 된다. 밑으로 쭉 내려서 배포 생성 버튼을 클릭해 생성을 완료하자.
생성된 배포의 일반 탭에 들어가면 배포 도메인 이름과 ARN을 확인할 수 있다. ARN은 S3 버킷 정책을 설정할 때 필요하니 복사해둔다.
S3 버킷의 파일에 S3 직접 URL로 접근하는 것을 차단하고, CloudFront를 통한 접속을 허용하기 위해서 S3 버킷에도 추가적인 설정이 필요하다.
버킷의 권한 탭에 들어가보자.
기존에 모든 퍼블릭 엑세스 차단을 비활성으로 설정해 둔 상태였다면, 이제는 퍼블릭 엑세스를 차단해야 하므로 편집을 누른 후 '모든 퍼블릭 엑세스 차단'을 체크한다.
변경 사항 저장을 클릭하면 확인을 입력해라 어쩌구 창이 뜨는데 하라는 대로 입력하면 변경이 완료된다.
버킷의 권한 탭에서 버킷 정책의 편집 버튼을 클릭한다.
{
"Version": "2008-10-17",
"Id": "PolicyForCloudFrontPrivateContent",
"Statement": [
{
"Sid": "AllowCloudFrontServicePrincipal",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-bucket-name/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "복사한-CloudFront-ARN"
}
}
}
]
}
그리고 위 내용을 복사해 붙여넣기 한다.
앞서 CloudFront 배포를 생성하고 부여받은 ARN을 복사해둔 것을 AWS:SourceArn의 value 값으로 넣어주면 된다.
기존 S3 직접 URL이
https://your-bucket.s3.ap-northeast-2.amazonaws.com/image1.jpg
였다면, 이제는
https://d12345abc.cloudfront.net/image.png
위 URL로 접속할 수 있다.
d12345abc.cloudfront.net
위치에 들어갈 자신의 도메인명은 생성된 CloudFront 배포의 일반탭에서 역시 확인할 수 있다.
기존 S3 직접 URL로 접속하면 AccessDenided가 뜬다.
새로운 CloudFront URL을 입력하니
위와 같이 파일 접근에 성공한 것을 확인할 수 있었다.
일단 build.gradle의 dependency에 다음의 의존성이 추가된 상태여야 한다.
implementation 'io.awspring.cloud:spring-cloud-starter-aws:2.4.4'
기존 코드는 다음과 같다.
@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 s3Client() {
return AmazonS3ClientBuilder
.standard()
.withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey)))
.withRegion(region)
.build();
}
}
여기는 변할 게 없을 것 같고
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.score.backend.exceptions.ExceptionType;
import com.score.backend.exceptions.ScoreCustomException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.UUID;
@Service
@RequiredArgsConstructor
public class ImageUploadService {
@Value("${cloud.aws.s3.bucket}")
private String bucket;
private final AmazonS3 amazonS3;
public String uploadImage(MultipartFile file) throws IOException {
String extension = StringUtils.getFilenameExtension(file.getOriginalFilename());
if (extension != null && (extension.equals("png") || extension.equals("jpg") || extension.equals("jpeg"))) {
String fileName = UUID.randomUUID().toString();
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(file.getSize());
amazonS3.putObject(bucket, fileName, file.getInputStream(), metadata);
return amazonS3.getUrl(bucket, fileName).toString();
}
throw new ScoreCustomException(ExceptionType.UNSUPPORTED_FILE_TYPE);
}
}
위 코드에서 기존에는 amazonS3.getUrl(bucket, fileName).toString()
를 통해 S3 직접 URL을 리턴하였는데, 이 부분을 다음과 같이 변경해주었다.
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.score.backend.exceptions.ExceptionType;
import com.score.backend.exceptions.ScoreCustomException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.UUID;
@Service
@RequiredArgsConstructor
public class ImageUploadService {
@Value("${cloud.aws.s3.bucket}")
private String bucket;
private final AmazonS3 amazonS3;
private final String CLOUD_FRONT_DOMAIN_NAME = "https://dc70yyxvhpj8a.cloudfront.net";
public String uploadImage(MultipartFile file) throws IOException {
String extension = StringUtils.getFilenameExtension(file.getOriginalFilename());
if (extension != null && (extension.equals("png") || extension.equals("jpg") || extension.equals("jpeg"))) {
String fileName = UUID.randomUUID().toString();
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(file.getSize());
amazonS3.putObject(bucket, fileName, file.getInputStream(), metadata);
return CLOUD_FRONT_DOMAIN_NAME + "/" + fileName;
}
throw new ScoreCustomException(ExceptionType.UNSUPPORTED_FILE_TYPE);
}
}