Skip to content

Conversation

@kaswhy
Copy link
Member

@kaswhy kaswhy commented Aug 24, 2025

📌 연관된 이슈

✨ 작업 내용

  • aws 의존성 버전 업데이트 -> 보안 관련 내용 다시 재설정 해야 됨
  • swagger 설정 추가 -> 명세서 일단 구현/리팩토링 후에 다시

💬 리뷰 요구사항(선택)

Summary by CodeRabbit

  • New Features

    • Swagger/OpenAPI UI 도입으로 API 문서 열람 가능(버전별 v1, v2 그룹 제공).
    • .env(.properties) 파일을 통한 환경 설정 로딩 지원(없어도 정상 구동).
  • Bug Fixes

    • 인증 필터가 Swagger 경로, 일부 공개 API 및 OPTIONS 사전요청을 우회하도록 수정해 접근 오류 해소.
  • Refactor

    • S3 클라이언트 마이그레이션으로 파일 업로드 및 URL 조회 안정성·성능 개선.
    • 패키지 구조 및 유틸/토큰 관련 경로 정비.
  • Chores

    • AWS 의존성 BOM 적용 및 빌드/라이브러리 버전 조정.

@kaswhy kaswhy self-assigned this Aug 24, 2025
@kaswhy kaswhy added the enhancement New feature or request label Aug 24, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 24, 2025

Walkthrough

Spring 설정과 패키지 구조를 inha.gdgoc.global.*로 이동, AWS S3 SDK v1→v2 마이그레이션, DotenvLoader 삭제 및 대신 optional .env import 추가, Springdoc/OpenAPI(및 Swagger UI) 추가, 보안 필터의 Swagger·특정 경로 예외 처리 추가, 일부 유틸/예외에 Lombok @Getter 적용.

Changes

Cohort / File(s) Summary
빌드·의존성
build.gradle
Spring Boot Gradle 플러그인 하향(3.4.1→3.3.6), io.awspring.cloud:spring-cloud-aws-starter-s3 및 AWS BOM 추가, org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0 추가.
환경 변수 로딩 삭제
src/main/java/inha/gdgoc/config/DotenvLoader.java
.env 로드하는 DotenvLoader 클래스 삭제.
AWS S3 마이그레이션
src/main/java/inha/gdgoc/config/S3Config.java (삭제),
src/main/java/inha/gdgoc/global/config/s3/S3Config.java,
src/main/java/inha/gdgoc/domain/resource/service/S3Service.java
기존 v1 AmazonS3 설정 제거, v2 S3Client 빈(Region, AwsCredentialsProvider 포함) 추가, S3Service를 AWS SDK v2 API로 리팩터링(PutObjectRequest, RequestBody, GetUrlRequest 등).
OpenAPI/Swagger 추가
src/main/java/inha/gdgoc/global/config/openapi/OpenApiConfig.java
Springdoc GroupedOpenApi 빈(전체, v1, v2) 추가. 경로 프리픽스 제거 커스터마이저 및 서버 URL 설정 로직 포함.
패키지 재배치·참조 업데이트
src/main/java/inha/gdgoc/global/config/jwt/JwtProperties.java
src/main/java/inha/gdgoc/global/config/jwt/TokenProvider.java
src/main/java/inha/gdgoc/global/config/querydsl/QueryDslConfig.java
src/main/java/inha/gdgoc/global/exception/NotFoundException.java
src/main/java/inha/gdgoc/global/util/EncryptUtil.java
src/main/java/inha/gdgoc/global/util/CookieUtil.java
여러 구성·유틸·예외 클래스 패키지를 inha.gdgoc.global.*로 이동, 관련 import 경로 업데이트(퍼블릭 시그니처 유효성 변화 포함).
보안 필터 예외 로직 추가
src/main/java/inha/gdgoc/global/security/TokenAuthenticationFilter.java
shouldNotFilter(HttpServletRequest) 오버라이드 추가로 /v3/api-docs, /swagger-ui*, /auth/, /test/, /game/, /apply/, /check/ 및 OPTIONS 요청을 인증 필터에서 제외.
SecurityConfig: Swagger 공개 경로 포함
src/main/java/inha/gdgoc/global/security/SecurityConfig.java
Swagger 관련 경로를 퍼블릭 엔드포인트 목록에 추가; 포맷/정렬 변경(기능 동일).
에러 응답 변경
src/main/java/inha/gdgoc/global/common/ErrorResponse.java
명시적 getter 삭제 후 클래스 레벨 Lombok @Getter 적용(동일한 필드 접근 제공).
application-*.yml: .env 임포트 추가
src/main/resources/application-dev.yml
src/main/resources/application-local.yml
src/main/resources/application-prod.yml
spring.config.import: optional:file:.env[.properties] 추가로 선택적 .env/.env.properties 파일 로드.
VCS ignore
.gitignore
.env.properties 항목 추가.
참조·포맷 변경(비기능적)
src/main/java/inha/gdgoc/domain/auth/service/AuthService.java
src/main/java/inha/gdgoc/domain/auth/service/RefreshTokenService.java
src/main/java/inha/gdgoc/domain/user/entity/User.java
src/main/java/inha/gdgoc/domain/user/service/UserService.java
패키지·정적 import 경로 업데이트 및 코드 포맷/정렬 변경, 기능적 변화 없음.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client as 클라이언트
  participant Security as SpringSecurity
  participant Filter as TokenAuthenticationFilter
  participant App as 애플리케이션

  Client->>Security: HTTP 요청
  activate Security
  Security->>Filter: doFilterInternal(...)
  alt shouldNotFilter = true (Swagger, /auth, /test, /game, /apply, /check, OPTIONS)
    Filter-->>Security: 필터 건너뜀
    Security->>App: 요청 전달
    App-->>Client: 응답 반환
  else shouldNotFilter = false
    Filter->>Filter: JWT 추출 및 검증
    Filter-->>Security: SecurityContext 설정 (성공 시)
    Security->>App: 요청 전달
    App-->>Client: 응답 (검증 실패 시 401)
  end
  deactivate Security
Loading
sequenceDiagram
  autonumber
  participant Caller as 호출자
  participant S3Service as S3Service
  participant S3Client as AWS_S3Client(v2)
  participant S3 as AWS_S3

  Caller->>S3Service: upload(file, userId, type)
  S3Service->>S3Service: key = user/{userId}/{type}/{filename}
  S3Service->>S3Client: putObject(PutObjectRequest, RequestBody.fromInputStream(...))
  S3Client->>S3: 업로드 요청
  S3-->>S3Client: 업로드 응답
  S3Client-->>S3Service: 업로드 완료
  S3Service->>S3Client: utilities().getUrl(GetUrlRequest{bucket,key})
  S3Client-->>S3Service: 파일 URL
  S3Service-->>Caller: 키 또는 URL 반환
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • [FEAT] swagger 적용 #179 — OpenApiConfig 추가 및 config 패키지 global로 이동 관련; 본 PR의 OpenAPI/패키지 재배치와 직접 연결됨.

Possibly related PRs

Poem

풀밭에서 깡충댄 커밋 토끼, 코드에 당근을 꽂았네 🥕
S3는 새 옷을 입고, 스웨거 길이 활짝 열렸네
패키지 옮겨 집도 넓히고, 필터는 문지기 역할 톡톡
.env는 조용히 숲에 숨기고, 개발자들 춤추게 하네 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/issue-179

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link

github-actions bot commented Aug 24, 2025

CI status
✅ Assemble 성공
✅ Test 성공

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (15)
src/main/java/inha/gdgoc/global/util/EncryptUtil.java (1)

12-33: 긴급: 비밀번호 해시 구현 보안 취약점 해결 (HmacSHA256 → BCrypt)
현재 EncryptUtil.encrypt/generateHashedValue는 HmacSHA256에 랜덤 salt를 키로 사용해 단회성 해시만 수행합니다. 이 방식은 반복(iterations)이나 워크 팩터(work factor)가 없어 비밀번호 기반 KDF의 역할을 하지 못하며, 무차별 대입 공격에 취약합니다. 또한 반환 문자열에 salt를 포함하지 않아 호출자가 별도 저장·관리해야 하므로, salt 누락 시 인증 로직 불일치 위험이 큽니다.

대체 제안

-package inha.gdgoc.global.util;
-...
-public class EncryptUtil {
-    public static String encrypt(String oldPassword, byte[] salt) throws NoSuchAlgorithmException, InvalidKeyException {
-        return generateHashedValue(oldPassword, salt);
-    }
-
-    public static byte[] generateSalt() {
-        SecureRandom random = new SecureRandom();
-        byte[] salt = new byte[16];
-        random.nextBytes(salt);
-        return salt;
-    }
-
-    public static String generateHashedValue(String oldPassword, byte[] salt)
-            throws NoSuchAlgorithmException, InvalidKeyException {
-        Mac mac = Mac.getInstance("HmacSHA256");
-        SecretKeySpec secretKeySpec = new SecretKeySpec(salt, "HmacSHA256");
-        mac.init(secretKeySpec);
-
-        byte[] hashedBytes = mac.doFinal(oldPassword.getBytes());
-        return Base64.getEncoder().encodeToString(hashedBytes);
-    }
-}
+package inha.gdgoc.global.util;
+
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+public class EncryptUtil {
+    private static final PasswordEncoder BCRYPT = new BCryptPasswordEncoder();
+
+    /** 비밀번호 해시 생성 — salt와 work factor 포함 */
+    public static String hashPassword(String rawPassword) {
+        return BCRYPT.encode(rawPassword);
+    }
+
+    /** 비밀번호 검증 */
+    public static boolean matches(String rawPassword, String hashed) {
+        return BCRYPT.matches(rawPassword, hashed);
+    }
+}

