Skip to content
5 changes: 3 additions & 2 deletions src/main/java/com/swyp/picke/domain/oauth/jwt/JwtFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ public class JwtFilter extends OncePerRequestFilter {
"/favicon.ico",
"/api/v1/auth/login",
"/api/v1/auth/refresh",
"/swagger-ui",
"/v3/api-docs",
"/swagger-ui",
"/v3/api-docs",
"/webjars",
"/api/v1/home",
"/api/v1/notices",
"/api/test",
Expand Down
19 changes: 16 additions & 3 deletions src/main/java/com/swyp/picke/global/config/S3Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,34 @@
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;

import java.net.URI;

@Configuration
public class S3Config {

@Bean
public S3Presigner s3Presigner(
@Value("${spring.cloud.aws.region.static}") String region,
@Value("${spring.cloud.aws.credentials.access-key}") String accessKey,
@Value("${spring.cloud.aws.credentials.secret-key}") String secretKey) {
@Value("${spring.cloud.aws.credentials.secret-key}") String secretKey,
@Value("${spring.cloud.aws.s3.endpoint:}") String endpoint,
@Value("${spring.cloud.aws.s3.path-style-access-enabled:false}") boolean pathStyleAccessEnabled) {

return S3Presigner.builder()
S3Presigner.Builder builder = S3Presigner.builder()
.region(Region.of(region))
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(accessKey, secretKey)))
.build();
.serviceConfiguration(S3Configuration.builder()
.pathStyleAccessEnabled(pathStyleAccessEnabled)
.build());

if (!endpoint.isBlank()) {
builder.endpointOverride(URI.create(endpoint));
}

return builder.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
"/api/v1/notices/**",
"/api/test/**",
"/swagger-ui/**", "/swagger-ui.html", "/v3/api-docs/**",
"/webjars/**",
"/js/**", "/css/**", "/favicon.ico",
"/api/v1/admin/login", "/api/v1/admin",
"/result/**",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;

import java.net.URI;
import java.time.Duration;
import java.util.Map;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -54,11 +55,16 @@ public <K> Map<K, String> generatePresignedUrls(Map<K, String> keyMap) {
));
}

/**
* DB에는 보통 순수 key만 저장되지만, AWS S3를 쓰던 시절 저장된 레거시 데이터는
* 전체 URL(https://{bucket}.s3.{region}.amazonaws.com/{key})일 수 있어 하위 호환을 위해 파싱한다.
* 스토리지 제공자(AWS/Railway 등)에 무관하게 동작하도록 호스트가 아닌 경로 기준으로 key를 뽑는다.
*/
private String extractKey(String input) {
if (input.startsWith("https://") && input.contains(".s3.") && input.contains(".amazonaws.com/")) {
int idx = input.indexOf(".amazonaws.com/") + ".amazonaws.com/".length();
return input.substring(idx);
if (!input.startsWith("http://") && !input.startsWith("https://")) {
return input;
}
return input;
String path = URI.create(input).getPath();
return path.startsWith("/") ? path.substring(1) : path;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;

import java.io.File;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
Expand Down Expand Up @@ -78,7 +79,7 @@ public File downloadFile(String fileUrl) {

try {
// URL에서 순수 Key만 추출
String pureKey = fileUrl.contains(".com/") ? fileUrl.split(".com/")[1] : fileUrl;
String pureKey = extractKey(fileUrl);

// 다운로드 받을 로컬 임시 파일 경로 생성
File tempFile = File.createTempFile("s3_download_", ".mp3");
Expand Down Expand Up @@ -112,7 +113,7 @@ public String getPresignedUrl(String key, Duration durationUrl) {
if (key == null || key.isEmpty()) return null;

// URL에서 도메인을 제외한 순수 'Key'만 추출 (만약 전체 URL이 들어올 경우를 대비)
String pureKey = key.contains(".com/") ? key.split(".com/")[1] : key;
String pureKey = extractKey(key);

GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder()
.signatureDuration(durationUrl)
Expand All @@ -122,6 +123,18 @@ public String getPresignedUrl(String key, Duration durationUrl) {
return s3Presigner.presignGetObject(presignRequest).url().toString();
}

/**
* DB/외부 입력에는 순수 key가 오는 게 기본이지만, 레거시 데이터나 실수로 전체 URL이
* 들어오는 경우를 대비해 호스트가 아닌 경로 기준으로 key를 추출한다(스토리지 제공자 무관).
*/
private String extractKey(String input) {
if (!input.startsWith("http://") && !input.startsWith("https://")) {
return input;
}
String path = URI.create(input).getPath();
return path.startsWith("/") ? path.substring(1) : path;
}

private String determineContentType(String key) {
if (key.endsWith(".mp3")) return "audio/mpeg";
if (key.endsWith(".png")) return "image/png";
Expand Down Expand Up @@ -149,7 +162,7 @@ public void deleteFile(String fileUrl) {

try {
// 1. 전체 URL에서 순수 Key 추출 (기존 getPresignedUrl에 있던 방식과 동일하게 처리)
String pureKey = fileUrl.contains(".com/") ? fileUrl.split(".com/")[1] : fileUrl;
String pureKey = extractKey(fileUrl);

// 2. AWS SDK v2 전용 삭제 요청 객체 생성
DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder()
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ spring:
aws:
s3:
bucket: ${AWS_S3_BUCKET}
endpoint: ${AWS_ENDPOINT_URL:}
path-style-access-enabled: ${S3_PATH_STYLE_ACCESS_ENABLED:false}
region:
static: ${AWS_REGION}
credentials:
Expand Down
Loading