From d3d35ff9a62933a7e7c9591825d7fde2a7a24e77 Mon Sep 17 00:00:00 2001 From: isExample Date: Sat, 23 Sep 2023 00:11:36 +0900 Subject: [PATCH] =?UTF-8?q?[Feat]=20AWS=20S3=20Pre-Signed=20Url=20?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../transaction/TransactionApplication.java | 4 +- .../transaction/config/AmazonS3Config.java | 36 ++++++++++++ .../controller/TransactionController.java | 11 +++- .../transaction/dto/PreSignedUrlRes.java | 14 +++++ .../transaction/service/AmazonS3Service.java | 55 +++++++++++++++++++ 6 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 src/main/java/HeyPorori/transaction/config/AmazonS3Config.java create mode 100644 src/main/java/HeyPorori/transaction/dto/PreSignedUrlRes.java create mode 100644 src/main/java/HeyPorori/transaction/service/AmazonS3Service.java diff --git a/build.gradle b/build.gradle index 44b2615..3a61b70 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,7 @@ dependencies { implementation 'org.springdoc:springdoc-openapi-ui:1.7.0' implementation 'org.springframework.boot:spring-boot-starter-webflux' // WebClient implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' // S3 compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' diff --git a/src/main/java/HeyPorori/transaction/TransactionApplication.java b/src/main/java/HeyPorori/transaction/TransactionApplication.java index fc9552d..c5c0269 100644 --- a/src/main/java/HeyPorori/transaction/TransactionApplication.java +++ b/src/main/java/HeyPorori/transaction/TransactionApplication.java @@ -7,7 +7,9 @@ @SpringBootApplication @EnableJpaAuditing public class TransactionApplication { - + static { + System.setProperty("com.amazonaws.sdk.disableEc2Metadata", "true"); // S3 예외발생 방지 + } public static void main(String[] args) { SpringApplication.run(TransactionApplication.class, args); } diff --git a/src/main/java/HeyPorori/transaction/config/AmazonS3Config.java b/src/main/java/HeyPorori/transaction/config/AmazonS3Config.java new file mode 100644 index 0000000..8a767f4 --- /dev/null +++ b/src/main/java/HeyPorori/transaction/config/AmazonS3Config.java @@ -0,0 +1,36 @@ +package HeyPorori.transaction.config; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +@Configuration +public class AmazonS3Config { + @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 + @Primary + public BasicAWSCredentials awsCredentialsProvider(){ + return new BasicAWSCredentials(accessKey, secretKey); + } + + @Bean + public AmazonS3 amazonS3Client() { + return AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCredentialsProvider())) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/HeyPorori/transaction/controller/TransactionController.java b/src/main/java/HeyPorori/transaction/controller/TransactionController.java index c57195e..e4edcb4 100644 --- a/src/main/java/HeyPorori/transaction/controller/TransactionController.java +++ b/src/main/java/HeyPorori/transaction/controller/TransactionController.java @@ -4,13 +4,14 @@ import HeyPorori.transaction.config.BaseResponse; import HeyPorori.transaction.config.BaseResponseStatus; import HeyPorori.transaction.dto.PostReq; +import HeyPorori.transaction.dto.PreSignedUrlRes; +import HeyPorori.transaction.service.AmazonS3Service; import HeyPorori.transaction.service.TransactionService; import HeyPorori.transaction.service.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @@ -24,6 +25,7 @@ public class TransactionController { private final TransactionService transactionService; private final UserService userService; + private final AmazonS3Service amazonS3Service; // 테스트용 APIs @Operation(summary = "Swagger UI 테스트용 메서드", description = "프로젝트 초기 Swagger UI 정상작동을 확인하기 위한 메서드입니다.") @@ -47,4 +49,11 @@ public BaseResponse createPost(@RequestHeader("Authorization transactionService.createPost(token, postReq); return new BaseResponse<>(BaseResponseStatus.SUCCESS); } + + @Operation(summary = "Pre-Signed Url 발급 API", description = "AWS S3 이미지 업로드 권한을 요청하기 위한 API입니다.") + @ApiResponse(responseCode = "200", description = "요청 성공", content = @Content(mediaType = "application/json", schema = @Schema(implementation = BaseResponse.class))) + @GetMapping("/url") + public BaseResponse getPreSignedUrl(@RequestHeader("Authorization") String token) throws BaseException { + return new BaseResponse<>(amazonS3Service.getPreSignedUrl()); + } } diff --git a/src/main/java/HeyPorori/transaction/dto/PreSignedUrlRes.java b/src/main/java/HeyPorori/transaction/dto/PreSignedUrlRes.java new file mode 100644 index 0000000..7b2fad2 --- /dev/null +++ b/src/main/java/HeyPorori/transaction/dto/PreSignedUrlRes.java @@ -0,0 +1,14 @@ +package HeyPorori.transaction.dto; + +import lombok.Builder; +import lombok.Getter; + +@Getter +public class PreSignedUrlRes { + private String url; + + @Builder + public PreSignedUrlRes(String url) { + this.url = url; + } +} diff --git a/src/main/java/HeyPorori/transaction/service/AmazonS3Service.java b/src/main/java/HeyPorori/transaction/service/AmazonS3Service.java new file mode 100644 index 0000000..0aef55b --- /dev/null +++ b/src/main/java/HeyPorori/transaction/service/AmazonS3Service.java @@ -0,0 +1,55 @@ +package HeyPorori.transaction.service; + +import HeyPorori.transaction.dto.PreSignedUrlRes; +import com.amazonaws.HttpMethod; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.Headers; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import javax.transaction.Transactional; +import java.util.Date; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +@Transactional +public class AmazonS3Service { + private final AmazonS3 amazonS3; + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + public PreSignedUrlRes getPreSignedUrl(){ + String uuid = UUID.randomUUID().toString(); + String objectKey = "transactions/"+uuid; + + GeneratePresignedUrlRequest request = generatePresignedUrlRequest(bucket, objectKey); + PreSignedUrlRes response = PreSignedUrlRes.builder() + .url(amazonS3.generatePresignedUrl(request).toString()) + .build(); + return response; + } + + private GeneratePresignedUrlRequest generatePresignedUrlRequest(String bucket, String imageName){ + Date expiration = new Date(); + long expTimeMillis = expiration.getTime(); + expTimeMillis += 1000 * 60 * 5; // 5분 + expiration.setTime(expTimeMillis); + + //Pre-Signed Url request 생성 + GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucket, imageName) + .withMethod(HttpMethod.PUT) + .withExpiration(expiration); + + //request 파라미터 추가 + request.addRequestParameter( + Headers.S3_CANNED_ACL, + CannedAccessControlList.PublicRead.toString()); + + return request; + } +} \ No newline at end of file