Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
17 changes: 11 additions & 6 deletions src/main/java/org/sopt/app/application/auth/JwtTokenService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
import io.jsonwebtoken.security.Keys;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Base64;
import java.util.Date;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.joda.time.LocalDateTime;

import org.sopt.app.application.auth.dto.PlaygroundAuthTokenInfo.AppToken;
import org.sopt.app.common.exception.UnauthorizedException;
import org.sopt.app.common.response.ErrorCode;
Expand Down Expand Up @@ -44,13 +46,14 @@ public AppToken issueNewTokens(Long userId, Long playgroundId) {

private String encodeJwtToken(Long userId, Long playgroundId) {
val now = LocalDateTime.now();

val nowDate = Date.from(now.atZone(ZoneId.systemDefault()).toInstant());
val nowDate1PlusDays = Date.from(now.plusDays(1).atZone(ZoneId.systemDefault()).toInstant());
return Jwts.builder()
.setHeaderParam(Header.TYPE, Header.JWT_TYPE)
.setIssuer("sopt-makers")
.setIssuedAt(now.toDate())
.setIssuedAt(nowDate)
.setSubject(userId.toString())
.setExpiration(now.plusDays(1).toDate())
.setExpiration(nowDate1PlusDays)
.claim("id", userId)
.claim("playgroundId", playgroundId)
.claim("roles", "USER")
Expand All @@ -60,10 +63,12 @@ private String encodeJwtToken(Long userId, Long playgroundId) {

private String encodeJwtRefreshToken(Long userId) {
val now = LocalDateTime.now();
val nowDate = Date.from(now.atZone(ZoneId.systemDefault()).toInstant());
val nowDate30PlusDays = Date.from(now.plusDays(30).atZone(ZoneId.systemDefault()).toInstant());
return Jwts.builder()
.setIssuedAt(now.toDate())
.setIssuedAt(nowDate)
.setSubject(userId.toString())
.setExpiration(now.plusDays(30).toDate())
.setExpiration(nowDate30PlusDays)
.claim("id", userId)
.claim("roles", "USER")
.signWith(getSigningKey(JWT_SECRET), SignatureAlgorithm.HS256)
Expand Down
100 changes: 68 additions & 32 deletions src/main/java/org/sopt/app/application/s3/S3Service.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
package org.sopt.app.application.s3;

import com.amazonaws.*;
import com.amazonaws.services.s3.*;
import java.io.*;
import java.net.*;
import java.util.*;
import lombok.*;
import org.joda.time.LocalDateTime;

import software.amazon.awssdk.core.exception.SdkException;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.PresignedPutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest;

import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.List;
import java.util.UUID;

import lombok.RequiredArgsConstructor;
import lombok.val;
import org.slf4j.LoggerFactory;
import org.sopt.app.application.stamp.StampDeletedEvent;
import org.sopt.app.common.exception.BadRequestException;
Expand All @@ -25,29 +36,43 @@ public class S3Service {
@Value("${cloud.aws.s3.uri}")
private String baseURI;

private final AmazonS3 amazonS3;
private final S3Client s3Client;
private final S3Presigner s3Presigner;

public S3Info.PreSignedUrl getPreSignedUrl(String folderName) {
val now = LocalDateTime.now();
val folderURI = bucket + "/mainpage/makers-app-img/" + folderName;
val randomFileName = UUID.randomUUID();
String keyPrefix = "mainpage/makers-app-img/" + folderName + "/";
String randomFileName = UUID.randomUUID().toString();
String objectKey = keyPrefix + randomFileName;

Duration expirationDuration = Duration.ofHours(1); // 1시간 유효기간

URI uri;
try {
// TODO: EC2와 자바의 시간이 UTC와 KST로 9시간이 차이나 있어 10시간을 더함, 추후 수정 필요
uri = amazonS3.generatePresignedUrl(folderURI,
randomFileName.toString(), now.plusHours(10).toDate(), HttpMethod.PUT).toURI();
} catch (NullPointerException | URISyntaxException e) {
throw new BadRequestException(ErrorCode.PRE_SIGNED_URI_ERROR);
}
PutObjectRequest objectRequest = PutObjectRequest.builder()
.bucket(bucket)
.key(objectKey)
.build();

PutObjectPresignRequest presignRequest = PutObjectPresignRequest.builder()
.signatureDuration(expirationDuration)
.putObjectRequest(objectRequest)
.build();

val preSignedURL = uri.toString();
val imageURL = baseURI + folderName + "/" + randomFileName;
PresignedPutObjectRequest presignedPutObjectRequest = s3Presigner.presignPutObject(presignRequest);
URI uri = presignedPutObjectRequest.url().toURI();
String preSignedURL = uri.toString();
String imageURL = baseURI + folderName + "/" + randomFileName;
return S3Info.PreSignedUrl.builder()
.preSignedURL(preSignedURL)
.imageURL(imageURL)
.build();

return S3Info.PreSignedUrl.builder()
.preSignedURL(preSignedURL)
.imageURL(imageURL)
.build();
} catch (S3Exception | URISyntaxException e) {
LoggerFactory.getLogger(S3Service.class).error("Error generating presigned URL: {}", e.getMessage());
throw new BadRequestException(ErrorCode.PRE_SIGNED_URI_ERROR);
} catch (SdkException e) {
LoggerFactory.getLogger(S3Service.class).error("AWS SDK error generating presigned URL: {}", e.getMessage());
throw new BadRequestException(ErrorCode.PRE_SIGNED_URI_ERROR);
}
}

@EventListener(StampDeletedEvent.class)
Expand All @@ -56,9 +81,12 @@ public void handleStampDeletedEvent(StampDeletedEvent event) {
}

private void deleteFiles(List<String> fileUrls, String folderName) {
val folderURI = bucket + "/mainpage/makers-app-img/" + folderName;
val fileNameList = getFileNameList(fileUrls);
fileNameList.forEach(file -> deleteFile(folderURI, file));
String keyPrefix = "mainpage/makers-app-img/" + folderName + "/";
List<String> fileNameList = getFileNameList(fileUrls);
fileNameList.forEach(fileName -> {
String objectKey = keyPrefix + fileName;
deleteFile(objectKey);
});
}

private List<String> getFileNameList(List<String> fileUrls) {
Expand All @@ -68,11 +96,19 @@ private List<String> getFileNameList(List<String> fileUrls) {
}).toList();
}

private void deleteFile(String folderURI, String fileName) {
private void deleteFile(String objectKey) {
try {
amazonS3.deleteObject(folderURI, fileName.replace(File.separatorChar, '/'));
} catch (AmazonServiceException e) {
LoggerFactory.getLogger(S3Service.class).error(e.getErrorMessage());
DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder()
.bucket(bucket)
.key(objectKey)
.build();
s3Client.deleteObject(deleteObjectRequest);
LoggerFactory.getLogger(S3Service.class).info("Successfully deleted S3 object: {}", objectKey);
} catch (S3Exception e) {
// SDK v2 예외 처리
LoggerFactory.getLogger(S3Service.class).error("Error deleting S3 object {}: {}", objectKey, e.awsErrorDetails().errorMessage());
} catch (SdkException e) {
LoggerFactory.getLogger(S3Service.class).error("AWS SDK error deleting S3 object {}: {}", objectKey, e.getMessage());
Comment on lines +108 to +111
Copy link

Copilot AI Apr 25, 2025

Choose a reason for hiding this comment

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

In the deleteFile method, errors caught from SdkException and S3Exception are only logged without propagating the failure. Consider rethrowing the exception or handling it appropriately if deletion failure must be communicated to callers.

Suggested change
// SDK v2 예외 처리
LoggerFactory.getLogger(S3Service.class).error("Error deleting S3 object {}: {}", objectKey, e.awsErrorDetails().errorMessage());
} catch (SdkException e) {
LoggerFactory.getLogger(S3Service.class).error("AWS SDK error deleting S3 object {}: {}", objectKey, e.getMessage());
LoggerFactory.getLogger(S3Service.class).error("Error deleting S3 object {}: {}", objectKey, e.awsErrorDetails().errorMessage());
throw new RuntimeException("Failed to delete S3 object: " + objectKey, e);
} catch (SdkException e) {
LoggerFactory.getLogger(S3Service.class).error("AWS SDK error deleting S3 object {}: {}", objectKey, e.getMessage());
throw new RuntimeException("AWS SDK error deleting S3 object: " + objectKey, e);

Copilot uses AI. Check for mistakes.
}
}
}
}
22 changes: 16 additions & 6 deletions src/main/java/org/sopt/app/common/config/AwsConfig.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
package org.sopt.app.common.config;

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 software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;

@Configuration
public class AwsConfig {

@Value("${cloud.aws.region.static}")
private String region;
private String regionValue;

@Bean
public AmazonS3 amazonS3() {
return AmazonS3ClientBuilder.standard()
.withRegion(region)
public S3Client s3Client() {
Region region = Region.of(regionValue);
return S3Client.builder()
.region(region)
.build();
}

@Bean
public S3Presigner s3Presigner() {
Region region = Region.of(regionValue);
return S3Presigner.builder()
.region(region)
.build();
}
}
Loading