상세 컨텐츠

본문 제목

사진을 빠르고 안전하게!! PreSigned URL

Backend

by 준빅 2023. 7. 29. 15:33

본문

서비스를 만들면 꼭 필요한 기능. 바로 이미지를 업로드하는 것입니다.

 

Sluv의 1차 MVP 당시 이미지 업로드는 S3에 접속 정보를 프론트가 관리했고, 프론트에서 이미지를 업로드 한 뒤, S3 주소를 서버에게 전달해주는 구조였습니다.

첫번째 문제점

 

위와 같은 방식으로 관리를 할 때 프론트에서 S3의 접속 정보를 관리하기 때문에 서버에서 관리하는 것보다 보안적인 측면에서 약하다는 문제가 있었습니다.

 

첫번째 해결방법

 

기존의 방식을 버리고, S3의 접속 정보를 서버에서 관리하며, 프론트에서 서버로 이미지 파일을 전달해주는 방식으로 해결하였습니다.

 

하지만 또 다른 문제점이 발생했습니다.

 

두번째 문제점

Sluv의 서비스는 사진이 매우매우 중요하며, 또한 한번의 요청에 많이 사진이 저장되어야 하는 구조입니다.

 

때문에 위 개선된 방식을 이용할 시, 프론트에서 서버로 사진파일을 전달하기 때문에 속도적인 측면에서 저하가 발생할 수 있고, 이는 대용량 트래픽이 발생한다면 치명적일 수 있습니다.

 

두번째 해결방법

이런 문제를 PreSigned Url로 해결하였습니다.

 

PreSigned Url이란?

S3의 접근 권한에 대한 인증을 마치면 S3에 업로드 할 수 있는 URL을 발급해 주는데, 이 URL을 Presigned URL이라고 합니다.
발급 받은 PreSigned URL을 이용하면 브라우저에서 AWS S3 버킷에 바로 파일을 업로드 할 수 있습니다.

 

이것을 이용해서 프론트가 요청 하면 서버가 Presigned URL을 프론트로 발급해주고, 발급 받은 Presigned URL로 프론트는 이미지를 업로드 한 뒤, S3 주소를 서버에게 전달하는 방식입니다.

 

 

구현 코드

S3Config.java

@Configuration
public class S3Config {
    @Value("${aws.s3.accessKey}")
    private String accessKey;

    @Value("${aws.s3.secretKey}")
    private String secretKey;

    @Value("${aws.s3.region}")
    private String region;

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


}

S3의 인증을 얻는 과정입니다.

 

PreSingedUrlResDto.java

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PreSingedUrlResDto {
    private String preSignedUrl;
    private String key;
}

 

ImgExtenstion.java

@Getter
public enum ImgExtension {
    JPG("jpeg"),
    JPEG("jpeg"),
    PNG("png");

    ImgExtension(String uploadExtension) {
        this.uploadExtension = uploadExtension;
    }

    private final String uploadExtension;
}

 

S3Service.java

@Service
@Slf4j
@RequiredArgsConstructor
public class AWSS3Service {

    private final AmazonS3 amazonS3;

    @Value("${aws.s3.bucketName}")
    private String bucketName;

    @Value("${aws.s3.baseUrl}")
    private String baseUrl = "";

    
    public PreSingedUrlResDto getPreSignedUrl(ImgExtension imgExtension) {
        String fixedExtension = imgExtension.getUploadExtension();
        String fileName = baseUrl + UUID.randomUUID() + "." + fixedExtension;
        log.info(fileName);
        URL url =
                amazonS3.generatePresignedUrl(
                        getGeneratePreSignedUrlRequest(bucketName, fileName, fixedExtension));
        return PreSingedUrlResDto.builder()
                .preSignedUrl(url.toString())
                .key(fileName)
                .build();
    }
    
    /**
     * PreSigned URL 생성
     */
	private GeneratePresignedUrlRequest getGeneratePreSignedUrlRequest(String bucket, String fileName, String imgExtension) {
        GeneratePresignedUrlRequest generatePresignedUrlRequest =
                new GeneratePresignedUrlRequest(bucket, fileName)
                        .withMethod(HttpMethod.PUT)
                        .withKey(fileName)
                        .withContentType("image/" + imgExtension)
                        .withExpiration(getPreSignedUrlExpiration());
        generatePresignedUrlRequest.addRequestParameter(
                Headers.S3_CANNED_ACL,
                CannedAccessControlList.PublicRead.toString());
        return generatePresignedUrlRequest;
    }

    /**
     * PreSigned URL의 유효기간 설정
     */
    private Date getPreSignedUrlExpiration() {
        Date expiration = new Date();
        long expTimeMillis = expiration.getTime();
        expTimeMillis += 100000 * 60 * 2;
        expiration.setTime(expTimeMillis);
        log.info(expiration.toString());
        return expiration;
    }
}

 

 

 

 

관련글 더보기

댓글 영역