수정이 필요한 호출부

  • src/main/java/inha/gdgoc/domain/user/service/UserService.java
    generateSalt() + encrypt(raw, salt)hashPassword(raw)
  • src/main/java/inha/gdgoc/domain/user/entity/User.java
    updatePassword 메서드 내 EncryptUtil.encrypt(password, this.salt)EncryptUtil.hashPassword(password)
    • 기존 salt 필드는 제거하거나 용도 재검토
  • src/main/java/inha/gdgoc/domain/auth/service/AuthService.java
    • 로그인 검증 로직에서 encrypt(raw, salt).equals(storedHash)EncryptUtil.matches(raw, storedHash)

마이그레이션 가이드(해시 저장/검증 패턴)와 회귀 테스트 템플릿이 필요하시면 언제든 요청해 주세요.

src/main/resources/application-local.yml (1)

51-55: 로깅 카테고리 오타: org.hibername.type → org.hibernate.type

오타로 인해 type 바인딩 로깅 레벨이 적용되지 않습니다.

적용 diff:

-    org.hibername.type: trace
+    org.hibernate.type: trace
src/main/java/inha/gdgoc/global/util/CookieUtil.java (2)

11-17: 쿠키 보안 속성(HttpOnly, Secure, SameSite) 기본 미설정

인증/세션/토큰 쿠키라면 최소한 HttpOnly와 Secure 설정이 필요합니다. SameSite도 정책적으로 정해야 CSRF/크로스사이트 이슈를 줄일 수 있습니다.

아래처럼 기본 보안 속성을 부여하세요(Lax는 일반 웹 앱의 기본값으로 무난).

     public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) {
-        Cookie cookie = new Cookie(name, value);
-        cookie.setPath("/");
-        cookie.setMaxAge(maxAge);
-
-        response.addCookie(cookie);
+        Cookie cookie = new Cookie(name, value);
+        cookie.setPath("/");
+        cookie.setMaxAge(maxAge);
+        cookie.setHttpOnly(true);
+        cookie.setSecure(true); // HTTPS 전제
+        response.addCookie(cookie);
+        // SameSite는 표준 API가 없어 헤더로 추가하거나 ResponseCookie 사용을 고려
+        // response.addHeader("Set-Cookie", String.format("%s=%s; Max-Age=%d; Path=/; HttpOnly; Secure; SameSite=Lax", name, value, maxAge));
     }

대안(가독성/제어 우수): org.springframework.http.ResponseCookie를 사용하면 SameSite를 명시적으로 설정할 수 있습니다. 필요 시 제안 드리겠습니다.


36-47: 필수 조치: CookieUtil.serializedeserialize 메서드 삭제 및 대체 구현 적용

현재 코드베이스 전체에서 해당 유틸리티 메서드는 오직 CookieUtil.java 내부에서만 선언되어 있을 뿐, 외부에서 호출되지 않으므로 삭제해도 기능 상 영향이 없습니다. 보안 취약점을 제거하기 위해 아래와 같이 조치해 주세요.

  • 대상 파일: src/main/java/inha/gdgoc/global/util/CookieUtil.java
  • 삭제할 메서드:
    • public static String serialize(Object obj) { … } (36–39행)
    • public static <T> T deserialize(Cookie cookie, Class<T> cls) { … } (41–45행)
  • 대체 방안(예시): JSON 직렬화 + HMAC 서명 또는 JWT 활용
    --- src/main/java/inha/gdgoc/global/util/CookieUtil.java
    +// 삭제된 serialize/deserialize 메서드 자리
    
    +// JSON + HMAC 서명 방식 예시
    +public static String encodeSignedJson(Object obj, String secret) throws JsonProcessingException {
    +    String json = new ObjectMapper().writeValueAsString(obj);
    +    String payload = Base64.getUrlEncoder().withoutPadding()
    +        .encodeToString(json.getBytes(StandardCharsets.UTF_8));
    +    String sig = hmacSha256(payload, secret);
    +    return payload + "." + sig;
    +}
    +
    +public static <T> T decodeSignedJson(String token, Class<T> cls, String secret)
    +        throws IOException {
    +    String[] parts = token.split("\\.");
    +    if (parts.length != 2) throw new IllegalArgumentException("Invalid token format");
    +    if (!hmacSha256(parts[0], secret).equals(parts[1])) {
    +        throw new SecurityException("Signature mismatch");
    +    }
    +    String json = new String(Base64.getUrlDecoder()
    +        .decode(parts[0]), StandardCharsets.UTF_8);
    +    return new ObjectMapper().readValue(json, cls);
    +}

위와 같이 네이티브 Java 직렬화를 제거하고, 안전하게 서명된 JSON 혹은 JWT 기반 저장 방식을 적용해 주세요.

src/main/java/inha/gdgoc/domain/auth/service/RefreshTokenService.java (5)

32-41: 민감정보(Refresh Token) 로그 유출 위험 — 토큰 전문 출력 제거 필수.

토큰 원문을 로그로 남기면 심각한 보안 이슈가 됩니다. 재사용/생성 여부만 로그로 남기고 토큰 값은 제거해 주세요.

-            if (refreshToken.getExpiryDate().isAfter(LocalDateTime.now())) {
-                log.info("유효한 Refresh Token이 존재합니다. 재사용합니다: {}", refreshToken.getToken());
-                return refreshToken.getToken();
-            }
+            if (refreshToken.getExpiryDate().isAfter(LocalDateTime.now())) {
+                log.info("유효한 Refresh Token이 존재하여 재사용합니다.");
+                return refreshToken.getToken();
+            }

43-51: 토큰 생성 로그에서도 원문 출력 제거 필요.

-        String newToken = tokenProvider.generateRefreshToken(user, duration, loginType);
-        log.info("새로운 Refresh Token 생성됨: {}", newToken);
+        String newToken = tokenProvider.generateRefreshToken(user, duration, loginType);
+        log.info("새로운 Refresh Token을 생성했습니다.");

53-61: 서비스 진입 로그에서 리프레시 토큰 원문 출력 제거.

-        log.info("리프레시 토큰 서비스 호출됨. 토큰: {}", refreshToken);
+        log.info("리프레시 토큰 서비스 호출됨.");

81-85: 불일치 시 DB 저장 토큰 원문 로그 제거.

-        if (!storedToken.getToken().equals(refreshToken)) {
-            log.info("DB에 저장된 토큰: {}", storedToken.getToken());
-            throw new RuntimeException("리프레시 토큰이 일치하지 않습니다.");
-        }
+        if (!storedToken.getToken().equals(refreshToken)) {
+            throw new RuntimeException("리프레시 토큰이 일치하지 않습니다.");
+        }

116-145: 민감한 토큰 로그 제거 및 만료 시간 UTC 처리

코드 전반에서 민감한 토큰 값이 로그에 노출되고 있어 보안 취약점으로 이어질 수 있습니다. 또한 만료 시간 계산 시 서버 로컬 타임존을 사용하고 있어, 서버 타임존 변경 시 예상치 못한 오류가 발생할 수 있습니다. 아래 사항을 반드시 수정해주세요.

• 수정 대상 위치

  • RefreshTokenService.getOrCreateRefreshToken():
    • 기존 토큰 재사용 로그 (refreshToken.getToken())
    • 신규 토큰 생성 로그 (newToken)
  • RefreshTokenService.refreshAccessToken():
    • 서비스 호출 로그 (log.info(... refreshToken))
  • RefreshTokenService.saveRefreshToken():
    • “Before update”/“After update” 로그 (tokenEntity.getToken())
    • 신규 엔티티 생성 로그 (tokenEntity.getToken())
    • 만료 시간 생성부 (LocalDateTime.now().plus(expiredAt))
  • AuthService:
    • Response Cookie 로그 (refreshCookie.toString())

• 제안하는 코드 변경 예시

