Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

코딩블로그

[Spring] Amazons S3 Presigned Url 구현하기 본문

PopcornMate

[Spring] Amazons S3 Presigned Url 구현하기

_hanbxx_ 2024. 3. 11. 14:08
728x90

팝콘메이트에서 프론트 분들과 통신을 진행하면서 갑자기 image를 "Multipart"로 전송을 하면 500에러가 뜬다는 오류를 전달 받았다. 단순한 스크린 샷은 업로드가 수월했지만, 폰의 카메라에서 찍은 사진을 업로드 하려고 할 때 용량 문제로 업로드가 되지 않고 있었다.

이에 대한 해결책을 찾던 도중, image api를 따로 만들어서 S3에 pre-sign된 링크를 발급 받아 그 곳으로 이미지를 PUT하는 방식, 즉 Presigned Url 방식으로 바꿔 문제를 해결하였다. 

1. 기존의 Multipart방식

S3에 하나의 대용량 파일을 업로드할 때  하나의 객체를 여러 부분으로 나누어 처리량을 향상 시키고 업로드를 실패한다면 빠른 복구를 한다는 장점이 있다.

나누어 처리를 하여 나누어진 조각들을 S3로 전송을 하고 모든 조각이 전송이 완료가 되면 하나의 Object File로 합친다

AWS 기준으로는 기본 용량을 제한하고 있어 임의로 자신이 원하는 크기 만큼 설정 할 수 있다

예시

sprgin: 
    servlet:
      multipart:
        max-file-size: 10MB
        max-request-size: 10MB

하지만 나는 이 방식을 택해도 오류가 해결되지 않아 아예 Presigned Url로 눈을 돌렸다

 

 

 

2. Pre-signed Url 방식

이 방식을 사용하면 서버의 네트워크 IO비용을 줄이고 자원을 절약할 수 있다. 또한, 무분별하게 S3에 접근하는 것을 막기에 보안적으로 강화가 된 접근 방식이다.

3.전체 코드

ScreeningController Class

 @Operation(description = "모임 대표 이미지")
    @GetMapping(value = "/image/{fileName}")
    public SuccessResponse<Object> uploadImage(@PathVariable("fileName") String fileName) throws IOException {
        try {
            String imageUrl = screeningUploadUseCase.uploadImage("images",fileName);
            SuccessResponse<Object> successResponse = SuccessResponse.onSuccess(200,imageUrl);
            return successResponse;
        } catch (IOException e) {
            throw  new IllegalArgumentException("오류");
        }
    }

 

ScreeningUploadUseCase Class

    public String uploadImage(String prefix, String fileName) throws IOException {
        // S3에 이미지 파일 업로드 및 업로드된 파일의 URL 생성
        String imageUrl = s3UploadService.getPreSignedUrl(prefix,fileName);
        return imageUrl;
    }

 

S3UploadService Class

    public String getPreSignedUrl(String prefix, String fileName) {
        if (!prefix.isEmpty()) {
            fileName = createPath(prefix, fileName);
        }
        GeneratePresignedUrlRequest generatePresignedUrlRequest = getGeneratePreSignedUrlRequest(fileName);
        URL url = amazonS3.generatePresignedUrl(generatePresignedUrlRequest);
        return url.toString();
    }

    private String createFileId() {
          return UUID.randomUUID().toString();
    }
    private String createPath(String prefix, String fileName) {
        String fileId = createFileId();
        return String.format("%s/%s", prefix, fileId + "-" + fileName);
    }

    private GeneratePresignedUrlRequest getGeneratePreSignedUrlRequest(String fileName) {
        GeneratePresignedUrlRequest generatePresignedUrlRequest =
                new GeneratePresignedUrlRequest(bucket, fileName)
                        .withMethod(HttpMethod.PUT)
                        .withExpiration(getPreSignedUrlExpiration());
        generatePresignedUrlRequest.addRequestParameter(
                Headers.S3_CANNED_ACL,
                CannedAccessControlList.PublicRead.toString());
        return generatePresignedUrlRequest;
    }

    private Date getPreSignedUrlExpiration() {
        Date expiration = new Date();
        long expTimeMillis = expiration.getTime();
        expTimeMillis += 1000 * 60 * 2;
        expiration.setTime(expTimeMillis);
        log.info(expiration.toString());
        return expiration;
    }

 

S3Config Class

@Configuration
public class S3Config {
    @Value("${cloud.aws.credentials.access-key}")
    private String accessKey;

    @Value("${cloud.aws.credentials.secret-key}")
    private String secretKey;

    @Bean
    public AmazonS3 amazonS3Client() {
        BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(accessKey, secretKey);
        return AmazonS3ClientBuilder.standard()
                .withRegion(Regions.AP_NORTHEAST_2)
                .withCredentials(new AWSStaticCredentialsProvider(basicAWSCredentials))
                .build();
    }
}

 

application.yml 

cloud:
  aws:
    s3:
      bucket: popcornmate-bucket
    credentials:
      access-key: ${S3_ACCESS}
      secret-key: ${S3_SECRET}
    region:
      static: ap-northeast-2
      auto: false
    stack:
      auto: false

 

 

4. 실행 화면

원하는 file이름을 지정해주면

ResponseBody에 Presigned-url이 data로 넘어온다

이 url을 가지고 PUT 메서드를 이용하여 실제 이미지 파일을 이미지를 업로드하면 200 OK라는 성공 응답이 온다

 

실제로 website에 접속해보면 테스트로 업로드해본 이미지가 나온다