회사에서 프로젝트 진행 중 이미지를 저장하고 조회하기 위해 S3를 사용하기로 하였다.
그러나 보안상의 이유료 presigned-url을 활용하기로 하였다.
S3의 Bucket Policy나 acl 같은 권한 설정과 관계없이 특정 유효기간에 S3에 put, get이 가능하게 하는 URL이다.
버킷을 public access로 열어놓으면 보안 상 문제가 발생할 수 있고
클라이언트 -> 서버 -> S3로 업로드 혹은
S3 -> 서버 -> 클라이언트로 다운로드를 진행하기 보단 presigned URL을 통해 클라이언트 <-> S3로 바로 업로드/다운로드를 하여 서버의 리소스를 절약할 수 있는 장점이 있다.
※ S3 버킷 생성 이전에 IAM 계정을 생성해야 합니다.
IAM 권한 설정 시 S3FullAccess을 추가해주어야 합니다.
버킷 만들기에 들어가 줍니다.
S3 region, 이름 입력 후 버킷 만들기를 클릭합니다.
이후 생성한 버킷에 들어간 후 권한에 들어가서 버킷 정책과 CORS 정책을 설정해주어야 합니다.
,
로 구분하여 입력, 전체 허용시 *
입력이후 Add statement 클릭 후 Generate Policy 클릭하면
아래와 같이 정책이 나오는데 이를 복사하여
정책 화면에 붙여넣기 하면 됩니다.
CORS 정책
application.yml
spring:
cloud:
aws:
s3:
credentials:
accessKey: ${AWS_S3_ACCESSKEY}
secretKey: ${AWS_S3_SECRETKEY}
bucket: ${AWS_S3_BUCKET}
bucket.url: ${AWS_S3_BUCKET_URL}
region:
static: ${AWS_REGION}
stack:
auto: false
이때 accessKey와 secretKey는 퍼블릭 레포에 올리면 안된다
만약 올렸다면 레포를 전체 지우고 다시 올려야 한다.(기록이 남기 때문에...)
accessKey와 secretKey를 application.yml에 올리지 않고도 사용하는 방법은 여러가지가 있으나 여기에선 환경변수를 설정하는 방법을 사용하겠다.
다른 좋은 방법에 대해서는 여기로
accessKey와 secretKey를 각각 AWS_ACCESS_KEY_ID
와 AWS_SECRET_ACCESS_KEY
로 환경변수를 설정해주면 EnvironmentVariableCredentialsProvider
에 주입된다.
이를 S3Config에 활용하면 된다.
@Configuration
public class AWSS3Config {
@Bean
public S3Client s3Client() {
return S3Client.builder()
.credentialsProvider(EnvironmentVariableCredentialsProvider.create())
.region(Region.AP_NORTHEAST_2)
.build();
}
@Bean
public S3Presigner presigner() {
return S3Presigner.builder()
.credentialsProvider(EnvironmentVariableCredentialsProvider.create())
.region(Region.AP_NORTHEAST_2)
.build();
}
}
AwsService
@RequiredArgsConstructor
@Component
public class AwsService {
private final S3Client s3Client;
private final S3Presigner presigner;
public String getPresignUrl(String filename) {
if(filename == null || filename.equals("")) {
return null;
}
GetObjectRequest getObjectRequest = GetObjectRequest.builder()
.bucket(bucketName)
.key(filename)
.build();
GetObjectPresignRequest getObjectPresignRequest = GetObjectPresignRequest.builder()
.signatureDuration(Duration.ofMinutes(5)) // presignedURL 5분간 접근 허용
.getObjectRequest(getObjectRequest)
.build();
PresignedGetObjectRequest presignedGetObjectRequest = presigner
.presignGetObject(getObjectPresignRequest);
String url = presignedGetObjectRequest.url().toString();
presigner.close(); // presigner를 닫고 획득한 모든 리소스를 해제
return url;
}
}
AController
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/abc")
@RestController
public class AController {
private final AwsService awsService;
@GetMapping("/file/{filename}")
public ResponseEntity<String> getFile(@PathVariable(value = "filename") String fileName) throws IOException {
String url = awsService.getPresignUrl(fileName);
return new ResponseEntity<>(url, HttpStatus.OK);
}
}
결과