--- a/src/main/java/inha/gdgoc/domain/auth/service/RefreshTokenService.java
+++ b/src/main/java/inha/gdgoc/domain/auth/service/RefreshTokenService.java
@@ private void saveRefreshToken(String refreshToken, User user, Duration expiredAt) {
-    // 1. 만료 시간 로컬 시간으로 설정 (KST)
-    LocalDateTime expiryDate = LocalDateTime.now().plus(expiredAt);
+    // 1. 만료 시간 UTC 기준으로 설정
+    LocalDateTime expiryDate = LocalDateTime.now(ZoneOffset.UTC).plus(expiredAt);

@@ if (existingToken.isPresent()) {
-        log.info("Before update: {}", tokenEntity.getToken());
+        log.info("Refresh Token 갱신 시작");

         // 기존 엔티티 업데이트
         tokenEntity.update(refreshToken, expiryDate);

-        log.info("After update: {}", tokenEntity.getToken());
+        log.info("Refresh Token 갱신 완료");
         refreshTokenRepository.save(tokenEntity);
         return;
@@ // 3. 없으면 새로운 엔티티 생성
-    log.info("새로운 Refresh Token 생성: {}", tokenEntity.getToken());
+    log.info("새로운 Refresh Token 엔티티 생성");
--- a/src/main/java/inha/gdgoc/domain/auth/service/RefreshTokenService.java
+++ b/src/main/java/inha/gdgoc/domain/auth/service/RefreshTokenService.java
@@ public String getOrCreateRefreshToken(...)
-    if (refreshToken.getExpiryDate().isAfter(LocalDateTime.now())) {
-        log.info("유효한 Refresh Token이 존재합니다. 재사용합니다: {}", refreshToken.getToken());
+    if (refreshToken.getExpiryDate().isAfter(LocalDateTime.now(ZoneOffset.UTC))) {
+        log.info("유효한 Refresh Token이 존재합니다. 재사용합니다");
         return refreshToken.getToken();
     }
@@ public String refreshAccessToken(String refreshToken) {
-    log.info("리프레시 토큰 서비스 호출됨. 토큰: {}", refreshToken);
+    log.info("리프레시 토큰 서비스 호출됨");
--- a/src/main/java/inha/gdgoc/domain/auth/service/AuthService.java
+++ b/src/main/java/inha/gdgoc/domain/auth/service/AuthService.java
@@ public void someAuthMethod(...) {
-    log.info("Response Cookie에 저장된 Refresh Token: {}", refreshCookie.toString());
+    log.info("Response Cookie에 Refresh Token을 저장했습니다");

• 민감 로그 일괄 점검 스크립트

#!/bin/bash
# 로그에 민감 토큰/쿠키 노출 여부 점검
rg -nP --hidden "log\.(info|debug|warn|error).*\b(Token|getToken|toString|cookie)\b" -g '!**/build/**' -C2

위 변경을 통해

  1. 로그에 토큰 값이 남지 않아 유출 위험을 제거
  2. 만료 시간 계산에 UTC 기준을 적용해 타임존 이슈 방지

를 보장할 수 있습니다.

src/main/java/inha/gdgoc/domain/user/service/UserService.java (1)

65-71: 비밀번호 암호화 로직 전면 교체 필요

현재 HMAC-SHA256 단일 해시(EncryptUtil) 방식은 오프라인 크래킹에 취약하므로, Spring Security의 PasswordEncoder(예: BCryptPasswordEncoder)로 전환이 필수입니다. 프로젝트 내 이미 PasswordEncoder 빈이 등록되어 있으니 이를 주입해 사용하는 방향으로 수정하세요.

수정이 필요한 주요 지점:

  • UserService.saveUser (src/main/java/inha/gdgoc/domain/user/service/UserService.java:65–71)
    generateSalt()/encrypt() 제거 → passwordEncoder.encode() 사용
    toEntity() 시그니처에서 salt 파라미터 제거
  • User.updatePassword (src/main/java/inha/gdgoc/domain/user/entity/User.java:118–122)
    EncryptUtil.encrypt(...) 호출 제거 → passwordEncoder.encode() 적용
  • AuthService 로그인 검증 로직 (src/main/java/inha/gdgoc/domain/auth/service/AuthService.java:132–136)
    • 입력 비밀번호 해시 비교(EncryptUtil.encrypt) → passwordEncoder.matches() 사용
  • 프로젝트 전반의 EncryptUtil 의존 제거 및 관련 필드/테이블 칼럼(salt) 정리

예시 변경 (UserService):

-    public void saveUser(UserSignupRequest req) throws NoSuchAlgorithmException, InvalidKeyException {
-        byte[] salt = generateSalt();
-        String hashed = encrypt(req.getPassword(), salt);
-        User user = req.toEntity(hashed, salt);
-        userRepository.save(user);
-    }
+    private final PasswordEncoder passwordEncoder;
+
+    public void saveUser(UserSignupRequest req) {
+        String hashed = passwordEncoder.encode(req.getPassword());
+        User user = req.toEntity(hashed); // salt 파라미터 제거
+        userRepository.save(user);
+    }

Action Required: 위 3개 위치를 포함해 EncryptUtil 사용을 전부 찾아(rg -nP 'EncryptUtil|generateSalt\(|encrypt\(' -g '!**/build/**') PasswordEncoder 방식으로 일관되게 대체하고, 엔티티 필드 및 DTO, 리포지토리 마이그레이션을 수행해주세요. 데이터 마이그레이션(기존 사용자 비밀번호) 및 테스트 코드 수정도 잊지 마시기 바랍니다.

src/main/java/inha/gdgoc/domain/user/entity/User.java (2)

32-32: 중복 @builder: Lombok 충돌로 컴파일 실패 가능

클래스 레벨(@line 32)과 생성자(@line 87)에 동시에 @builder가 선언되어 있습니다. Lombok은 동일한 타입에 빌더 두 개를 기본 이름(builder)로 생성하려 해 충돌합니다. 하나만 유지하세요.

다음과 같이 클래스 레벨의 @builder를 제거하는 것을 권장합니다(생성자 빌더만 유지).

-@Builder
+// @Builder (class-level) 제거: 생성자 빌더만 유지

대안: 생성자 @builder에 builderMethodName, builderClassName을 지정해 공존시키는 방법도 있으나, 불필요한 복잡도를 야기할 수 있습니다.

Also applies to: 87-87


119-121: 비밀번호 해싱 방식 취약(HMAC-SHA256+salt는 비밀번호 해싱 아님)

현재 EncryptUtil은 HMAC-SHA256을 salt로 키잉하여 Base64로 저장합니다. 이는 비밀번호 저장에 적합한 KDF(BCrypt/Argon2/PBKDF2)가 아니며, GPU 브루트포싱에 취약합니다. SecurityConfig에 BCryptPasswordEncoder 빈이 이미 있으므로 이를 사용하세요. 또한 엔티티에서 해싱을 수행하면 DI가 어려워 테스트/유지보수성이 떨어집니다. 해싱은 서비스 계층으로 이동하는 것이 바람직합니다.

권장 변경(엔티티는 “해시된 값”만 세팅):

- public void updatePassword(String password) throws NoSuchAlgorithmException, InvalidKeyException {
-     this.password = EncryptUtil.encrypt(password, this.salt);
- }
+ public void updatePassword(String hashedPassword) {
+     this.password = hashedPassword;
+ }

서비스 계층 예시(엔티티 외부):

// 예: UserService 혹은 AuthService 내
@RequiredArgsConstructor
public class UserService {
  private final PasswordEncoder passwordEncoder;

  public void changePassword(User user, String rawPassword) {
    String hashed = passwordEncoder.encode(rawPassword);
    user.updatePassword(hashed);
  }
}

추가로, salt 필드(byte[])는 BCrypt 사용 시 불필요합니다(BCrypt가 내부적으로 salt 포함). 유지하려면 PBKDF2/Argon2로 전환 고려.

src/main/java/inha/gdgoc/global/config/jwt/TokenProvider.java (1)

68-73: JWT 'id' 클레임 파싱 시 Integer 강제 캐스팅: ClassCastException 위험

Long으로 넣고(Integer로) 꺼내고 있습니다. JJWT는 숫자 클레임을 Integer/Long/Double 등으로 역직렬화할 수 있어, 안전하게 Number로 받아야 합니다.

- UserRole userRole = UserRole.valueOf(claims.get("role", String.class));
- Long userId = claims.get("id", Integer.class).longValue();
- String username = claims.getSubject();
+ UserRole userRole = UserRole.valueOf(claims.get("role", String.class));
+ Number idNumber = claims.get("id", Number.class);
+ if (idNumber == null) {
+     throw new IllegalArgumentException("JWT에 사용자 id(claim 'id')가 없습니다.");
+ }
+ Long userId = idNumber.longValue();
+ String username = claims.getSubject();
src/main/java/inha/gdgoc/domain/auth/service/AuthService.java (1)

125-137: 패스워드 검증 로직 교체 필요(HMAC → BCrypt) 및 체크 예외 전파 제거

현재 HMAC 기반 비교는 취약합니다. BCryptPasswordEncoder.matches 사용으로 교체하고, 메서드 시그니처의 체크 예외(throws) 전파를 제거하세요.

- public LoginResponse loginWithPassword(UserLoginRequest userLoginRequest,
-     HttpServletResponse response)
-     throws NoSuchAlgorithmException, InvalidKeyException {
+ public LoginResponse loginWithPassword(UserLoginRequest userLoginRequest,
+     HttpServletResponse response) {
@@
- String hashedInputPassword = encrypt(userLoginRequest.password(), foundUser.getSalt());
- if (!foundUser.getPassword().equals(hashedInputPassword)) {
+ if (!passwordEncoder.matches(userLoginRequest.password(), foundUser.getPassword())) {
     return new LoginResponse(false, null);
 }

추가(클래스 필드/주입; 파일 상단 import 및 생성자 주입 필요):

import org.springframework.security.crypto.password.PasswordEncoder;

@RequiredArgsConstructor
public class AuthService {
  private final PasswordEncoder passwordEncoder;
  ...
}
src/main/java/inha/gdgoc/global/security/TokenAuthenticationFilter.java (1)

54-61: 액세스 토큰 전체 로그 노출: 보안 위험

토큰 전체를 로그에 남기면 탈취 위험이 큽니다. 마스킹하세요.

- log.info("요청 URI: {}, 추출된 access token: {}", request.getRequestURI(), token);
+ String masked = (token != null && token.length() > 12)
+     ? token.substring(0, 6) + "..." + token.substring(token.length() - 4)
+     : "***";
+ log.info("요청 URI: {}, access token: {}", request.getRequestURI(), masked);
🧹 Nitpick comments (26)
src/main/resources/application-prod.yml (3)

5-6: 프로덕션에서 .env 파일 import는 신중히—운영 환경 우발 로드/우선순위 리스크

spring.config.import로 file:.env[.properties]를 프로덕션 프로파일에서도 로드하면, 배포 환경의 워킹 디렉터리에 남아 있던 .env가 우발적으로 적용될 수 있고(컨테이너/서버 재사용 시), 설정 우선순위가 application-prod.yml과 섞이며 예기치 못한 오버라이드가 발생할 수 있습니다. 운영 환경에서는 가능하면 시스템 환경변수/비밀 관리(Param Store/Secrets Manager/KMS)만 사용하고 .env import는 제거하는 것을 권장합니다.

다음과 같이 prod에서만 import를 제거하는 패치를 제안합니다:

 spring:
-  config:
-    import: optional:file:.env[.properties]
+  config: {}

46-49: 운영에서 Hibernate SQL 로그 debug는 과도—성능/민감정보 노출 우려

org.hibernate.SQL을 debug로 두면 쿼리 로그가 대량으로 쌓이고, 간접적으로 PII 유출 리스크가 있습니다(바인딩 파라미터는 off이나 SQL 자체가 메타데이터를 드러낼 수 있음). 운영에서는 info 이상을 권장합니다.

 logging:
   level:
-    org.hibernate.SQL: debug
+    org.hibernate.SQL: info
     org.hibernate.type: off

62-71: cloud.aws 프로퍼티 참조 위치 확인 및 AWS SDK v2 대응 리팩토링 제안

application-prod.yml의 cloud.aws.* 프로퍼티는 아직 아래 위치에서 직접 참조되고 있어, 단순 제거 시 런타임 에러가 발생합니다. AWS SDK v2로 마이그레이션하며 Spring Cloud AWS 의존성을 제거할 계획이라면 다음 지점을 검토・수정하세요.

  • src/main/java/inha/gdgoc/global/config/s3/S3Config.java
    • 15행: @Value("${cloud.aws.region.static}")
  • src/main/java/inha/gdgoc/domain/resource/service/S3Service.java
    • 21행: @Value("${cloud.aws.s3.bucket}")

권장 리팩토링

  1. application-prod.yml에서 spring-cloud-aws 전용 프로퍼티를 제거하고 환경변수 직접 주입용 custom 프로퍼티로 대체
    src/main/resources/application-prod.yml
    cloud:
  • aws:
  •  credentials:
    
  •    access-key: ${AWS_ACCESS_KEY_ID}
    
  •    secret-key: ${AWS_SECRET_ACCESS_KEY}
    
  •  region:
    
  •    static: ${AWS_REGION}
    
  •  s3:
    
  •    bucket: ${AWS_RESOURCE_BUCKET}
    
  • awsRegion: ${AWS_REGION}
  • s3Bucket: ${AWS_RESOURCE_BUCKET}

2. S3Config.java에서 spring-cloud-aws 자격 증명・리전 바인딩 대신 기본 체인 사용  
```diff
 @Bean
-   public Region awsRegion(@Value("${cloud.aws.region.static}") String region) {
+   public Region awsRegion(@Value("${AWS_REGION}") String region) {
    return Region.of(region);
 }

 @Bean
-   public S3Client s3Client(AwsCredentialsProvider provider, Region region) {
-       return S3Client.builder()
-           .credentialsProvider(provider) // spring-cloud-aws 공급자
+   public S3Client s3Client(Region region) {
+       return S3Client.builder()
+           .credentialsProvider(DefaultCredentialsProvider.create())
         .region(region)
         .build();
 }
  1. S3Service.java에서 버킷명 주입 프로퍼티 이름 변경
  • @value("${cloud.aws.s3.bucket}")
  • @value("${AWS_RESOURCE_BUCKET}")
    private String bucketName;
    
    

이렇게 수정 후 기존 cloud.aws.* 설정을 제거하시면
• Spring Cloud AWS 의존성 제거 가능
• AWS SDK v2 기본 체인(DefaultCredentialsProvider, 시스템 환경변수 기반 리전) 활용
• 설정 충돌 및 혼동 최소화
됩니다.

src/main/java/inha/gdgoc/global/config/querydsl/QueryDslConfig.java (1)

9-17: 경미한 리팩터 제안: 생성자 주입 대신 메서드 파라미터 주입

현재는 @requiredargsconstructor로 EntityManager를 필드 주입 중입니다. 구성 빈에 한해 메서드 파라미터 주입이 더 간결하며 순환참조 리스크도 줄입니다(기능 동일).

-@Configuration
-@RequiredArgsConstructor
-public class QueryDslConfig {
-    private final EntityManager entityManager;
-
-    @Bean
-    public JPAQueryFactory jpaQueryFactory() {
-        return new JPAQueryFactory(entityManager);
-    }
-}
+@Configuration
+public class QueryDslConfig {
+    @Bean
+    public JPAQueryFactory jpaQueryFactory(EntityManager entityManager) {
+        return new JPAQueryFactory(entityManager);
+    }
+}
src/main/java/inha/gdgoc/global/exception/NotFoundException.java (1)

6-11: @ResponseStatus 전파 vs 전역 예외 처리 일관성

@ResponseStatus를 개별 예외에 부착하는 방식은 간단하지만, 글로벌 에러 포맷(예: RFC7807/ProblemDetail)과 혼용 시 응답 일관성이 깨질 수 있습니다. 이미 @ControllerAdvice 기반 전역 핸들러가 있다면 해당 핸들러로 통일하는 것을 고려해 주세요.

원하시면 전역 예외 응답 스펙(ProblemDetail)로 마이그레이션 초안 드리겠습니다.

src/main/resources/application-dev.yml (1)

62-71: 개발용 AWS 버킷/자격 증명 관리 점검

dev 프로파일에서 AWS_TEST_RESOURCE_BUCKET을 분리해 둔 점 좋습니다. 다만 cloud.aws.* 사용 여부는 prod와 동일 이슈입니다(SDK v2로 전환 시 불필요할 수 있음). 또한 로컬 개발 시에는 로컬 스택/로컬 미니오 같은 대안을 고려하면 비용과 안전성에 유리합니다.

원하시면 local 프로파일용 MinIO 설정 스니펫 제공해 드립니다.

src/main/resources/application-local.yml (1)

61-70: *cloud.aws.credentials.는 3.x에서 사용되지 않습니다 → 혼란 방지 위해 제거 또는 주석 권장

Aawspring-cloud 3.x + AWS SDK v2 조합에서는 cloud.aws.credentials.* 프로퍼티를 읽지 않습니다(현재 S3Config도 DefaultCredentialsProvider 사용). 남겨두면 “설정했는데 왜 안 되지?” 혼선을 줄 수 있습니다.

혼선을 방지하려면 아래처럼 credentials 블록을 제거(또는 주석)하세요. region, s3.bucket은 유지 가능합니다.

 cloud:
   aws:
-    credentials:
-      access-key: ${AWS_ACCESS_KEY_ID}
-      secret-key: ${AWS_SECRET_ACCESS_KEY}
     region:
       static: ${AWS_REGION}
     s3:
       bucket: ${AWS_TEST_RESOURCE_BUCKET}

또는 README에 “AWS 자격증명은 OS 환경변수/프로파일/Role을 사용” 명시도 좋습니다.

src/main/java/inha/gdgoc/global/util/CookieUtil.java (1)

19-34: 쿠키 삭제 시에도 속성 미러링 필요

생성 시 설정한 Path/Domain/SameSite/HttpOnly/Secure 속성과 동일하게 맞춰 만료시켜야 브라우저가 정확히 삭제합니다. 현재 Path만 "/"로 강제하고 있어 일부 브라우저/도메인 조합에서 잔존 가능성이 있습니다.

     public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String name) {
         Cookie[] cookies = request.getCookies();

         if (cookies == null) {
             return;
         }

         for (Cookie cookie : cookies) {
             if (name.equals(cookie.getName())) {
-                cookie.setValue("");
-                cookie.setPath("/");
-                cookie.setMaxAge(0);
-                response.addCookie(cookie);
+                cookie.setValue("");
+                cookie.setPath(cookie.getPath() == null ? "/" : cookie.getPath());
+                // cookie.setDomain(knownDomain); // 가능하면 도메인도 동일하게
+                cookie.setMaxAge(0);
+                cookie.setHttpOnly(true);
+                cookie.setSecure(true);
+                response.addCookie(cookie);
             }
         }
     }

SameSite를 사용 중이라면 동일 헤더로 만료 헤더도 추가하세요.

build.gradle (3)

73-75: Swagger UI 의존성 추가는 OK, 운영 노출 정책을 함께 정의하세요

운영/스테이징 노출이 불필요하다면 다음 중 하나를 적용해 주세요.

  • OpenApiConfig에 @Profile({"local","dev"}) 또는 @ConditionalOnClass(GroupedOpenApi.class) 추가
  • application-prod.yml에 springdoc.api-docs.enabled=false, springdoc.swagger-ui.enabled=false

예시(조건부 로딩):

@Configuration
@ConditionalOnClass(org.springdoc.core.models.GroupedOpenApi.class)
public class OpenApiConfig {
    // ...
}

60-60: java-dotenv는 현재 용도와 맞지 않습니다 → 제거 권장

이 PR에서 DotenvLoader를 삭제하고 Spring의 spring.config.import를 채택했으므로, 런타임 Dotenv 의존성은 잉여입니다. 유지하면 운영에서 .env 오사용을 유도할 수 있습니다.

적용 diff:

-    // --- 환경변수(.env) ---
-    implementation 'io.github.cdimascio:java-dotenv:5.2.2'

31-71: ConfigurationProperties 메타데이터 생성기 추가 제안

JwtProperties 등 @ConfigurationProperties를 사용하고 있으므로 IDE 보조와 오타 방지에 configuration-processor를 추가하는 것이 좋습니다.

적용 diff(의존성 블록 내):

     // --- Lombok ---
     compileOnly        'org.projectlombok:lombok'
     annotationProcessor 'org.projectlombok:lombok'

+    // --- Spring Configuration Processor (IDE 메타데이터) ---
+    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
src/main/java/inha/gdgoc/global/config/jwt/JwtProperties.java (1)

1-16: 패키지 리로케이션 OK, 프로퍼티 키와 일치합니다

application-*.yml의 jwt.selfIssuer/googleIssuer/secretKey와 필드가 일치하며, @component + @ConfigurationProperties("jwt") 조합도 동작에 문제 없습니다.

  • 불변성과 바인딩 안정성을 위해 record + 생성자 바인딩으로 전환을 고려해볼 수 있습니다(선택). 예시는 아래와 같습니다.
// 예시
@ConfigurationProperties(prefix = "jwt")
public record JwtProperties(String selfIssuer, String googleIssuer, String secretKey) {}

이 경우 @component 대신 @ConfigurationPropertiesScan 또는 @EnableConfigurationProperties(JwtProperties.class)가 필요합니다.

src/main/java/inha/gdgoc/global/common/ErrorResponse.java (1)

7-23: Lombok @Getter로 간소화 👍

불변 필드 + 명시 생성자 조합에 @Getter 적용 깔끔합니다. 직렬화/응답 용도에 충분합니다.

  • 필요 시 @JsonInclude(JsonInclude.Include.NON_NULL) 추가로 응답 깔끔화 가능.
  • 추후 표준화(타임스탬프, path 등)가 필요하면 RFC7807(Problem Details)로 확장도 고려해볼 만합니다.
src/main/java/inha/gdgoc/global/config/s3/S3Config.java (1)

24-30: S3Client 자원 해제(destroyMethod)와 타임아웃/재시도 설정 권장.

S3ClientSdkAutoCloseable을 구현합니다. 컨테이너 종료 시 안전하게 소켓을 닫도록 destroyMethod="close" 지정을 권장합니다. 또한 기본 타임아웃은 무제한에 가깝기 때문에 운영 환경에서는 합리적 타임아웃/재시도 정책을 설정하는 편이 안전합니다.

-@Bean
+@Bean(destroyMethod = "close")
 public S3Client s3Client(Region region, AwsCredentialsProvider provider) {
-    return S3Client.builder()
-        .region(region)
-        .credentialsProvider(provider)
-        .build();
+    return S3Client.builder()
+        .region(region)
+        .credentialsProvider(provider)
+        .overrideConfiguration(cfg -> cfg
+            .apiCallTimeout(java.time.Duration.ofSeconds(30))
+            .apiCallAttemptTimeout(java.time.Duration.ofSeconds(10)))
+        .build();
 }
src/main/java/inha/gdgoc/global/config/openapi/OpenApiConfig.java (1)

32-47: 경로 재작성 시 '/api/v1/' 같은 트레일링 슬래시 케이스 보완 제안.

현재는 p.equals(fullPrefix)만 루트 매핑으로 변환합니다. 실제 스프링 매핑에서 간혹 /api/v1/가 생성될 수 있어 해당 케이스까지 함께 처리하면 안전합니다. 또한 서버 정보 설정 시 기존 Servers를 완전히 덮어쓰므로, 공용 서버 설정이 있는 경우에는 병합이 필요할 수 있습니다.

-                if (p.equals(fullPrefix)) p = "/";
-                else if (p.startsWith(fullPrefix + "/")) p = p.substring(fullPrefix.length());
+                if (p.equals(fullPrefix) || p.equals(fullPrefix + "/")) {
+                    p = "/";
+                } else if (p.startsWith(fullPrefix + "/")) {
+                    p = p.substring(fullPrefix.length());
+                }

추가로, 제품명/버전/설명을 노출하려면 OpenAPI Info 설정을 별도 빈으로 구성하는 것도 고려해 주세요.

src/main/java/inha/gdgoc/domain/resource/service/S3Service.java (2)

24-24: 파라미터 네이밍 일관성(nit): s3key → s3Key 권장.

자바 관례상 카멜케이스(s3Key)가 가독성에 유리합니다. 추후 API/호출처 영향 범위를 고려하여 리팩터링 시 반영을 권장합니다.


38-41: 사설 버킷이라면 getUrl()은 접근 불가 — Presigned URL 필요 여부 확인.

S3Client.utilities().getUrl(...)은 서명되지 않은 단순 URL입니다. 버킷/오브젝트가 퍼블릭이 아니라면 접근이 불가능합니다. 다운로드/직접 접근이 필요하다면 S3Presigner를 사용해 서명 URL을 발급하는 방식을 권장합니다. 필요 시 S3Presigner 빈을 구성해 드리겠습니다.

버킷 정책이 퍼블릭인지(또는 CloudFront 등을 통해 공개하는지) 확인해 주세요. 프리사인드 URL이 필요하다면 알려 주세요. 해당 메서드 대체 코드를 제공하겠습니다.

src/main/java/inha/gdgoc/domain/user/entity/User.java (1)

105-117: null 삽입 가능성: 연관관계 편의 메서드 가드 필요

null 인자가 넘어오면 리스트에 null이 추가됩니다(이후 NPE 위험). addStudy, addStudyAttendee 모두 null 가드를 적용하세요.

- public void addStudy(Study study) {
-     this.studies.add(study);
-     if (study != null && study.getUser() != this) {
-         study.setUser(this);
-     }
- }
+ public void addStudy(Study study) {
+     if (study == null) return;
+     this.studies.add(study);
+     if (study.getUser() != this) {
+         study.setUser(this);
+     }
+ }
- public void addStudyAttendee(StudyAttendee studyAttendee) {
-     this.studyAttendees.add(studyAttendee);
-     if (studyAttendee != null && studyAttendee.getUser() != this) {
-         studyAttendee.setUser(this);
-     }
- }
+ public void addStudyAttendee(StudyAttendee studyAttendee) {
+     if (studyAttendee == null) return;
+     this.studyAttendees.add(studyAttendee);
+     if (studyAttendee.getUser() != this) {
+         studyAttendee.setUser(this);
+     }
+ }
src/main/java/inha/gdgoc/global/config/jwt/TokenProvider.java (1)

63-66: 불필요한 throws 선언

validToken은 런타임 예외를 던지는 JJWT 파서를 래핑합니다. 메서드 시그니처의 체크 예외 throws 표기는 불필요하며 호출부에 과도한 예외 선언을 강제합니다.

- public Claims validToken(String token) throws ExpiredJwtException, UnsupportedJwtException,
-         MalformedJwtException, SignatureException, IllegalArgumentException {
+ public Claims validToken(String token) {
src/main/java/inha/gdgoc/domain/auth/service/AuthService.java (2)

79-85: Google 사용자 정보 응답 NPE 방지 및 오류 처리

userInfoResponse.getBody()가 null이거나 "email" 키가 없으면 NPE가 발생합니다. 실패 응답(status 4xx/5xx) 처리와 null 가드를 추가하세요.

 ResponseEntity<Map> userInfoResponse = restTemplate.exchange(
     "https://www.googleapis.com/oauth2/v2/userinfo",
     HttpMethod.GET,
     userInfoRequest,
     Map.class
 );
 
- Map userInfo = userInfoResponse.getBody();
- String email = (String) userInfo.get("email");
- String name = (String) userInfo.get("name");
+ if (!userInfoResponse.getStatusCode().is2xxSuccessful() || userInfoResponse.getBody() == null) {
+     throw new IllegalStateException("구글 사용자 정보 요청 실패: " + userInfoResponse.getStatusCode());
+ }
+ Map userInfo = userInfoResponse.getBody();
+ String email = (String) userInfo.getOrDefault("email", null);
+ String name = (String) userInfo.getOrDefault("name", null);
+ if (email == null) {
+     throw new IllegalStateException("구글 사용자 정보에 email이 없습니다.");
+ }

Also applies to: 87-90


144-156: Refresh Token 쿠키에 .domain(...) 속성 일관적으로 적용 및 중복 제거 제안

현재 Google OAuth 로그인(라인 106–109)과 자체 회원가입 로그인(라인 144–147) 모두 ResponseCookie.from("refresh_token", …) 빌더에 .domain(...) 호출이 빠져 있습니다. 동일한 도메인 정책을 유지하려면 두 위치에 모두 도메인 설정을 추가하고, 중복 코드를 공용 메서드로 분리하는 것을 권장드립니다.

  • 변경 위치

    • src/main/java/inha/gdgoc/domain/auth/service/AuthService.java
      • Google 로그인 처리부 (약 106행)
      • 자체 로그인 처리부 (약 144행)
  • 제안하는 코드 예시

- ResponseCookie refreshCookie = ResponseCookie.from("refresh_token", refreshToken)
-     .httpOnly(true)
-     .secure(true)
-     .sameSite("None")
+ ResponseCookie refreshCookie = buildRefreshCookie(refreshToken);

...

+ // AuthService 클래스 내 공용 메서드
+ private ResponseCookie buildRefreshCookie(String token) {
+     String domain = isProdProfile() ? ".gdgocinha.com" : null;
+     return ResponseCookie.from("refresh_token", token)
+         .httpOnly(true)
+         .secure(true)
+         .sameSite("None")
+         .domain(domain)   // dev 환경에서는 null로 처리되어 header에 미포함
+         .path("/")
+         .maxAge(Duration.ofDays(1))
+         .build();
+ }
  • 로그 레벨 조정
    • 보안 민감 정보를 남기지 않도록 .toString() 직접 로깅은 지양하고, 디버그 전용 메시지로 변경 바랍니다:
      log.debug("Refresh Token 쿠키 설정 완료");

위 리팩토링을 통해

  1. 쿠키 설정 코드 중복 제거
  2. 프로파일별 도메인 분기 적용
  3. 민감 로그 노출 최소화
    를 동시에 달성할 수 있습니다.
src/main/java/inha/gdgoc/global/security/TokenAuthenticationFilter.java (2)

25-37: shouldNotFilter 범위 적절—SecurityConfig와 경로 목록 중복 관리 필요

Swagger, auth, 기타 공개 엔드포인트를 필터에서 배제하는 접근은 적절합니다. 다만 SecurityConfig의 permitAll 목록과 경로가 중복 관리되고 있어 추후 불일치 위험이 있습니다(아래 추가 코멘트 참조).


45-51: 중복 예외 경로 로직 제거 제안(shouldNotFilter로 대체 가능)

shouldNotFilter가 이미 /auth/** 등을 포괄합니다. doFilterInternal 내 skipPaths 검사는 중복이며 유지보수 비용만 증가합니다.

- List<String> skipPaths = List.of("/auth/refresh", "/auth/login", "/auth/oauth2/google/callback",
-         "/auth/signup", "/auth/findId", "/auth/password-reset/request", "/auth/password-reset/verify",
-         "/auth/password-reset/confirm");
- if (skipPaths.contains(uri)) {
-     filterChain.doFilter(request, response);
-     return;
- }
+ // shouldNotFilter에서 공통 관리
src/main/java/inha/gdgoc/global/security/SecurityConfig.java (3)

38-41: permitAll 경로와 TokenAuthenticationFilter.shouldNotFilter 경로 중복

동일 경로 목록을 두 곳에서 관리 중입니다. 상수/설정으로 단일화하여 드리프트를 방지하세요. 예: @ConfigurationProperties 혹은 public static final Set ALLOWLIST.


47-49: 인증 실패 상태코드: 403 → 401 권장

인증 부재/실패는 일반적으로 401 Unauthorized가 적합합니다. 403은 인증은 되었으나 권한이 부족한 경우에 사용합니다. 클라이언트 UX/리트라이 로직에도 영향이 있으므로 수정 권장.

- response.setStatus(HttpStatus.FORBIDDEN.value());
+ response.setStatus(HttpStatus.UNAUTHORIZED.value());

68-73: CORS 설정과 Secure 쿠키 정책 정합성 점검 요청

  • src/main/java/inha/gdgoc/global/security/SecurityConfig.java (67–74행)
    config.setAllowedOrigins에 "http://gdgocinha.com"이 포함되어 있으나,
    inha/gdgoc/domain/auth/service/AuthService.java에서 생성되는 refresh_token ResponseCookie는
    secure=true & sameSite("None") 으로 설정되어 있어 HTTPS 환경이 아니면 브라우저가 쿠키를 전송하지 않습니다.
    → 개발(dev) 환경에서는 HTTP로 동작할 수 있도록 secure 옵션을 프로파일별로 분기 처리하거나,
    운영(prod) 환경에서는 allowedOriginPatterns("https://*.gdgocinha.com")을 사용해 HTTPS 전용만 허용하도록 분리 검토 바랍니다.

  • 필요 시, 프론트엔드에서 읽어야 하는 커스텀 헤더가 있다면
    config.setExposedHeaders(List.of("헤더명1", "헤더명2")) 로 추가 설정하세요.
    (쿠키 자체는 JS에서 접근 불가하므로 일반적으로 불필요)

※ 위 지침은 75–77행에도 동일 적용됩니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 96820e3 and b5e1f41.

📒 Files selected for processing (22)
  • build.gradle (3 hunks)
  • src/main/java/inha/gdgoc/config/DotenvLoader.java (0 hunks)
  • src/main/java/inha/gdgoc/config/S3Config.java (0 hunks)
  • src/main/java/inha/gdgoc/domain/auth/service/AuthService.java (6 hunks)
  • src/main/java/inha/gdgoc/domain/auth/service/RefreshTokenService.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/resource/service/S3Service.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/user/entity/User.java (1 hunks)
  • src/main/java/inha/gdgoc/domain/user/service/UserService.java (2 hunks)
  • src/main/java/inha/gdgoc/global/common/ErrorResponse.java (1 hunks)
  • src/main/java/inha/gdgoc/global/config/jwt/JwtProperties.java (1 hunks)
  • src/main/java/inha/gdgoc/global/config/jwt/TokenProvider.java (1 hunks)
  • src/main/java/inha/gdgoc/global/config/openapi/OpenApiConfig.java (1 hunks)
  • src/main/java/inha/gdgoc/global/config/querydsl/QueryDslConfig.java (1 hunks)
  • src/main/java/inha/gdgoc/global/config/s3/S3Config.java (1 hunks)
  • src/main/java/inha/gdgoc/global/exception/NotFoundException.java (1 hunks)
  • src/main/java/inha/gdgoc/global/security/SecurityConfig.java (3 hunks)
  • src/main/java/inha/gdgoc/global/security/TokenAuthenticationFilter.java (2 hunks)
  • src/main/java/inha/gdgoc/global/util/CookieUtil.java (1 hunks)
  • src/main/java/inha/gdgoc/global/util/EncryptUtil.java (1 hunks)
  • src/main/resources/application-dev.yml (1 hunks)
  • src/main/resources/application-local.yml (1 hunks)
  • src/main/resources/application-prod.yml (1 hunks)
💤 Files with no reviewable changes (2)
  • src/main/java/inha/gdgoc/config/DotenvLoader.java
  • src/main/java/inha/gdgoc/config/S3Config.java
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: kaswhy
PR: GDGoCINHA/24-2_GDGoC_Server#180
File: gdgoc/build.gradle:52-54
Timestamp: 2025-08-23T03:40:02.707Z
Learning: In the gdgoc project using Spring Boot 3, the user kaswhy confirmed that spring-cloud-aws 2.2.6.RELEASE dependency is currently working for them despite known compatibility concerns with Jakarta EE migration.
📚 Learning: 2025-08-23T03:40:02.707Z
Learnt from: kaswhy
PR: GDGoCINHA/24-2_GDGoC_Server#180
File: gdgoc/build.gradle:52-54
Timestamp: 2025-08-23T03:40:02.707Z
Learning: In the gdgoc project using Spring Boot 3, the user kaswhy confirmed that spring-cloud-aws 2.2.6.RELEASE dependency is currently working for them despite known compatibility concerns with Jakarta EE migration.

Applied to files:

  • build.gradle
🧬 Code graph analysis (5)
src/main/java/inha/gdgoc/global/config/openapi/OpenApiConfig.java (1)
src/main/java/inha/gdgoc/global/security/SecurityConfig.java (1)
  • Configuration (23-88)
src/main/java/inha/gdgoc/domain/user/entity/User.java (1)
src/main/java/inha/gdgoc/global/util/EncryptUtil.java (1)
  • EncryptUtil (10-33)
src/main/java/inha/gdgoc/domain/user/service/UserService.java (1)
src/main/java/inha/gdgoc/global/util/EncryptUtil.java (1)
  • EncryptUtil (10-33)
src/main/java/inha/gdgoc/domain/auth/service/AuthService.java (3)
src/main/java/inha/gdgoc/global/util/EncryptUtil.java (1)
  • EncryptUtil (10-33)
src/main/java/inha/gdgoc/domain/auth/service/RefreshTokenService.java (1)
  • Slf4j (18-146)
src/main/java/inha/gdgoc/global/config/jwt/TokenProvider.java (1)
  • RequiredArgsConstructor (29-131)
src/main/java/inha/gdgoc/global/common/ErrorResponse.java (1)
src/main/java/inha/gdgoc/global/config/jwt/TokenProvider.java (1)
  • Getter (121-130)
🔇 Additional comments (16)
src/main/java/inha/gdgoc/global/config/querydsl/QueryDslConfig.java (1)

1-1: 확인 완료 — 컴포넌트 스캔에 문제가 없습니다

GdgocApplicationinha.gdgoc 패키지에 위치하며 @SpringBootApplication에 별도의 scanBasePackages 지정이 없어, 기본적으로 inha.gdgoc 하위 패키지 전체를 스캔합니다.
따라서 inha.gdgoc.global.config.querydsl.QueryDslConfig도 자동으로 빈으로 등록되어 정상 동작합니다.

src/main/java/inha/gdgoc/global/exception/NotFoundException.java (1)

1-1: 기존 import 경로 참조 없음 확인
rg 명령(rg -n -P '\binha\.gdgoc\.exception\.NotFoundException\b' --type=java) 결과, inha.gdgoc.exception.NotFoundException 잔여 참조가 없습니다.

src/main/resources/application-dev.yml (1)

46-49: 개발에서 SQL 로그 debug 유지 OK

개발/디버깅에 유용하며, org.hibernate.type을 off로 둬 파라미터 노출을 막은 점도 적절합니다.

src/main/java/inha/gdgoc/global/util/EncryptUtil.java (1)

1-1: 패키지 이동 OK—그러나 내부 구현은 보안상 재검토 필요

패키지 정리는 좋습니다. 다만 아래 메서드 구현이 비밀번호 해시 용도로는 안전하지 않습니다(자세한 코멘트는 하단 참조).

build.gradle (3)

77-81: AWS BOM 도입 적절

3.1.1 BOM으로 awspring-cloud 계열의 버전 정합성 확보가 됩니다. 이후 추가 모듈(예: spring-cloud-aws-starter-sqs 등)도 버전 지정 없이 사용 가능해집니다.


53-53: AmazonS3 참조 제거 검증 완료

rg를 통해 AmazonS3com.amazonaws.services.s3 패턴 검색 시 코드베이스에 일치 항목이 없음을 확인했습니다.


3-3: File: build.gradle
Lines: 3-3

Snippet showing the final state of code at these lines

    id 'org.springframework.boot' version '3.3.6'

Comment

Spring Boot 3.3.6 고정: 로컬 호환성 검증 요청

현 조합(boot 3.3.6 + springdoc 2.6.0 + flyway 10.21.0 + awspring-cloud 3.1.1)은 일반적으로 호환됩니다. 다만, JAVA_HOME 설정 오류로 인해 의존성 트리 확인이 실패했습니다. 아래 환경 설정을 적용한 뒤 로컬 빌드 및 의존성 충돌 검증을 부탁드립니다:

#!/bin/bash
# 올바른 JDK 경로로 JAVA_HOME 재설정
export JAVA_HOME=/path/to/valid/jdk
# 의존성 트리 확인
./gradlew -q dependencies --configuration runtimeClasspath | sed -n '1,200p'
src/main/java/inha/gdgoc/global/config/s3/S3Config.java (1)

11-12: AWS SDK v2 기반 S3 클라이언트 구성 도입, 방향성 좋습니다.

v1 → v2 마이그레이션에 맞춰 구성 클래스를 분리한 점과 기본 자격 증명 공급자 체인을 사용하는 선택이 합리적입니다.

src/main/java/inha/gdgoc/global/config/openapi/OpenApiConfig.java (2)

14-22: 버전별 GroupedOpenApi 분리 구성 깔끔합니다.

/api/v1/**, /api/v2/**로 그룹을 나눠 운영/문서화 모두에 유용합니다. 보안 설정에 /v3/api-docs/**, /swagger-ui/**가 이미 허용되어 있어 접근성도 확보되어 있습니다.


24-30: 커스터마이저 분리 구조 적절합니다.

그룹 공통 로직을 헬퍼(groupedApi)로 모아 중복을 줄인 점 좋습니다.

src/main/java/inha/gdgoc/domain/auth/service/RefreshTokenService.java (1)

3-3: 패키지 경로 변경 일치 — import 정리 OK.

TokenProvider의 글로벌 패키지 이동에 맞춘 수정으로 보이며, 기능 영향은 없습니다.

src/main/java/inha/gdgoc/domain/user/service/UserService.java (1)

3-5: 글로벌 유틸 import 경로 업데이트 적절합니다.

src/main/java/inha/gdgoc/domain/user/entity/User.java (2)

7-7: EncryptUtil 패키지 이동 반영 OK

import 경로 변경만으로 런타임 동작에 영향 없음. 리팩토링 방향(utilities → global.util)과도 일치합니다.


67-68: salt 필드 초기화 경로 확인 완료: 문제 없음
UserService.saveUser(…)에서 generateSalt()로 생성한 값을 userSignupRequest.toEntity(hashedPassword, salt)로 넘겨 .salt에 설정하고 있음을 확인했습니다. 따라서 @Column(nullable = false) 제약 조건을 만족하며, 현 구현에서 saltnull이 되어 updatePassword 시 NPE가 발생할 가능성은 없습니다.

src/main/java/inha/gdgoc/global/config/jwt/TokenProvider.java (1)

102-107: 서명 키 처리 개선 및 JJWT 버전 확인 필요

build.gradle에서 현재 io.jsonwebtoken:jjwt:0.9.1를 사용 중이므로, 0.11+에서 제공하는 parserBuilderKeys.hmacShaKeyFor API는 아직 적용할 수 없습니다. 다음 두 가지 방안 중 하나를 선택해 적용해주세요:

• 현 버전(0.9.1) 유지

  • Base64 재인코딩 제거
  • 시크릿이 Base64 문자열인 경우:
    byte[] keyBytes = Base64.getDecoder().decode(jwtProperties.getSecretKey());
  • 일반 시크릿 문자열인 경우:
    byte[] keyBytes = jwtProperties.getSecretKey().getBytes(StandardCharsets.UTF_8);
  • SecretKeySpec으로 Key 객체 생성 후 사용
    - .signWith(SignatureAlgorithm.HS256,
    -         Base64.getEncoder().encodeToString(jwtProperties.getSecretKey().getBytes())
    - )
    + byte[] keyBytes = /* Base64 디코드 or UTF-8 바이트 */;
    + Key key = new SecretKeySpec(keyBytes, SignatureAlgorithm.HS256.getJcaName());
    + .signWith(SignatureAlgorithm.HS256, key)
    - return Jwts.parser()
    -         .setSigningKey(
    -                 Base64.getEncoder().encodeToString(jwtProperties.getSecretKey().getBytes())
    -         )
    -         .parseClaimsJws(token)
    -         .getBody();
    + return Jwts.parser()
    +         .setSigningKey(key)
    +         .parseClaimsJws(token)
    +         .getBody();
  • 추가 import:
    import javax.crypto.spec.SecretKeySpec;
    import java.nio.charset.StandardCharsets;

• JJWT 0.11.x 이상으로 업그레이드

  • build.gradle 의존성 수정:
    - implementation 'io.jsonwebtoken:jjwt:0.9.1'
    + implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
    + implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
    + implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
  • 다음과 같이 변경:
    - .signWith(SignatureAlgorithm.HS256, /* byte[] or Key */)
    + .signWith(Keys.hmacShaKeyFor(jwtProperties.getSecretKey().getBytes(StandardCharsets.UTF_8)), SignatureAlgorithm.HS256)
    - Jwts.parser()…
    + Jwts.parserBuilder()
    +     .setSigningKey(Keys.hmacShaKeyFor(jwtProperties.getSecretKey().getBytes(StandardCharsets.UTF_8)))
    +     .build()
    +     .parseClaimsJws(token)
    +     .getBody();
  • 추가 import:
    import io.jsonwebtoken.security.Keys;
    import java.nio.charset.StandardCharsets;

위 방안 중 팀의 정책(의존성 업그레이드 가능 여부 등)에 맞춰 선택・적용해 주세요.

src/main/java/inha/gdgoc/global/security/SecurityConfig.java (1)

33-45: 전반적 보안 체인 구성은 적절

  • CSRF 비활성화 + 세션 무상태 + 사용자 정의 JWT 필터 삽입 흐름 적절합니다.
  • Swagger 경로 공개 설정도 기대 동작과 부합합니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/main/java/inha/gdgoc/global/config/openapi/OpenApiConfig.java (2)

37-53: 프리픽스 제거 커스터마이저에 가드 추가(와일드카드·빈 프리픽스 시 조기 반환 + 서버 설정 안전장치)

현재 구현은 어떤 fullPrefix든 서버를 강제로 덮어씁니다. 실수로 와일드카드나 빈 문자열을 넘기면 잘못된 서버 URL이 반영됩니다. 안전장치를 두면 회귀에 강해집니다.

 private OpenApiCustomizer stripPrefixAndSetServer(String fullPrefix) {
     return openApi -> {
+        // 방어 로직: 와일드카드 또는 비어있는 프리픽스는 리라이트/서버 설정을 건너뜀
+        if (fullPrefix == null || fullPrefix.isBlank() || fullPrefix.contains("*")) {
+            return;
+        }
         Paths src = openApi.getPaths();
         if (src == null || src.isEmpty()) return;

         Paths dst = new Paths();
         src.forEach((path, item) -> {
             String p = path;
             if (p.equals(fullPrefix)) p = "/";
             else if (p.startsWith(fullPrefix + "/")) p = p.substring(fullPrefix.length());
             dst.addPathItem(p, item);
         });

         openApi.setPaths(dst);
         openApi.setServers(List.of(new Server().url(fullPrefix)));
     };
 }

추가로, 프리픽스 제거 결과가 충돌(동일 키)하는 경우 마지막 항목으로 덮어쓰게 됩니다. 현재 요구사항에서는 가능성이 낮지만, 충돌 로깅이 필요하면 put 전 존재 여부 체크 후 로그 남기는 것을 고려하세요.


11-12: OpenAPI 메타 정보(Info) 추가 고려

UI 식별성을 위해 제목/설명/버전 정보를 설정하면 좋습니다. 예시:

// 추가 제안: 별도 Bean
@Bean
public io.swagger.v3.oas.models.OpenAPI baseOpenAPI() {
    return new io.swagger.v3.oas.models.OpenAPI()
        .info(new io.swagger.v3.oas.models.info.Info()
            .title("GDGoC Server API")
            .description("GDGoC 서비스의 공개 REST API 명세")
            .version("v1"));
}

원하시면 프로젝트 메타데이터(패키지명, build.gradle 버전 등)를 반영해 자동화되는 템플릿으로 드리겠습니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b5e1f41 and e29bfa0.

📒 Files selected for processing (1)
  • src/main/java/inha/gdgoc/global/config/openapi/OpenApiConfig.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/inha/gdgoc/global/config/openapi/OpenApiConfig.java (1)
src/main/java/inha/gdgoc/global/security/SecurityConfig.java (1)
  • Configuration (23-88)
🔇 Additional comments (2)
src/main/java/inha/gdgoc/global/config/openapi/OpenApiConfig.java (2)

19-23: 버전 그룹 구성은 방향성 좋습니다

v1, v2 그룹을 별도로 나누고, 프리픽스 제거 + 서버 설정으로 UI에서 버전별로 베이스 경로를 명확히 보여주는 접근은 합리적입니다. 위의 가드/매칭 보완만 반영하면 충분히 안정적으로 동작할 것 같습니다.

Also applies to: 24-27


29-35: 경로 매핑 누락 없음 확인

/api/v1 또는 /api/v2를 루트에 직접 매핑한 컨트롤러가 존재하지 않음을 확인했습니다.
따라서 pathsToMatch(fullPrefix + "/**")만으로도 모든 엔드포인트가 포함되며, 추가 수정은 불필요합니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/main/java/inha/gdgoc/global/config/openapi/OpenApiConfig.java (3)

32-38: groupedApi 구성 깔끔합니다

pathsToMatch(fullPrefix + "/**")와 전용 커스터마이저 결합이 의도에 부합합니다. 현재 구조 기준으로 불필요한 스캔을 크게 줄일 필요까지는 없어 보이나, 추후 API 스펙이 커질 경우 packagesToScan("inha.gdgoc") 추가를 고려하면 초기화 시간이 더 짧아질 수 있습니다.


40-52: 경로 리라이트 엣지 케이스 보완 제안(트레일링 슬래시·중복 키 방지)

입력 프리픽스가 /api/v1/처럼 슬래시로 끝나거나, 소스에 /api/v1/api/v1/가 동시에 존재할 때 현재 로직은 "/"로 중복 맵핑되어 마지막 항목으로 덮어쓰기 될 수 있습니다. 프리픽스를 정규화하고 "/" 케이스를 명시적으로 처리하면 안전합니다.

아래 패치 제안:

-    private OpenApiCustomizer stripPrefixAndSetServer(String fullPrefix) {
-        return openApi -> {
+    private OpenApiCustomizer stripPrefixAndSetServer(String fullPrefix) {
+        return openApi -> {
             Paths src = openApi.getPaths();
             if (src == null || src.isEmpty()) return;
 
             Paths dst = new Paths();
             src.forEach((path, item) -> {
-                String p = path;
-                if (p.equals(fullPrefix)) p = "/";
-                else if (p.startsWith(fullPrefix + "/")) p = p.substring(fullPrefix.length());
+                String p = path;
+                // 프리픽스 정규화: 끝 슬래시 제거
+                final String prefix = fullPrefix.endsWith("/")
+                    ? fullPrefix.substring(0, fullPrefix.length() - 1)
+                    : fullPrefix;
+                if (p.equals(prefix) || p.equals(prefix + "/")) {
+                    p = "/";
+                } else if (p.startsWith(prefix + "/")) {
+                    p = p.substring(prefix.length());
+                }
                 dst.addPathItem(p, item);
             });

53-55: servers 강제 덮어쓰기는 환경에 따라 오작동 가능 — 비어있을 때만 설정하거나 컨텍스트패스를 포함하세요

현재는 servers를 항상 [ { url: fullPrefix } ]로 교체합니다. 컨텍스트패스(/app) 또는 프록시 프리픽스가 있을 경우 실제 호출 경로가 어긋날 수 있습니다. 최소한 servers가 비어있을 때만 설정하도록 완화하면 기존 상위 설정을 존중할 수 있습니다.

아래와 같이 조건부 설정으로 변경 제안:

-            openApi.setServers(List.of(new Server().url(fullPrefix)));
+            if (openApi.getServers() == null || openApi.getServers().isEmpty()) {
+                openApi.setServers(List.of(new Server().url(fullPrefix)));
+            }

또는 컨텍스트패스를 포함한 값으로 설정하려면(선택):

// 클래스 필드 추가
// @Value("${server.servlet.context-path:}")
// private String contextPath;

// setServers 시
openApi.setServers(List.of(new Server().url((contextPath == null ? "" : contextPath) + fullPrefix)));

프록시 환경을 적극 지원하려면 ForwardedHeaderFilter 적용 및 관련 설정(server.forward-headers-strategy=framework)도 검토해 주세요.

원하시면 환경별(로컬/스테이징/운영) 동작을 표로 정리해 드리고, 가장 안전한 설정 조합을 PR에 반영하는 패치를 같이 올리겠습니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1fd75c2 and 2815d7a.

📒 Files selected for processing (1)
  • src/main/java/inha/gdgoc/global/config/openapi/OpenApiConfig.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/inha/gdgoc/global/config/openapi/OpenApiConfig.java (1)
src/main/java/inha/gdgoc/global/security/SecurityConfig.java (1)
  • Configuration (23-88)
🔇 Additional comments (2)
src/main/java/inha/gdgoc/global/config/openapi/OpenApiConfig.java (2)

22-30: v1/v2 그룹 구성 방향 OK — 프록시/컨텍스트패스 환경에서의 동작 확인 필요

스크립트 실행 결과 JSON 파싱 오류(parse error: Invalid numeric literal)가 발생해 .servers[0].url.paths 정보가 정상 출력되지 않았습니다. 애플리케이션이 실제 배포된 컨텍스트패스(예: /app)나 리버스 프록시(X-Forwarded-Prefix) 환경에서도 Swagger UI의 “Try it out” 요청 경로가 올바르게 매핑되는지, 다음 사항을 직접 확인해 주세요.

• 확인 대상

  • http://{HOST}:{PORT}/v3/api-docs/v1/v2 엔드포인트 응답이 유효한 JSON인지
  • 응답 내 .servers[0].url 값이 실제 프록시/컨텍스트패스 적용 후의 절대 URL을 반영하는지
  • .paths 하위 엔드포인트 목록이 기대하는 API 경로(예: /api/v1/..., /app/api/v1/...)로 나오는지

• 예시 검증 스니펫

BASE=${1:-http://localhost:8080}

for g in v1 v2; do
  echo "=== API 그룹: $g ==="
  curl -s "$BASE/v3/api-docs/$g" | jq .  # 전체 JSON 확인
  echo "servers[0].url:" 
  curl -s "$BASE/v3/api-docs/$g" | jq -r '.servers[0].url'
  echo "paths 키 일부:"
  curl -s "$BASE/v3/api-docs/$g" | jq -r '.paths | keys[:5][]'
  echo
done

위 명령으로 반환된 URL과 경로가 실제 환경에서 올바르게 매핑되는지 개발/스테이징 배포 환경에서도 반드시 검증 부탁드립니다.


14-20: SpringDoc 엔드포인트 노출 확인 필요

현재 curl 응답에 Express 헤더가 보이며 404가 반환되고 있어, Java Spring Boot 애플리케이션이 정상 구동된 서버(올바른 포트 및 context-path)인지 확인이 필요합니다.

• Spring Boot 애플리케이션이 실제 구동 중인 포트 확인 (application.yml/properties 또는 실행 로그 참고)
• 올바른 포트로 /v3/api-docs/v3/api-docs/all 요청
• 요청 시 servers 필드가 빈 배열([])이거나 유효한 URL로만 노출되는지 확인

로컬에서 확인하실 때는 아래 스크립트를 참고해 주세요.

#!/bin/bash
# 1) 실제 포트 확인 (환경변수 또는 설정 파일 기반)
PORT=${PORT:-8080}
BASE="http://localhost:${PORT}"

echo "=== /v3/api-docs 상태 & servers 필드 ==="
curl -s -D - "$BASE/v3/api-docs" -o /dev/null | head -n 10
curl -s "$BASE/v3/api-docs" | jq '.servers'

echo -e "\n=== /v3/api-docs/all 상태 & servers 필드 ==="
curl -s -D - "$BASE/v3/api-docs/all" -o /dev/null | head -n 10
curl -s "$BASE/v3/api-docs/all" | jq '.servers'

확인 후 servers 출력값이 빈 배열 또는 유효한 서버 URL만 노출되는지 최종 검증 부탁드립니다.

@kaswhy kaswhy merged commit ac46047 into develop Aug 24, 2025
2 checks passed
@kaswhy kaswhy deleted the feature/issue-179 branch August 24, 2025 15:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants