From eed0af37ec6a9531bbfbec42671203609058fefa Mon Sep 17 00:00:00 2001 From: si-zero Date: Mon, 7 Sep 2026 10:59:14 +0900 Subject: [PATCH 1/6] =?UTF-8?q?refactor:=20S3=20=EC=97=94=EB=93=9C?= =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=8A=B8/path-style=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=EC=9D=84=20=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98=EB=A1=9C=20?= =?UTF-8?q?=EC=98=A4=EB=B2=84=EB=9D=BC=EC=9D=B4=EB=93=9C=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=ED=95=98=EA=B2=8C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 60c036c8..faff467b 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -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: From ffdb1f4b3928221e4d5ce0180c9b205faec0a9be Mon Sep 17 00:00:00 2001 From: si-zero Date: Mon, 7 Sep 2026 10:59:19 +0900 Subject: [PATCH 2/6] =?UTF-8?q?refactor:=20S3Presigner=EC=97=90=20endpoint?= =?UTF-8?q?=20override=EC=99=80=20path-style=20access=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../swyp/picke/global/config/S3Config.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/swyp/picke/global/config/S3Config.java b/src/main/java/com/swyp/picke/global/config/S3Config.java index f2e88717..923505ed 100644 --- a/src/main/java/com/swyp/picke/global/config/S3Config.java +++ b/src/main/java/com/swyp/picke/global/config/S3Config.java @@ -6,8 +6,11 @@ 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 { @@ -15,12 +18,22 @@ public class S3Config { 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(); } } From 941f7b9398bc039f9bb4cd7443a73261041b707f Mon Sep 17 00:00:00 2001 From: si-zero Date: Mon, 7 Sep 2026 10:59:23 +0900 Subject: [PATCH 3/6] =?UTF-8?q?refactor:=20presigned=20URL=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=8B=9C=20key=20=EC=B6=94=EC=B6=9C=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=9D=84=20=EC=8A=A4=ED=86=A0=EB=A6=AC=EC=A7=80=20?= =?UTF-8?q?=EC=A0=9C=EA=B3=B5=EC=9E=90=20=EB=AC=B4=EA=B4=80=ED=95=98?= =?UTF-8?q?=EA=B2=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infra/s3/service/S3PresignedUrlService.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/swyp/picke/global/infra/s3/service/S3PresignedUrlService.java b/src/main/java/com/swyp/picke/global/infra/s3/service/S3PresignedUrlService.java index 7edc0d70..4c23e88b 100644 --- a/src/main/java/com/swyp/picke/global/infra/s3/service/S3PresignedUrlService.java +++ b/src/main/java/com/swyp/picke/global/infra/s3/service/S3PresignedUrlService.java @@ -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; @@ -54,11 +55,16 @@ public Map generatePresignedUrls(Map 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; } } From ffc9aa77a971cb5dcc4098cf4277ad5f9524ec1e Mon Sep 17 00:00:00 2001 From: si-zero Date: Mon, 7 Sep 2026 10:59:28 +0900 Subject: [PATCH 4/6] =?UTF-8?q?refactor:=20S3=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C/=EB=8B=A4=EC=9A=B4=EB=A1=9C=EB=93=9C/=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EC=8B=9C=20key=20=EC=B6=94=EC=B6=9C=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=9D=84=20=EC=8A=A4=ED=86=A0=EB=A6=AC=EC=A7=80=20?= =?UTF-8?q?=EC=A0=9C=EA=B3=B5=EC=9E=90=20=EB=AC=B4=EA=B4=80=ED=95=98?= =?UTF-8?q?=EA=B2=8C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../infra/s3/service/S3UploadServiceImpl.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/swyp/picke/global/infra/s3/service/S3UploadServiceImpl.java b/src/main/java/com/swyp/picke/global/infra/s3/service/S3UploadServiceImpl.java index 89004aaa..eb276bed 100644 --- a/src/main/java/com/swyp/picke/global/infra/s3/service/S3UploadServiceImpl.java +++ b/src/main/java/com/swyp/picke/global/infra/s3/service/S3UploadServiceImpl.java @@ -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; @@ -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"); @@ -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) @@ -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"; @@ -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() From 38c967f841a5b9bd52a2d807a027eb51eeb4bf45 Mon Sep 17 00:00:00 2001 From: si-zero Date: Mon, 7 Sep 2026 10:59:33 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20?= =?UTF-8?q?=EC=A0=95=EC=A0=81=20=EB=A6=AC=EC=86=8C=EC=8A=A4(webjars)?= =?UTF-8?q?=EA=B0=80=20=EC=9D=B8=EC=A6=9D=20=EC=97=86=EC=9D=B4=20=EC=A0=91?= =?UTF-8?q?=EA=B7=BC=EB=90=98=EB=8F=84=EB=A1=9D=20=ED=97=88=EC=9A=A9=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/swyp/picke/global/config/SecurityConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/swyp/picke/global/config/SecurityConfig.java b/src/main/java/com/swyp/picke/global/config/SecurityConfig.java index d0037617..d48f9003 100644 --- a/src/main/java/com/swyp/picke/global/config/SecurityConfig.java +++ b/src/main/java/com/swyp/picke/global/config/SecurityConfig.java @@ -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/**", From 9fb7321af0c232cf491696624bb18ce4b6a2b5f3 Mon Sep 17 00:00:00 2001 From: si-zero Date: Mon, 7 Sep 2026 10:59:37 +0900 Subject: [PATCH 6/6] =?UTF-8?q?fix:=20JwtFilter=20=ED=99=94=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=EB=A6=AC=EC=8A=A4=ED=8A=B8=EC=97=90=EB=8F=84=20webjar?= =?UTF-8?q?s=20=EA=B2=BD=EB=A1=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/swyp/picke/domain/oauth/jwt/JwtFilter.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/swyp/picke/domain/oauth/jwt/JwtFilter.java b/src/main/java/com/swyp/picke/domain/oauth/jwt/JwtFilter.java index 474f9158..e3172bcc 100644 --- a/src/main/java/com/swyp/picke/domain/oauth/jwt/JwtFilter.java +++ b/src/main/java/com/swyp/picke/domain/oauth/jwt/JwtFilter.java @@ -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",