daangn-daangn / daangn-server

🥕당근 서버 리포지토리🥕
4 stars 2 forks source link

presignedURL을 사용하여 물품 이미지 비즈니스 로직 작성 #42

Closed cotchan closed 2 years ago

cotchan commented 2 years ago

작업내용

PR

목차

  1. 작업 목적
    • presignedURL이란?
  2. 작업 내용
    • SDK for Java 2.x
    • presignedURL을 사용해서 객체 조회하는 방법(GET)
    • presignedURL을 사용해서 객체 업로드하는 방법(PUT)
    • PresignerUtils
    • 삽질했던 부분(중요)

작업목적

presignedURL이란?

S3의 접근권한이 Public일 경우 버킷과 파일주소만 안다면 업로드된 파일을 자유롭게 접근가능합니다.
이를 통해서 S3에 이미지 리소스를 저장한다거나 업로드되는 파일을 저장하는 방법으로 사용하고 있습니다.

이런 편의성은 큰 장점이지만 보안관점에서 본다면 큰 위험요소가 될 수 있습니다.
AWS S3에서는 이런 위험요소를 보안하기 위해서 미리 서명된 URL(Presigned-URL)이라는 기능을 제공합니다.
이 기능은 제한된 시간동안 접근권한을 부여하는 기능입니다.
이를 이용하면 특정 대상에게 제한된 시간동안 S3의 접근권한을 부여하여 조금이나마 위험부담을 줄일 수 있습니다.

https://{Bucket}.s3.{region}.amazonaws.com/{FileName}?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential={KEY}%2F{region}%2Fs3%2Faws4_request&X-Amz-Date=20190718T234545Z&X-Amz-Expires=30&X-Amz-Signature=d7f47df5514f17f725e2e5213ff58487dbc61122e9851feea43898587a38c5d1&X-Amz-SignedHeaders=host

작업 내용

SDK for Java 2.x

Using the SDK with gradle

// build.gradle

dependencies {
    implementation platform('software.amazon.awssdk:bom:2.17.204')
    implementation 'software.amazon.awssdk:s3'
}

S3Presigner

aws-sdk-java-v1에서 사용하는 AmazonS3Client 대신 S3Presigner을 사용합니다.

@Slf4j
@Configuration
@RequiredArgsConstructor
public class AwsServiceConfigure {

    private final AwsConfigure awsConfigure;

    @Bean
    public S3Presigner s3Presigner() {
        AwsCredentialsProvider credentialsProvider =
            StaticCredentialsProvider.create(
                AwsBasicCredentials.create(awsConfigure.getAccessKey(), awsConfigure.getSecretKey())
            );

        return S3Presigner.builder()
                .region(Region.AP_NORTHEAST_2)
                .credentialsProvider(credentialsProvider)
                .build();
    }
}
@Setter
@Getter
@ToString
@Component
@ConfigurationProperties(prefix = "cloud.aws.s3")
@PropertySource(value = {"classpath:/aws.properties"}, ignoreResourceNotFound = true)
public class AwsConfigure {

    private String region;

    private String url;

    private String bucket;

    private String accessKey;

    private String secretKey;

    private String uploadKey;
}
# aws.properties
cloud.aws.s3.region=ap-northeast-2
cloud.aws.s3.url=https://s3.ap-northeast-2.amazonaws.com
cloud.aws.s3.bucket=
cloud.aws.s3.accessKey=
cloud.aws.s3.secretKey=

presignedURL을 사용해서 객체 조회하는 방법(GET)

  1. GetObjectRequest 만들고
  2. GetObjectPresignRequest 만들어서
  3. GetObjectPresignRequest로부터 presigned-url을 얻습니다.
  4. GET presigned-url로 버킷에 있는 파일을 조회할 수 있습니다.
// keyName은 S3 버킷에서 조회할 folder/filename.jpg를 의미합니다.
public static void signBucket(S3Presigner presigner, String bucketName, String keyName) {

    GetObjectRequest getObjectRequest = GetObjectRequest.builder()
            .bucket(awsConfigure.getBucket())
            .key(filePathKey)
            .build();

    GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder()
            .signatureDuration(Duration.ofMinutes(10))
            .getObjectRequest(getObjectRequest)
            .build();

    PresignedGetObjectRequest presignedRequest = presigner.presignGetObject(presignRequest);

    // Get content to the Amazon S3 bucket by using this URL
    String presignerUrl = presignedRequest.url().toString();
}

presignedURL을 사용해서 객체 업로드하는 방법(PUT)

  1. PutObjectRequest 만들고
  2. PutObjectPresignRequest 만들어서
  3. PutObjectPresignRequest로부터 presigned-url을 얻습니다.
  4. PUT presigned-url + request bodybinary 타입으로 파일을 업로드하면 됩니다.
// keyName은 S3 버킷에 올릴 folder/filename.jpg를 의미합니다.
public static void signBucket(S3Presigner presigner, String bucketName, String keyName) {

    PutObjectRequest objectRequest = PutObjectRequest.builder()
            .bucket(bucketName)
            .key(keyName)
            //.contentType("text/plain")
            .build();

    PutObjectPresignRequest presignRequest = PutObjectPresignRequest.builder()
            .signatureDuration(Duration.ofMinutes(10))
            .putObjectRequest(objectRequest)
            .build();

    PresignedPutObjectRequest presignedRequest = presigner.presignPutObject(presignRequest);

    // Upload content to the Amazon S3 bucket by using this URL
    String presignerUrl = presignedRequest.url().toString();
}

PresignerUtils

@Component
@RequiredArgsConstructor
public class PresignerUtils {

    private static final String PRODUCT_IMAGE_FOLDER = "product-image";
    private final AwsConfigure awsConfigure;
    private final S3Presigner presigner;

    public String getProductPresignedGetUrl(String filename) {
        GetObjectPresignRequest presignGetRequest = toGetObjectPresignRequest(PRODUCT_IMAGE_FOLDER, filename);
        return presigner.presignGetObject(presignGetRequest).url().toString();
    }

    public String getProductPresignedPutUrl(String filename) {
        PutObjectPresignRequest presignPutRequest = toPutObjectPresignRequest(PRODUCT_IMAGE_FOLDER, filename);
        return presigner.presignPutObject(presignPutRequest).url().toString();
    }

    private GetObjectPresignRequest toGetObjectPresignRequest(String folder, String filename) {

        String filePathKey = toKey(folder, filename);

        GetObjectRequest getObjectRequest = GetObjectRequest.builder()
                .bucket(awsConfigure.getBucket())
                .key(filePathKey)
                .build();

        return GetObjectPresignRequest.builder()
                .signatureDuration(Duration.ofMinutes(10))
                .getObjectRequest(getObjectRequest)
                .build();
    }

    private PutObjectPresignRequest toPutObjectPresignRequest(String folder, String filename) {

        String filePathKey = toKey(folder, filename);

        PutObjectRequest objectRequest = PutObjectRequest.builder()
                .bucket(awsConfigure.getBucket())
                .key(filePathKey)
//                .acl(ObjectCannedACL.PUBLIC_READ)
                .build();

        return PutObjectPresignRequest.builder()
                .signatureDuration(Duration.ofMinutes(10))
                .putObjectRequest(objectRequest)
                .build();
    }

    private String toKey(String folder, String filename) {
        checkArgument(isNotEmpty(folder), "folder name must not be empty");
        checkArgument(isNotEmpty(filename), "filename must not be empty");

        return folder + "/" + filename;
    }
}

삽질했던 부분(중요)

AWS 파일에 접근제한 부여하기 - Presigned URL