Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] S3 PreSignedUrl 발급 로직 구현 #48

Merged
merged 6 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
Expand All @@ -29,6 +30,11 @@ public class AuthController {
private final AuthServiceProvider authServiceProvider;
private final JwtService jwtService;

@Value("${spring.security.oauth2.client.naver.client-id}")
private String clientId;
@Value("${spring.security.oauth2.client.naver.redirect-uri}")
private String redirectUri;

@PostMapping("/social/login")
public ResponseEntity<ApiResponse<MemberAuthResponseDTO>> login(
@RequestHeader(value = "authorization-code") final String authorizationCode,
Expand All @@ -49,10 +55,11 @@ public ResponseEntity<ApiResponse<MemberReissueResponseDTO>> reissue(HttpServlet

@GetMapping("/authTest")
public String authTest(HttpServletRequest request, HttpServletResponse response) {

String redirectURL = "https://nid.naver.com/oauth2.0/authorize?client_id=" + clientId
+ "&redirect_uri=" + redirectUri + "&response_type=code";
try {
response.sendRedirect(
"https://nid.naver.com/oauth2.0/authorize?&client_id=iuUGaSHmzPMxKmNlc0BD&redirect_uri=http://localhost:8080/login/oauth2/code/naver&response_type=code");
redirectURL);
Comment on lines -52 to +62
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 이렇게 바꾸니 좋네요! 보안 생각하면 이게 맞겠네요 굿굿!

} catch (Exception e) {
log.info("authTest = {}", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import com.nonsoolmate.nonsoolmateServer.domain.university.exception.UniversityExamException;
import com.nonsoolmate.nonsoolmateServer.domain.university.exception.UniversityExamExceptionType;
import com.nonsoolmate.nonsoolmateServer.domain.university.repository.UniversityExamImageRepository;
import com.nonsoolmate.nonsoolmateServer.external.aws.service.S3Service;
import com.nonsoolmate.nonsoolmateServer.external.aws.service.CloudFrontService;
import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
Expand All @@ -28,7 +28,7 @@ public class UniversityExamService {

private final UniversityExamRepository universityExamRepository;
private final UniversityExamImageRepository universityExamImageRepository;
private final S3Service s3Service;
private final CloudFrontService cloudFrontService;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

따로 분리하는게 맞는거 같네요 좋습니다~!



public UniversityExamInfoResponseDTO getUniversityExam(Long universityExamId) {
Expand All @@ -55,15 +55,15 @@ public UniversityExamImageAndAnswerResponseDTO getUniversityExamImageAndAnswer(L
.orElseThrow(() -> new UniversityExamException(
UniversityExamExceptionType.NOT_FOUND_UNIVERSITY_EXAM));

String examAnswerUrl = s3Service.createPresignedGetUrl(EXAM_ANSWER_PATH,
String examAnswerUrl = cloudFrontService.createPreSignedGetUrl(EXAM_ANSWER_PATH,
universityExam.getExamAnswerFileName());
List<UniversityExamImageResponseDTO> examImageUrls = new ArrayList<>();

List<UniversityExamImage> UniversityExamImages = universityExamImageRepository.findAllByUniversityExam(
universityExam);

UniversityExamImages.stream().forEach(universityExamImage -> {
String presignedGetUrl = s3Service.createPresignedGetUrl(EXAM_IMAGE_PATH,
String presignedGetUrl = cloudFrontService.createPreSignedGetUrl(EXAM_IMAGE_PATH,
universityExamImage.getUniversityExamImageFileName());

examImageUrls.add(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.nonsoolmate.nonsoolmateServer.domain.universityExamRecord.service;

import static com.nonsoolmate.nonsoolmateServer.domain.university.exception.UniversityExamExceptionType.NOT_FOUND_UNIVERSITY_EXAM;
import static com.nonsoolmate.nonsoolmateServer.external.aws.FolderName.EXAM_ANSWER_FOLDER_NAME;
import static com.nonsoolmate.nonsoolmateServer.external.aws.FolderName.EXAM_RESULT_FOLDER_NAME;

import com.nonsoolmate.nonsoolmateServer.domain.member.entity.Member;
import com.nonsoolmate.nonsoolmateServer.domain.university.entity.UniversityExam;
Expand All @@ -9,7 +11,7 @@
import com.nonsoolmate.nonsoolmateServer.domain.universityExamRecord.controller.dto.UniversityExamRecordResponseDTO;
import com.nonsoolmate.nonsoolmateServer.domain.universityExamRecord.entity.UniversityExamRecord;
import com.nonsoolmate.nonsoolmateServer.domain.universityExamRecord.repository.UniversityExamRecordRepository;
import com.nonsoolmate.nonsoolmateServer.external.aws.service.S3Service;
import com.nonsoolmate.nonsoolmateServer.external.aws.service.CloudFrontService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -18,14 +20,9 @@
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class UniversityExamRecordService {

private static final String EXAM_ANSWER_FOLDER_NAME = "exam-answer/";
private static final String EXAM_RESULT_FOLDER_NAME = "exam-result/";


private final UniversityExamRecordRepository universityExamRecordRepository;
private final UniversityExamRepository universityExamRepository;
private final S3Service s3Service;
private final CloudFrontService cloudFrontService;

public UniversityExamRecordResponseDTO getUniversityExamRecord(Long universityExamId, Member member) {

Expand All @@ -35,9 +32,9 @@ public UniversityExamRecordResponseDTO getUniversityExamRecord(Long universityEx
UniversityExamRecord universityExamRecord = universityExamRecordRepository.findByUniversityExamAndMemberOrElseThrowException(
universityExam, member);

String answerUrl = s3Service.createPresignedGetUrl(EXAM_ANSWER_FOLDER_NAME,
String answerUrl = cloudFrontService.createPreSignedGetUrl(EXAM_ANSWER_FOLDER_NAME,
universityExam.getExamAnswerFileName());
String resultUrl = s3Service.createPresignedGetUrl(EXAM_RESULT_FOLDER_NAME,
String resultUrl = cloudFrontService.createPreSignedGetUrl(EXAM_RESULT_FOLDER_NAME,
universityExamRecord.getExamRecordResultFileName());

return UniversityExamRecordResponseDTO.of(answerUrl, resultUrl);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.nonsoolmate.nonsoolmateServer.external.aws;

import lombok.Getter;

@Getter
public class FolderName {
public static final String EXAM_ANSWER_FOLDER_NAME = "exam-answer/";
public static final String EXAM_RESULT_FOLDER_NAME = "exam-result/";
public static final String EXAM_SHEET_FOLDER_NAME = "exam-sheet/";
}
Comment on lines +6 to +10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

따로 분리하는 거 넘 좋습니다!

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.nonsoolmate.nonsoolmateServer.external.aws.service;

import com.amazonaws.services.cloudfront.CloudFrontUrlSigner;
import com.amazonaws.services.cloudfront.util.SignerUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.spec.InvalidKeySpecException;
import java.time.Duration;
import java.util.Date;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.ResourceUtils;


@Component
@RequiredArgsConstructor
@Slf4j
public class CloudFrontService {
@Value("${aws-property.distribution-domain}")
private String distributionDomain;

@Value("${aws-property.private-key-file-path}")
private String privateKeyFilePath;

@Value("${aws-property.key-pair-id}")
private String keyPairId;

public String createPreSignedGetUrl(String path, String fileName) {
String s3ObjectKey = path + fileName; // S3 객체 키
Duration duration = Duration.ofMinutes(1);
Date expirationTime = new Date(System.currentTimeMillis() + duration.toMillis());
SignerUtils.Protocol protocol = SignerUtils.Protocol.https;
String signedUrl = null;
try {
File privateKeyFile = ResourceUtils.getFile(privateKeyFilePath);
signedUrl = CloudFrontUrlSigner.getSignedURLWithCannedPolicy(
protocol,
distributionDomain,
privateKeyFile,
s3ObjectKey,
keyPairId,
expirationTime
);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (InvalidKeySpecException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
return signedUrl;
}
}
Original file line number Diff line number Diff line change
@@ -1,72 +1,50 @@
package com.nonsoolmate.nonsoolmateServer.external.aws.service;

import com.amazonaws.services.cloudfront.CloudFrontUrlSigner;
import com.amazonaws.services.cloudfront.util.SignerUtils;
import com.nonsoolmate.nonsoolmateServer.external.aws.config.AWSConfig;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.spec.InvalidKeySpecException;
import com.nonsoolmate.nonsoolmateServer.external.aws.service.vo.PreSignedUrlVO;
import java.time.Duration;
import java.util.Date;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.ResourceUtils;
import org.springframework.stereotype.Service;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest;
import java.net.URL;


@Component
@Service
@RequiredArgsConstructor
@Slf4j
public class S3Service {

@Value("${aws-property.s3-bucket-name}")
private String bucketName;
private final AWSConfig awsConfig;
private static final Long PRE_SIGNED_URL_EXPIRE_MINUTE = 1L;

@Value("${aws-property.distribution-domain}")
private String distributionDomain;

@Value("${aws-property.private-key-file-path}")
private String privateKeyFilePath;
public PreSignedUrlVO getUploadPreSignedUrl(final String prefix) {
String uuidFileName = generateZipFileName();
String key = prefix + uuidFileName;

@Value("${aws-property.key-pair-id}")
private String keyPairId;
S3Presigner preSigner = awsConfig.getS3Presigner();

private static final Long PRE_SIGNED_URL_EXPIRE_MINUTE = 1L;
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
.bucket(bucketName)
.key(key)
.build();

public String createPresignedGetUrl(String path, String fileName) {
final String key = path + fileName;
String s3ObjectKey = key; // S3 객체 키
PutObjectPresignRequest preSignedUrlRequest = PutObjectPresignRequest.builder()
.signatureDuration(Duration.ofMinutes(PRE_SIGNED_URL_EXPIRE_MINUTE))
.putObjectRequest(putObjectRequest)
.build();

// 현재 시간에서 1시간 유효한 URL 생성
Duration duration = Duration.ofHours(1);
Date expirationTime = new Date(System.currentTimeMillis() + duration.toMillis());
File privateKeyFile = null;
String signedUrl = null;
SignerUtils.Protocol protocol = SignerUtils.Protocol.https;
try {
privateKeyFile = ResourceUtils.getFile(privateKeyFilePath);
URL url = preSigner.presignPutObject(preSignedUrlRequest).url();

signedUrl = CloudFrontUrlSigner.getSignedURLWithCannedPolicy(
protocol,
distributionDomain,
privateKeyFile,
s3ObjectKey,
keyPairId,
expirationTime
);
return PreSignedUrlVO.of(uuidFileName, url.toString());
}

System.out.println("Signed URL: " + signedUrl);
}catch (FileNotFoundException e){
log.info("FileNotFoundException = {}", e);
}catch (IOException e){
log.info("IOException = {}", e);
}catch (InvalidKeySpecException e){
log.info("InvalidKeySpecException = {}", e);
}
return signedUrl;
private String generateZipFileName() {
return UUID.randomUUID() + ".zip";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.nonsoolmate.nonsoolmateServer.external.aws.service.vo;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class PreSignedUrlVO {
private String fileName;
private String url;

public static PreSignedUrlVO of(String fileName, String url) {
return new PreSignedUrlVO(fileName, url);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
@Transactional(readOnly = true)
public class NaverAuthService extends AuthService {

@Value("${naver.client-id}")
@Value("${spring.security.oauth2.client.naver.client-id}")
private String clientId;
@Value("${naver.client-secret}")
@Value("${spring.security.oauth2.client.naver.client-secret}")
private String clientSecret;
@Value("${naver.state}")
@Value("${spring.security.oauth2.client.naver.state}")
private String state;

public NaverAuthService(MemberRepository memberRepository) {
Expand Down
Loading