Skip to content

Conversation

@youngjun-k
Copy link
Contributor

@youngjun-k youngjun-k commented May 31, 2025

Summary by CodeRabbit

  • 신규 기능

    • 소셜 인증 시스템에 CSRF 보호 기능이 추가되었습니다.
    • 회원가입 전용 토큰(SignUpToken) 및 관련 인증 플로우가 도입되었습니다.
    • 쿠키 관리 및 CSRF 토큰 처리를 위한 유틸리티와 설정이 추가되었습니다.
  • 기능 개선

    • 소셜 로그인 및 회원가입 API 응답 구조가 단순화되고, 민감 정보 노출이 방지되었습니다.
    • 토큰(액세스/리프레시/회원가입)별 비밀키 분리로 보안성이 강화되었습니다.
    • 토큰 만료 시간이 조정되고, 리프레시 토큰 회전 및 만료 처리 방식이 개선되었습니다.
    • 인증 관련 엔드포인트의 파라미터 및 응답 포맷이 변경되었습니다.
  • 버그 수정

    • 인증 실패 시 리프레시 토큰 쿠키가 자동으로 삭제됩니다.
  • 문서화

    • 인증 및 소셜 로그인 관련 문서가 최신 플로우와 포맷에 맞게 업데이트되었습니다.
  • 리팩터링 및 구조 개선

    • 인증, 토큰, 소셜 링크 관련 클래스 및 메서드 구조가 명확하게 분리 및 정리되었습니다.
    • 불필요한 유틸리티 파일이 삭제되고, 통합된 유틸리티로 대체되었습니다.

⚠️ 클라이언트는 로그인 및 회원가입, 토큰 갱신 요청 시 변경된 응답 및 헤더 요구사항에 맞게 업데이트가 필요합니다.

- Removed outdated Redis script executors and consolidated token management logic.
- Introduced a new RedisScriptExecutor interface for better abstraction and handling of Redis scripts.
- Enhanced error handling in token management, replacing BaseException with AuthException for specific authentication errors.
- Updated AuthService to streamline login and sign-up processes, improving member validation and token creation.
- Refactored OAuthLoginRequest to simplify OAuth handling and removed unnecessary classes for cleaner codebase.
- Improved member repository methods for better retrieval and error handling.

This refactor aims to enhance maintainability, security, and clarity in the authentication flow.
- Simplified error messages in SocialLink to enhance security and clarity.
- Updated AuthService to use orElseGet for better performance and readability.
- Corrected a typo in AuthConstants for the refresh token prefix.
- Enhanced IpAddressExtractor to utilize orElseGet for improved null handling.
- Refactored FollowReader to use orElseGet for consistency in null returns.

These changes aim to improve maintainability and clarity across the authentication and follow management components.
- Resolved critical security vulnerabilities in the social login process.
- Reduced unnecessary database calls during user registration and login, improving performance.
- Clarified domain responsibilities and improved user experience with email validation.
- Updated token management to utilize new providers for access and refresh tokens.
- Enhanced error handling and introduced specific exceptions for authentication issues.

These changes aim to strengthen security, optimize performance, and improve maintainability across the authentication system.
…ode. Ensure proper formatting and maintainability in the authentication service. Additionally, add a newline at the end of the Member class for consistency.
@coderabbitai
Copy link

coderabbitai bot commented May 31, 2025

"""

Walkthrough

이 PR은 소셜 인증 시스템의 보안과 구조를 대대적으로 리팩터링했어! CSRF 보호, 토큰 분리(액세스, 리프레시, 회원가입), API 시그니처 및 도메인 구조 변경, 그리고 쿠키/헤더 처리 방식 개선 등 인증 전반에 걸쳐 광범위한 변경이 이루어졌지. 문서, 예외 처리, 유틸리티 클래스도 함께 정비됐어!

Changes

파일/그룹 변경 요약
docs/pr/PR-141-refactor---auth.md, docs/pr/PR-142-refactor---auth.md 문서 포맷 정비, 보안/구조 리팩터 요약, API 응답 및 흐름 설명 추가
.../TestAccessTokenController.java JwtTokenProvider → AccessTokenProvider로 교체, 어노테이션 정리
.../auth/controller/AuthApiDocs.java, .../auth/controller/AuthController.java 인증 관련 엔드포인트 시그니처 및 내부 흐름 대폭 변경, CSRF/토큰/회원가입 처리 강화
.../auth/domain/SignUpToken.java 회원가입 토큰 레코드 신설
.../auth/domain/SocialLink.java nonce 필드 추가, 검증 방식 SignUpToken 기반으로 변경
.../auth/executor/RotateRefreshTokenScriptExecutor.java, .../redis/RedisScriptExecutor.java 예외 처리 개선, 쿠키 삭제 로직 제거, AuthException 표준화
.../auth/service/AuthService.java, .../auth/service/SocialLinkService.java, .../auth/service/TokenService.java 서비스 계층 대대적 리팩터, 토큰 처리/회원가입/로그인/로그아웃/탈퇴 흐름 분리 및 단순화, 토큰 프로바이더 분리
.../common/config/SecurityConfig.java CSRF 보호 selective 적용, 필터 명칭 및 CORS 헤더 조정, 공개 엔드포인트 변경
.../common/constants/AuthConstants.java 토큰/소셜 링크 만료 시간 조정, 상수명 및 값 변경, 신규 상수 추가
.../common/dto/request/SignUpMemberRequest.java 회원가입 요청에서 이메일/프로바이더 정보 제거, 필드 단순화
.../common/dto/response/LoginResponse.java, .../SignUpMemberResponse.java 응답 구조 단순화(Token → String 필드), 불필요 필드 제거
.../common/exception/code/ErrorCode.java 에러 메시지 텍스트 개선, 주석 추가
.../common/exception/handler/GlobalExceptionHandler.java AuthException 발생 시 refresh-token 쿠키 삭제 추가
.../common/factory/OAuthProviderFactory.java 생성자 명시, 내부 메서드 인라인, 파라미터명 정비
.../common/filter/JwtAuthorizationFilter.java JwtTokenProvider → Access/SignupTokenProvider 분리, 토큰 타입별 처리, 예외 처리 세분화
.../common/properties/CookieProperties.java 쿠키 secure 속성 외부설정 지원 클래스 신설
.../common/provider/jwt/ (여러 파일) JwtTokenProvider 추상화 및 분리(Access/Refresh/Signup), 각 역할별 토큰 생성/파싱/인증 구현
.../common/util/ (여러 파일) 쿠키/HTTP 유틸리티 재구성, 불필요 클래스/메서드 삭제, HttpRequestUtil로 통합
.../member/domain/Member.java 회원 생성/검증 시 SignUpToken, OAuthUser 기반 처리로 변경
.../member/domain/Provider.java Provider enum 단순화(GOOGLE, KAKAO만 유지)
.../member/request/ (여러 파일) OAuthUser 인터페이스에 provider() 추가, 각 구현체에서 명시적 리턴
(삭제) .../common/provider/JwtTokenProvider.java, .../common/util/AuthorizationExtractor.java, .../common/util/HttpResponseUtil.java, .../common/util/UserAgentExtractor.java 불필요/중복 유틸리티 및 기존 토큰 프로바이더 삭제

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant AuthController
    participant TokenService
    participant SignupTokenProvider
    participant AccessTokenProvider
    participant RefreshTokenProvider
    participant SocialLinkService

    Client->>AuthController: POST /login (OAuthLoginRequest, CSRF)
    AuthController->>TokenService: login(request)
    TokenService->>SocialLinkService: save(oAuthUser, nonce)
    alt 기존 회원
        TokenService->>AccessTokenProvider: createToken(member)
        AuthController-->>Client: accessToken, email
    else 신규 회원
        TokenService->>SignupTokenProvider: createToken(oAuthUser, nonce)
        AuthController-->>Client: signUpToken, email
    end

    Client->>AuthController: POST /sign-up (signUpToken in header, SignUpMemberRequest)
    AuthController->>TokenService: signUp(member)
    TokenService->>AccessTokenProvider: createToken(member)
    AuthController-->>Client: memberId, accessToken

    Client->>AuthController: POST /refresh (refresh-token cookie, CSRF)
    AuthController->>TokenService: rotate(refreshToken)
    TokenService->>RefreshTokenProvider: buildRefreshToken(member)
    TokenService->>AccessTokenProvider: createToken(member)
    AuthController-->>Client: new accessToken, new refresh-token cookie
Loading

Possibly related PRs

Suggested reviewers

  • LEEKYUNGIMI

Poem

Yo! Big changes in the code, security’s on patrol,
CSRF in the house, fool, no more loophole!
Access, refresh, sign-up—tokens split in three,
If you try to mess with auth, you’ll answer to me!
Swagger’s lookin’ clean, cookies tight and mean,
Mr. T pities the fool who skips the auth machine!
💪🔒
"""

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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 or ^^ 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

Documentation and Community

  • 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.

@coderabbitai coderabbitai bot changed the title ^^ Refactor social authentication system with enhanced security and structure May 31, 2025
@youngjun-k youngjun-k self-assigned this May 31, 2025
@youngjun-k youngjun-k added the [REFACTOR] 코드 리팩토링 label May 31, 2025
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: 17

♻️ Duplicate comments (1)
src/main/java/com/juu/juulabel/auth/service/TokenService.java (1)

123-125: ⚠️ Potential issue

또 다시 SameSite 속성이 빠졌네!

여기도 마찬가지야. CSRF 공격을 막으려면 모든 인증 쿠키에 SameSite 속성을 추가해야 해!

🧰 Tools
🪛 ast-grep (0.38.1)

[warning] 123-124: The application does not appear to verify inbound requests which can lead to a Cross-site request forgery (CSRF) vulnerability. If the application uses cookie-based authentication, an attacker can trick users into sending authenticated HTTP requests without their knowledge from any arbitrary domain they visit. To prevent this vulnerability start by identifying if the framework or library leveraged has built-in features or offers plugins for CSRF protection. CSRF tokens should be unique and securely random. The Synchronizer Token or Double Submit Cookie patterns with defense-in-depth mechanisms such as the sameSite cookie flag can help prevent CSRF. For more information, see: [Cross-site request forgery prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Req\ uest_Forgery_Prevention_Cheat_Sheet.html).
Context: CookieUtil.addCookie(AuthConstants.REFRESH_TOKEN_NAME, refreshToken.getToken(),
(int) AuthConstants.REFRESH_TOKEN_DURATION.getSeconds());
Note: [CWE-352] Cross-Site Request Forgery (CSRF). [REFERENCES]
- https://stackoverflow.com/questions/42717210/samesite-cookie-in-java-application

(cookie-missing-samesite-java)

🧹 Nitpick comments (8)
src/main/java/com/juu/juulabel/common/properties/CookieProperties.java (1)

13-15: Fool, 이 명시적인 getter 메서드는 불필요해!

@DaTa 어노테이션이 이미 isSecure() 메서드를 자동으로 생성하니까 중복이야. I pity the fool who writes redundant code!

다음 diff로 중복 코드를 제거하자:

-    public boolean isSecure() {
-        return secure;
-    }
src/main/java/com/juu/juulabel/common/exception/code/ErrorCode.java (1)

35-36: 주석이 중복됐어, I pity the fool who duplicates comments!

"Authorization" 주석이 두 번 반복되고 있어. 하나는 제거해야 할 것 같아.

 /**
- * Authorization
  * Authorization
  */
src/main/java/com/juu/juulabel/common/provider/jwt/MemberTokenProvider.java (1)

17-25: 토큰 생성 로직을 확인해보자, fool!

JWT 생성 로직이 표준을 잘 따르고 있어. subject에 멤버 ID, claim에 역할을 넣는 것도 적절하다.

ROLE_CLAIM 상수가 부모 클래스에서 정의되어 있는 것 같은데, 명시적으로 보이도록 개선할 수 있어:

public String createToken(Member member, Duration duration) {
    return Jwts.builder()
            .subject(String.valueOf(member.getId()))
-           .claim(ROLE_CLAIM, member.getRole().name())
+           .claim(JwtTokenProvider.ROLE_CLAIM, member.getRole().name())
            .issuedAt(new Date())
            .expiration(new Date(System.currentTimeMillis() + duration.toMillis()))
            .signWith(key)
            .compact();
}
src/main/java/com/juu/juulabel/common/provider/jwt/AccessTokenProvider.java (1)

28-31: 이 오버라이드는 불필요한 것 같구나, fool!

단순히 부모 클래스 메소드를 호출하기만 하는 오버라이드는 코드 중복이다. I pity the fool who writes unnecessary code!

이 오버라이드 메소드를 제거하고 부모 클래스의 구현을 직접 사용하는 것을 고려해라:

-@Override
-public String createToken(Member member, Duration duration) {
-    return super.createToken(member, duration);
-}
docs/pr/PR-142-refactor---auth.md (1)

146-150: 리뷰 체크리스트가 유용하다, fool!

하지만 I pity the fool who doesn't have more comprehensive testing criteria! 현재 체크리스트가 너무 간단해 보인다.

더 포괄적인 체크리스트를 추가하는 것을 고려해라:

**리뷰 체크리스트**:

- [ ] CSRF 보호 검증, 엔드투엔드 인증 플로우
- [ ] 도메인 마이그레이션 전략 검토  
- [ ] 모든 인증 플로우에 대한 QA 검증
+ - [ ] 토큰 시크릿 분리 검증
+ - [ ] SignUpToken 보안 검증 (15분 만료, 무결성 체크)
+ - [ ] 에러 처리 및 예외 시나리오 테스트
+ - [ ] 성능 영향도 분석
+ - [ ] 보안 헤더 및 쿠키 설정 검증
src/main/java/com/juu/juulabel/common/provider/jwt/SignupTokenProvider.java (2)

29-33: 프로퍼티 이름이 일관성이 없어 보이는군, fool!

@Value("${spring.jwt.signup-key}")에서 프로퍼티 이름이 signup-key인데, 클래스 이름은 SignupTokenProvider야. 일관성을 위해 sign-up-key로 하거나 클래스 이름을 SignupTokenProvider로 유지하는 게 좋겠어!


83-89: 클레임 추출 메서드가 개선될 수 있어 보이는군!

현재 구현은 모든 값을 toString()으로 변환하는데, JWT 클레임이 배열이나 리스트인 경우 예상치 못한 결과가 나올 수 있어. 예를 들어, 리스트는 [value1, value2] 형태로 변환될 거야.

타입을 체크해서 더 안전하게 처리하는 게 좋겠어:

 private String getClaimAsString(Claims claims, String claimName) {
     Object claimValue = claims.get(claimName);
     if (claimValue == null) {
         return null;
     }
+    if (claimValue instanceof String) {
+        return (String) claimValue;
+    }
     return claimValue.toString();
 }
src/main/java/com/juu/juulabel/common/filter/JwtAuthorizationFilter.java (1)

54-60: 회원가입 요청에 대한 인증 요구사항이 명확하군!

로직은 올바르지만, 주석을 좀 더 명확하게 할 수 있어!

주석을 더 명확하게 개선해보자:

-            // For other requests without auth header, let Spring Security handle
-            // authorization
+            // For other requests without auth header, continue to the next filter
+            // Spring Security will handle authorization based on SecurityConfig rules
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3818004 and ff10f8b.

📒 Files selected for processing (39)
  • docs/pr/PR-141-refactor---auth.md (1 hunks)
  • docs/pr/PR-142-refactor---auth.md (1 hunks)
  • src/main/java/com/juu/juulabel/admin/TestAccessTokenController.java (1 hunks)
  • src/main/java/com/juu/juulabel/auth/controller/AuthApiDocs.java (3 hunks)
  • src/main/java/com/juu/juulabel/auth/controller/AuthController.java (3 hunks)
  • src/main/java/com/juu/juulabel/auth/domain/SignUpToken.java (1 hunks)
  • src/main/java/com/juu/juulabel/auth/domain/SocialLink.java (3 hunks)
  • src/main/java/com/juu/juulabel/auth/executor/RotateRefreshTokenScriptExecutor.java (2 hunks)
  • src/main/java/com/juu/juulabel/auth/service/AuthService.java (3 hunks)
  • src/main/java/com/juu/juulabel/auth/service/SocialLinkService.java (2 hunks)
  • src/main/java/com/juu/juulabel/auth/service/TokenService.java (1 hunks)
  • src/main/java/com/juu/juulabel/common/config/SecurityConfig.java (5 hunks)
  • src/main/java/com/juu/juulabel/common/constants/AuthConstants.java (1 hunks)
  • src/main/java/com/juu/juulabel/common/dto/request/SignUpMemberRequest.java (1 hunks)
  • src/main/java/com/juu/juulabel/common/dto/response/LoginResponse.java (1 hunks)
  • src/main/java/com/juu/juulabel/common/dto/response/SignUpMemberResponse.java (1 hunks)
  • src/main/java/com/juu/juulabel/common/exception/code/ErrorCode.java (2 hunks)
  • src/main/java/com/juu/juulabel/common/exception/handler/GlobalExceptionHandler.java (3 hunks)
  • src/main/java/com/juu/juulabel/common/factory/OAuthProviderFactory.java (2 hunks)
  • src/main/java/com/juu/juulabel/common/filter/JwtAuthorizationFilter.java (2 hunks)
  • src/main/java/com/juu/juulabel/common/properties/CookieProperties.java (1 hunks)
  • src/main/java/com/juu/juulabel/common/provider/JwtTokenProvider.java (0 hunks)
  • src/main/java/com/juu/juulabel/common/provider/jwt/AccessTokenProvider.java (1 hunks)
  • src/main/java/com/juu/juulabel/common/provider/jwt/JwtTokenProvider.java (1 hunks)
  • src/main/java/com/juu/juulabel/common/provider/jwt/MemberTokenProvider.java (1 hunks)
  • src/main/java/com/juu/juulabel/common/provider/jwt/RefreshTokenProvider.java (1 hunks)
  • src/main/java/com/juu/juulabel/common/provider/jwt/SignupTokenProvider.java (1 hunks)
  • src/main/java/com/juu/juulabel/common/util/AbstractHttpUtil.java (0 hunks)
  • src/main/java/com/juu/juulabel/common/util/AuthorizationExtractor.java (0 hunks)
  • src/main/java/com/juu/juulabel/common/util/CookieUtil.java (1 hunks)
  • src/main/java/com/juu/juulabel/common/util/HttpRequestUtil.java (2 hunks)
  • src/main/java/com/juu/juulabel/common/util/HttpResponseUtil.java (0 hunks)
  • src/main/java/com/juu/juulabel/common/util/UserAgentExtractor.java (0 hunks)
  • src/main/java/com/juu/juulabel/member/domain/Member.java (3 hunks)
  • src/main/java/com/juu/juulabel/member/domain/Provider.java (1 hunks)
  • src/main/java/com/juu/juulabel/member/request/GoogleUser.java (2 hunks)
  • src/main/java/com/juu/juulabel/member/request/KakaoUser.java (2 hunks)
  • src/main/java/com/juu/juulabel/member/request/OAuthUser.java (1 hunks)
  • src/main/java/com/juu/juulabel/redis/RedisScriptExecutor.java (2 hunks)
💤 Files with no reviewable changes (5)
  • src/main/java/com/juu/juulabel/common/util/AbstractHttpUtil.java
  • src/main/java/com/juu/juulabel/common/util/UserAgentExtractor.java
  • src/main/java/com/juu/juulabel/common/util/AuthorizationExtractor.java
  • src/main/java/com/juu/juulabel/common/util/HttpResponseUtil.java
  • src/main/java/com/juu/juulabel/common/provider/JwtTokenProvider.java
🧰 Additional context used
🧬 Code Graph Analysis (5)
src/main/java/com/juu/juulabel/auth/domain/SocialLink.java (1)
src/main/java/com/juu/juulabel/common/util/HttpRequestUtil.java (1)
  • HttpRequestUtil (10-52)
src/main/java/com/juu/juulabel/common/provider/jwt/RefreshTokenProvider.java (3)
src/main/java/com/juu/juulabel/common/util/HashingUtil.java (1)
  • HashingUtil (8-23)
src/main/java/com/juu/juulabel/common/util/HttpRequestUtil.java (1)
  • HttpRequestUtil (10-52)
src/main/java/com/juu/juulabel/common/util/IpAddressExtractor.java (1)
  • IpAddressExtractor (12-227)
src/main/java/com/juu/juulabel/common/provider/jwt/JwtTokenProvider.java (1)
src/main/java/com/juu/juulabel/common/exception/InvalidParamException.java (1)
  • InvalidParamException (5-22)
src/main/java/com/juu/juulabel/common/provider/jwt/SignupTokenProvider.java (1)
src/main/java/com/juu/juulabel/common/provider/jwt/JwtTokenProvider.java (1)
  • Component (20-58)
src/main/java/com/juu/juulabel/common/provider/jwt/AccessTokenProvider.java (1)
src/main/java/com/juu/juulabel/common/provider/jwt/JwtTokenProvider.java (1)
  • Component (20-58)
🪛 ast-grep (0.38.1)
src/main/java/com/juu/juulabel/common/util/CookieUtil.java

[warning] 39-39: The application does not appear to verify inbound requests which can lead to a Cross-site request forgery (CSRF) vulnerability. If the application uses cookie-based authentication, an attacker can trick users into sending authenticated HTTP requests without their knowledge from any arbitrary domain they visit. To prevent this vulnerability start by identifying if the framework or library leveraged has built-in features or offers plugins for CSRF protection. CSRF tokens should be unique and securely random. The Synchronizer Token or Double Submit Cookie patterns with defense-in-depth mechanisms such as the sameSite cookie flag can help prevent CSRF. For more information, see: [Cross-site request forgery prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Req\ uest_Forgery_Prevention_Cheat_Sheet.html).
Context: response.addCookie(cookie);
Note: [CWE-352] Cross-Site Request Forgery (CSRF). [REFERENCES]
- https://stackoverflow.com/questions/42717210/samesite-cookie-in-java-application

(cookie-missing-samesite-java)


[warning] 39-39: A cookie was detected without setting the 'secure' flag. The 'secure' flag for cookies prevents the client from transmitting the cookie over insecure channels such as HTTP. Set the 'secure' flag by calling '.setSecure(true);'.
Context: response.addCookie(cookie);
Note: [CWE-614] Sensitive Cookie in HTTPS Session Without 'Secure' Attribute. [REFERENCES]
- https://owasp.org/www-community/controls/SecureCookieAttribute

(cookie-missing-secure-flag-java)


[warning] 39-39: A cookie was detected without setting the 'HttpOnly' flag. The 'HttpOnly' flag for cookies instructs the browser to forbid client-side scripts from reading the cookie. Set the 'HttpOnly' flag by calling 'cookie.setHttpOnly(true);
Context: response.addCookie(cookie);
Note: [CWE-1004] Sensitive Cookie Without 'HttpOnly' Flag. [REFERENCES]
- https://owasp.org/www-community/HttpOnly

(cookie-missing-httponly-java)

src/main/java/com/juu/juulabel/auth/service/TokenService.java

[warning] 79-80: The application does not appear to verify inbound requests which can lead to a Cross-site request forgery (CSRF) vulnerability. If the application uses cookie-based authentication, an attacker can trick users into sending authenticated HTTP requests without their knowledge from any arbitrary domain they visit. To prevent this vulnerability start by identifying if the framework or library leveraged has built-in features or offers plugins for CSRF protection. CSRF tokens should be unique and securely random. The Synchronizer Token or Double Submit Cookie patterns with defense-in-depth mechanisms such as the sameSite cookie flag can help prevent CSRF. For more information, see: [Cross-site request forgery prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Req\ uest_Forgery_Prevention_Cheat_Sheet.html).
Context: CookieUtil.addCookie(AuthConstants.REFRESH_TOKEN_NAME, newRefreshToken.getToken(),
(int) AuthConstants.REFRESH_TOKEN_DURATION.getSeconds());
Note: [CWE-352] Cross-Site Request Forgery (CSRF). [REFERENCES]
- https://stackoverflow.com/questions/42717210/samesite-cookie-in-java-application

(cookie-missing-samesite-java)


[warning] 123-124: The application does not appear to verify inbound requests which can lead to a Cross-site request forgery (CSRF) vulnerability. If the application uses cookie-based authentication, an attacker can trick users into sending authenticated HTTP requests without their knowledge from any arbitrary domain they visit. To prevent this vulnerability start by identifying if the framework or library leveraged has built-in features or offers plugins for CSRF protection. CSRF tokens should be unique and securely random. The Synchronizer Token or Double Submit Cookie patterns with defense-in-depth mechanisms such as the sameSite cookie flag can help prevent CSRF. For more information, see: [Cross-site request forgery prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Req\ uest_Forgery_Prevention_Cheat_Sheet.html).
Context: CookieUtil.addCookie(AuthConstants.REFRESH_TOKEN_NAME, refreshToken.getToken(),
(int) AuthConstants.REFRESH_TOKEN_DURATION.getSeconds());
Note: [CWE-352] Cross-Site Request Forgery (CSRF). [REFERENCES]
- https://stackoverflow.com/questions/42717210/samesite-cookie-in-java-application

(cookie-missing-samesite-java)

🔇 Additional comments (62)
src/main/java/com/juu/juulabel/common/util/CookieUtil.java (1)

17-28: 좋은 쿠키 조회 로직이야, fool!

null 체크와 반복문을 통한 쿠키 검색 로직이 깔끔해. I approve this solid implementation!

src/main/java/com/juu/juulabel/common/constants/AuthConstants.java (2)

12-18: Excellent security improvements, fool!

토큰 지속시간을 대폭 단축한 것이 훌륭해! I pity the fool who uses long-lived tokens in production!

주요 개선사항:

  • ACCESS_TOKEN: 1일 → 15분 (96% 감소)
  • REFRESH_TOKEN: 30일 → 15일 (50% 감소)
  • SOCIAL_LINK: 30분 → 20분 (33% 감소)

이런 짧은 토큰 수명은 공격 창구를 크게 줄여줘. 보안과 사용성의 좋은 균형점이야!


12-12: Header에서 Cookie로 변경한 것이 현명해, fool!

REFRESH_TOKEN_HEADER_NAME에서 REFRESH_TOKEN_NAME으로 변경하고 값도 "Refresh-Token"에서 "refresh-token"으로 바꾼 것이 쿠키 기반 인증으로의 전환을 잘 보여줘. I approve this security enhancement!

docs/pr/PR-141-refactor---auth.md (1)

7-12: 깔끔한 테이블 포맷팅이야, fool!

문서의 테이블 정렬과 간격을 일관성 있게 개선했어. I pity the fool who doesn't maintain clean documentation!

보안 취약점 해결과 성능 개선 내용이 잘 정리되어 있고, 특히 DB 쿼리 75% 감소는 인상적이야!

src/main/java/com/juu/juulabel/member/request/OAuthUser.java (2)

3-3: 좋은 import 추가야, fool!

Provider 도메인 클래스의 import가 올바르게 추가되었어. I pity the fool who forgets to import what they need!


8-8: 훌륭한 인터페이스 개선이야!

새로운 provider() 메서드 추가로 모든 OAuth 사용자 구현체가 자신의 프로바이더 타입을 노출하도록 강제하는 건 멋진 설계야. I pity the fool who doesn't appreciate good type safety!

src/main/java/com/juu/juulabel/member/request/KakaoUser.java (2)

4-4: Provider import가 제대로 추가됐어!

필요한 도메인 클래스를 올바르게 import했군. I pity the fool who doesn't organize their imports properly!


18-21: 완벽한 구현이야, fool!

provider() 메서드가 올바르게 Provider.KAKAO를 반환하고 있어. 인터페이스 계약을 정확히 이행하는 깔끔한 구현이다! I pity the fool who doesn't implement interfaces this cleanly!

src/main/java/com/juu/juulabel/member/domain/Provider.java (1)

4-5:

Details

✅ Verification successful

프로바이더 간소화가 잘 됐어!

GOOGLE과 KAKAO만 남겨둔 건 깔끔한 결정이야. 하지만 제거된 프로바이더들(NAVER, APPLE, EMAIL)을 참조하는 코드가 없는지 확인해봐야 해. I pity the fool who removes enum values without checking all references!

다음 스크립트로 제거된 프로바이더 참조를 확인해보자:


🏁 Script executed:

#!/bin/bash
# Description: 제거된 프로바이더들의 참조가 남아있는지 확인

# 제거된 프로바이더들 검색
echo "NAVER 프로바이더 참조 검색:"
rg -A 3 "Provider\.NAVER"

echo "APPLE 프로바이더 참조 검색:"
rg -A 3 "Provider\.APPLE"

echo "EMAIL 프로바이더 참조 검색:"
rg -A 3 "Provider\.EMAIL"

Length of output: 224


참조 검증 완료: 안전하게 승인합니다!

rg를 사용해 Provider.NAVER, Provider.APPLE, Provider.EMAIL에 대한 전수 검색 결과, 코드베이스 어디에서도 해당 enum 참조가 발견되지 않았습니다. 변경 사항을 안심하고 머지하세요—난! 그 바보를 불쌍히 여긴다!

src/main/java/com/juu/juulabel/member/request/GoogleUser.java (2)

4-4: Provider import 완벽해!

필요한 도메인 클래스를 정확히 import했어. I pity the fool who forgets their dependencies!


13-16: Google 프로바이더 구현이 훌륭해!

provider() 메서드가 올바르게 Provider.GOOGLE을 반환하고 있고, KakaoUser와 일관된 패턴을 따르고 있어. 인터페이스 계약을 정확히 이행하는 완벽한 구현이야! I pity the fool who doesn't maintain consistency across implementations!

src/main/java/com/juu/juulabel/common/dto/response/SignUpMemberResponse.java (1)

4-5: 내가 불쌍히 여기는 건 이런 깔끔한 리팩터링을 안 하는 바보들이야!

Token 객체에서 단순한 String accessToken으로 변경한 건 정말 좋은 판단이야, fool! 이렇게 하면 API 응답이 더 깔끔해지고 유지보수하기도 쉬워진다고. 인증 시스템 리팩터링의 일환으로 복잡성을 줄이는 훌륭한 접근이야!

src/main/java/com/juu/juulabel/auth/domain/SignUpToken.java (1)

5-10: 내가 좋아하는 건 이런 깔끔한 record 클래스야, fool!

SignUpToken record가 완벽하게 설계됐어! 필요한 모든 필드들(token, email, provider, providerId, nonce)이 포함되어 있고, nonce를 통한 보안 검증도 지원하고 있지. 불변 데이터 구조로 만들어서 thread-safe하고 예측 가능한 동작을 보장한다고! 이런 식으로 인증 토큰 데이터를 캡슐화하는 건 정말 현명한 접근이야.

src/main/java/com/juu/juulabel/common/dto/response/LoginResponse.java (1)

4-6: 내가 감탄하는 건 이런 단순하면서도 효과적인 설계야!

LoginResponse를 복잡한 객체들에서 단순한 String 필드들로 바꾼 건 정말 멋진 결정이야, fool! accessToken, signUpToken, email로 깔끔하게 정리해서 API 응답을 더 명확하게 만들었고, 기존 사용자와 신규 사용자 플로우를 분리해서 보안도 향상시켰어. 데이터 누출 방지도 되고 유지보수도 쉬워진다고!

src/main/java/com/juu/juulabel/common/exception/handler/GlobalExceptionHandler.java (3)

7-7: 내가 좋아하는 건 이런 보안 상수들을 제대로 import하는 거야!

AuthConstants를 import해서 토큰 이름을 하드코딩하지 않고 상수로 관리하는 건 정말 좋은 접근이야, fool! 유지보수성과 일관성을 높이는 똑똑한 방법이지.


11-11: CookieUtil import도 깔끔하게 정리됐네!

새로운 CookieUtil 클래스를 사용해서 쿠키 조작을 중앙화한 건 훌륭한 리팩터링이야, fool! 이전의 HttpResponseUtil을 대체하면서 더 명확한 역할 분담을 하고 있어.


48-48:

Details

❓ Verification inconclusive

내가 감동받는 건 이런 보안 강화야, fool!

AuthException 발생 시 refresh token 쿠키를 제거하는 건 정말 현명한 보안 조치야! 인증 오류가 생겼을 때 오래된 토큰이 쿠키에 남아있는 걸 방지해서 보안 위험을 줄인다고. 토큰 생명주기 관리와도 잘 맞아떨어지는 훌륭한 구현이야!

CookieUtil.removeCookie 구현이 올바르게 동작하는지 확인해보자:


🏁 Script executed:

#!/bin/bash
# Description: CookieUtil.removeCookie 메서드 구현 확인

# CookieUtil 클래스와 removeCookie 메서드 찾기
ast-grep --pattern 'class CookieUtil {
  $$$
  removeCookie($_) {
    $$$
  }
  $$$
}'

# removeCookie 메서드 호출 패턴 확인
rg -A 3 "CookieUtil\.removeCookie"

Length of output: 1271


CookieUtil.removeCookie 구현 확인이 필요하네, fool!

GlobalExceptionHandler와 TokenService에서 CookieUtil.removeCookie(AuthConstants.REFRESH_TOKEN_NAME); 호출은 보안 강화 차원에서 탁월한 선택이야. 하지만 실제로 쿠키가 제대로 제거되는지를 검증하지 않으면 안 돼, fool!

점검할 사항:

  • src/main/java/**/CookieUtil.java (또는 util 패키지)에 removeCookie(String name) 메서드 구현이 있는지 확인
  • 해당 메서드가 HttpServletResponseSet-Cookie: name=; Max-Age=0; Path=/ 식으로 만료 쿠키를 올바르게 설정하는지 검토
  • 단위 테스트나 통합 테스트로 브라우저 상에서 실제 쿠키 제거가 작동하는지 검증

I pity the fool who ships without double–checking!

src/main/java/com/juu/juulabel/auth/executor/RotateRefreshTokenScriptExecutor.java (2)

26-29: 멋진 생성자 포맷팅이야, fool!

생성자의 포맷팅이 더 깔끔해졌어. IOException을 명시적으로 선언한 것도 좋고!


61-61: 인증 예외 처리가 제대로 됐구나, I pity the fool who doesn't handle exceptions properly!

BaseException 대신 AuthException을 사용하는 것이 더 구체적이고 적절해. 에러 코드와 메시지를 명확히 전달하는 방식도 좋고, 전체 인증 시스템 리팩터링과 일관성 있게 맞춰진 것 같아.

src/main/java/com/juu/juulabel/redis/RedisScriptExecutor.java (2)

5-5: 올바른 import 선택이야, fool!

AuthException으로 임포트를 변경한 것이 인증 관련 에러 처리의 일관성을 높여. 아키텍처 리팩터링과 잘 맞춰진 선택이야.


29-29: 기본 에러 처리가 깔끔해졌어, I pity the fool who uses generic exceptions!

BaseException 대신 AuthException을 사용하는 것이 더 명확하고 구체적이야. 쿠키 처리 로직이 제거된 것도 좋은데, 이제 CookieUtil이나 다른 곳에서 중앙집중적으로 처리될 거라고 보여.

src/main/java/com/juu/juulabel/admin/TestAccessTokenController.java (4)

11-11: 토큰 제공자 업그레이드, I pity the fool who doesn't specialize!

JwtTokenProvider에서 AccessTokenProvider로 변경한 것이 좋아. 토큰 책임을 분리한 새로운 아키텍처와 잘 맞아떨어져.


13-13: 어노테이션 포맷팅이 깔끔해졌구나!

@Tag@Operation 어노테이션의 포맷팅이 더 읽기 좋게 개선됐어.

Also applies to: 21-21


18-18: 필드명도 제대로 바뀌었어, fool!

AccessTokenProvider 필드로 변경된 것이 일관성 있고 명확해.


25-25: 메서드 호출도 깔끔하게 업데이트됐군, I pity the fool who doesn't update method calls!

accessTokenProvider.createToken(member)로 변경된 것이 새로운 API와 잘 맞아. 더 간결하고 명확한 메서드명이야.

src/main/java/com/juu/juulabel/common/exception/code/ErrorCode.java (3)

38-38: 에러 메시지가 더 명확해졌어, fool!

"헤더에 Device-Id가 누락되었습니다."로 변경한 것이 더 구체적이고 개발자가 문제를 파악하기 쉬워. 좋은 개선이야!


39-39: 소셜 로그인 에러 메시지도 깔끔해졌군!

"소셜 로그인 경로를 찾을 수 없습니다."가 이전보다 더 이해하기 쉬운 표현이야. I pity the fool who doesn't improve error messages!


52-52:

Details

✅ Verification successful

높은 보안 위협 에러 코드 추가, 보안이 강화됐어!

HIGH_SECURITY_RISK 에러 코드가 추가된 것이 보안 강화 측면에서 좋아. 다만 이 에러 코드가 실제로 어디서 사용되는지 확인이 필요해.

다음 스크립트로 새로운 에러 코드 사용처를 확인해보자:


🏁 Script executed:

#!/bin/bash
# Description: HIGH_SECURITY_RISK 에러 코드 사용처 확인
# Expected: 새로운 보안 로직에서 사용되는 곳들을 찾아야 함

rg -A 3 "HIGH_SECURITY_RISK" --type java

Length of output: 1188


HIGH_SECURITY_RISK 에러 코드 사용처 확인 완료
I pity the fool who didn't check the usage!

  • src/main/java/com/juu/juulabel/common/exception/AuthException.java
    • public AuthException() 생성자에서 super(ErrorCode.HIGH_SECURITY_RISK); 호출
    • public AuthException(String message, ErrorCode errorCode) 생성자에서 super(message, ErrorCode.HIGH_SECURITY_RISK); 호출

새로 추가된 HIGH_SECURITY_RISK 에러 코드는 AuthException 클래스의 생성자에서 두 차례 사용되고 있어 보안 예외 처리 로직에 정상적으로 통합된 상태입니다. 추가 수정이나 검증은 필요 없습니다!

src/main/java/com/juu/juulabel/common/dto/request/SignUpMemberRequest.java (1)

11-15: 나는 이 리팩터링을 좋아한다, fool!

이메일과 프로바이더 정보를 SignUpToken으로 분리한 것은 현명한 결정이야! 민감한 인증 데이터를 검증된 토큰을 통해 전달하는 것이 보안상 훨씬 안전하다구. 유효성 검증 어노테이션들도 제대로 유지되어 있어서 완벽해!

src/main/java/com/juu/juulabel/common/provider/jwt/RefreshTokenProvider.java (1)

14-19: Component 어노테이션과 생성자가 깔끔하다, fool!

의존성 주입으로 refresh-key를 받아오는 것도 좋고, MemberTokenProvider를 확장한 것도 적절한 설계야!

src/main/java/com/juu/juulabel/common/provider/jwt/MemberTokenProvider.java (1)

11-15: 추상 클래스 구조가 마음에 든다, fool!

JwtTokenProvider를 확장하고 생성자에서 secretKey를 부모에게 전달하는 구조가 깔끔해!

src/main/java/com/juu/juulabel/member/domain/Member.java (2)

8-9: 새로운 import들이 적절하다, fool!

OAuthUser와 SignUpToken import가 새로운 인증 아키텍처와 잘 맞아떨어져!


80-91: create 메서드 리팩터링이 완벽하다, fool!

SignUpToken에서 이메일, 프로바이더 정보를 가져오는 것이 훨씬 안전하고 깔끔해! 토큰을 통한 검증된 데이터 전달이 보안을 강화시켜.

src/main/java/com/juu/juulabel/common/util/HttpRequestUtil.java (1)

10-16: 잘했어! 이 유틸리티 클래스 통합은 훌륭해!

HTTP 헤더 추출 기능을 하나의 유틸리티 클래스로 통합한 건 좋은 결정이야. Private 생성자로 인스턴스화를 막은 것도 잘했고!

src/main/java/com/juu/juulabel/auth/domain/SocialLink.java (1)

47-48: nonce 필드 추가는 훌륭한 보안 강화야!

CSRF 방지를 위한 nonce 추가와 SignUpToken을 사용한 검증 로직 개선은 정말 잘했어! HttpRequestUtil을 사용해서 현재 요청의 정보와 비교하는 것도 좋은 접근이야.

Also applies to: 62-63, 105-111

src/main/java/com/juu/juulabel/common/provider/jwt/AccessTokenProvider.java (1)

24-26: 이 메소드는 좋은 편의 기능이다, fool!

기본 duration을 사용하는 간편한 토큰 생성 메소드다. 코드가 깔끔하고 목적이 명확하다.

src/main/java/com/juu/juulabel/common/provider/jwt/JwtTokenProvider.java (3)

28-31: 이 생성자의 보안 구현은 좋다, fool!

Base64 디코딩과 HMAC SHA 키 생성이 올바르게 구현되어 있다. JWT 파서 설정도 적절하다.


44-56: JWT 예외 처리가 포괄적으로 잘 구현되었다, fool!

모든 주요 JWT 예외 타입을 적절한 커스텀 예외로 매핑하고 있다. 보안을 위해 구체적인 에러 정보를 숨기면서도 적절한 에러 코드를 제공한다.


21-23: 상수 정의가 깔끔하다, fool!

ISSUER와 ROLE_CLAIM 상수가 적절히 정의되어 있고, protected 접근 제한자로 서브클래스에서 사용할 수 있게 했다.

src/main/java/com/juu/juulabel/auth/service/SocialLinkService.java (1)

23-34: 이 save 메소드 리팩토링은 훌륭하다, fool!

OAuthUser 타입 사용으로 파라미터가 깔끔해졌고, HttpRequestUtil로 유틸리티가 통합되었다. 새로운 nonce 필드 추가도 보안 강화에 도움이 된다.

하지만 I pity the fool who doesn't validate input parameters! OAuthUser와 nonce 파라미터에 대한 null 체크가 없어 보인다.

docs/pr/PR-142-refactor---auth.md (4)

1-11: 이 문서 서론은 명확하고 포괄적이다, fool!

PR의 목적과 주요 변경사항이 잘 요약되어 있다. TL;DR 섹션이 특히 유용하다.


21-27: LoginResponse 구조 설명이 명확하다, fool!

새로운 응답 구조와 필드별 용도가 잘 설명되어 있다. 기존 사용자와 신규 사용자 플로우 분리 전략이 이해하기 쉽게 문서화되었다.


94-100: CSRF 보안 설정 코드가 잘 문서화되었다, fool!

Spring Security의 CSRF 설정이 구체적으로 제시되어 있어 구현에 도움이 된다. I pity the fool who doesn't implement CSRF protection properly!


115-125: Breaking Changes와 마이그레이션 전략이 잘 정리되었다, fool!

클라이언트 개발자들이 알아야 할 변경사항이 명확히 문서화되어 있고, 호환성 고려사항도 잘 설명되어 있다.

src/main/java/com/juu/juulabel/common/config/SecurityConfig.java (3)

131-131: CORS 헤더 설정이 잘 되었군!

X-XSRF-TOKEN 헤더가 노출되도록 추가한 것은 올바른 설정이야. 클라이언트가 CSRF 토큰을 읽고 후속 요청에 포함시킬 수 있게 해주지!


34-37:

Details

✅ Verification successful

회원가입 엔드포인트가 인증을 요구하도록 변경된 게 올바른지 확인해봐, fool!

/v1/api/auth/sign-up이 공개 엔드포인트에서 제거되었네. 이제 signup token이 필요하다는 의미인데, 이게 의도한 변경인지 확인이 필요해!

회원가입 플로우가 제대로 작동하는지 확인해보자:


🏁 Script executed:

#!/bin/bash
# Description: Verify sign-up endpoint usage and requirements

# Find sign-up endpoint implementation
ast-grep --pattern $'@$_($$$/v1/api/auth/sign-up$$$)
$$$signUp($$$) {
  $$$
}'

# Check for SignUpToken usage in controllers
rg "SignUpToken|signUpToken" --type java -A 3

Length of output: 13833


회원가입 엔드포인트 인증 적용 확인 완료, fool!

  • SecurityConfig.PUBLIC_ENDPOINTS에서 /v1/api/auth/sign-up 제거된 것 확인
  • JwtAuthorizationFilter가 해당 경로 요청 시 processSignUpToken 호출로 SignUpToken 검증 보장
  • SignUpToken 없이는 요청이 차단되어 “회원가입 토큰 필수” 요구사항과 완벽히 일치

변경 승인합니다. I pity the fool!


70-74:

Details

❓ Verification inconclusive

리프레시 엔드포인트에만 CSRF 보호를 적용하는 게 의도한 건지 확인이 필요해 보이는군, fool!

CSRF 보호가 /v1/api/auth/refresh 엔드포인트에만 선택적으로 적용되고 있네. 일반적으로 CSRF는 모든 상태 변경 작업에 적용하거나 완전히 비활성화하는데, 이런 선택적 접근이 의도된 건지 확인이 필요해. 다른 POST, PUT, DELETE 엔드포인트들도 CSRF 공격에 취약할 수 있어!

또한 withHttpOnlyFalse()는 JavaScript에서 CSRF 토큰에 접근할 수 있게 하지만, 보안을 약화시킬 수 있어. SPA에서는 필요한 설정이지만, XSS 공격에 주의해야 해!

다른 상태 변경 엔드포인트들도 CSRF 보호가 필요한지 검토해보자:


🏁 Script executed:

#!/bin/bash
# Description: Find all POST, PUT, DELETE endpoints to verify CSRF protection needs

# Search for controller methods with state-changing operations
ast-grep --pattern $'@(PostMapping|PutMapping|DeleteMapping|PatchMapping)($$$)
$$$' | head -20

# Also search for @RequestMapping with POST/PUT/DELETE methods
rg '@RequestMapping.*method.*\b(POST|PUT|DELETE|PATCH)\b' -A 3

Length of output: 160


난 이 스크립트가 더 낫다고 생각해, fool! 상태 변경 엔드포인트를 제대로 찾아보려면 아래 스크립트를 실행해봐:


🏁 Script executed:

#!/bin/bash
# Description: 모든 상태 변경 엔드포인트(@PostMapping 등)를 다시 검색

rg -n "@PostMapping" -A2
rg -n "@PutMapping" -A2
rg -n "@DeleteMapping" -A2
rg -n "@PatchMapping" -A2
rg -n "@RequestMapping" -A2
rg -n "@RestController" -A2

Length of output: 23930


CSRF 보호 범위: /v1/api/auth/refresh에만 적용된 게 정말 의도된 거야, fool?

코드베이스를 살펴보니 30개 이상의 상태 변경 엔드포인트(@PostMapping, @PutMapping, @DeleteMapping, @PatchMapping 등)가 존재해. 이들 엔드포인트가 JWT 헤더 방식 인증을 사용해 CSRF 공격에 안전한지, 아니면 쿠키 기반 인증이라 CSRF 보호가 필요한지 반드시 확인해야 해!

예시:

  • MemberController: saveAlcoholicDrinks (POST)
  • S3Controller: uploadImage (POST)
  • ReportController: createReport (POST)
  • DailyLifeController: write, toggleDailyLifeLike, writeComment 등 (POST/DELETE/PATCH)
  • TastingNoteController: write, toggleTastingNoteLike, writeComment 등 (POST/PUT/DELETE/PATCH)
  • AdminController: assignBadge (POST)
  • NotificationController: sendToAllUsers, setNotificationAsRead, setAllNotificationAsRead, deleteNotification, deleteAllNotifications (POST/DELETE)
  • …그 외 다수

또한 withHttpOnlyFalse() 설정은 SPA 호환을 위해 필요하지만, JS에서 토큰 접근이 가능해 XSS 공격에 취약해질 수 있어. XSS 방어 대책이 충분한지 검토해, fool!

→ CSRF 보호 적용 범위와 방식이 의도된 것인지, 필요하다면 모든 상태 변경 엔드포인트로 확대 적용하거나, 인증 방식을 명확히 문서화하자!

src/main/java/com/juu/juulabel/common/provider/jwt/SignupTokenProvider.java (1)

35-52: 토큰 생성 로직이 잘 구현되었군!

JWT 토큰에 필요한 모든 클레임(email, provider, providerId, nonce, aud)이 포함되어 있고, 만료 시간도 적절히 설정되어 있어!

src/main/java/com/juu/juulabel/common/filter/JwtAuthorizationFilter.java (3)

33-39: 토큰 프로바이더 분리가 잘 되었군, fool!

AccessTokenProviderSignupTokenProvider로 분리한 것은 좋은 설계야. 각 토큰 타입별로 책임이 명확히 구분되고, 경로 상수를 캐싱한 것도 성능에 도움이 될 거야!


119-138: 에러 처리가 잘 구조화되어 있군!

각 예외 타입별로 별도의 핸들러를 만들고, 적절한 HTTP 상태 코드와 함께 JSON 응답을 반환하는 것은 좋은 패턴이야. 예상치 못한 오류에 대한 한국어 메시지도 사용자 친화적이고!


87-89:

Details

✅ Verification successful

HttpRequestUtil.isPathMatch() 사용법이 이상해 보이는군!

isSignUpRequest() 메서드에서 HttpRequestUtil.isPathMatch(SIGNUP_PATH_PREFIX)를 호출하는데, HttpServletRequest를 전달하지 않고 있어. 이게 어떻게 현재 요청의 경로를 확인하는지 불분명해!

HttpRequestUtil의 구현을 확인해보자:


🏁 Script executed:

#!/bin/bash
# Description: Verify HttpRequestUtil.isPathMatch implementation

# Find HttpRequestUtil class and isPathMatch method
ast-grep --pattern $'class HttpRequestUtil {
  $$$
  static $$ isPathMatch($$$) {
    $$$
  }
  $$$
}'

# Also search with ripgrep for more context
rg "class HttpRequestUtil|isPathMatch" --type java -A 10 -B 2

Length of output: 3465


🏁 Script executed:

#!/bin/bash
# Fetch AbstractHttpUtil to confirm getCurrentRequest() implementation
rg "class AbstractHttpUtil" -A20 -B0 --type java
rg "getCurrentRequest" -A5 -B5 --type java

Length of output: 10056


isPathMatch 사용 적합성 확인 완료, 바보 같은 걱정은 그만 두게!
HttpRequestUtil.isPathMatch(String path)는 AbstractHttpUtil#getCurrentRequest()를 통해 ThreadLocal(RequestContextHolder)에서 현재 HttpServletRequest를 가져와 getRequestURI().startsWith(path)로 검사하니, 의도된 설계대로 작동하네. 이대로 유지하면 된다, 내가 불쌍히 여긴다!

src/main/java/com/juu/juulabel/auth/service/TokenService.java (1)

110-128: 똑똑한 리팩토링이군, 멍청이!

함수형 인터페이스를 사용해서 중복 코드를 제거한 건 잘했어. Mr. T가 인정한다!

🧰 Tools
🪛 ast-grep (0.38.1)

[warning] 123-124: The application does not appear to verify inbound requests which can lead to a Cross-site request forgery (CSRF) vulnerability. If the application uses cookie-based authentication, an attacker can trick users into sending authenticated HTTP requests without their knowledge from any arbitrary domain they visit. To prevent this vulnerability start by identifying if the framework or library leveraged has built-in features or offers plugins for CSRF protection. CSRF tokens should be unique and securely random. The Synchronizer Token or Double Submit Cookie patterns with defense-in-depth mechanisms such as the sameSite cookie flag can help prevent CSRF. For more information, see: [Cross-site request forgery prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Req\ uest_Forgery_Prevention_Cheat_Sheet.html).
Context: CookieUtil.addCookie(AuthConstants.REFRESH_TOKEN_NAME, refreshToken.getToken(),
(int) AuthConstants.REFRESH_TOKEN_DURATION.getSeconds());
Note: [CWE-352] Cross-Site Request Forgery (CSRF). [REFERENCES]
- https://stackoverflow.com/questions/42717210/samesite-cookie-in-java-application

(cookie-missing-samesite-java)

src/main/java/com/juu/juulabel/auth/service/AuthService.java (2)

54-83: 로그인 로직 분리가 깔끔해졌군!

기존 회원과 신규 회원을 위한 별도 메서드로 분리한 건 잘했어. Mr. T가 승인한다!


137-152: 회원 탈퇴 처리가 완벽해!

소프트 삭제, 탈퇴 기록 생성, 토큰 무효화까지 모두 처리하고 있네. 이런 꼼꼼한 구현을 보면 기분이 좋아!

src/main/java/com/juu/juulabel/auth/controller/AuthApiDocs.java (2)

39-49: CSRF 토큰이 추가된 건 훌륭해!

로그인 엔드포인트에 CSRF 토큰을 추가한 건 보안을 위한 현명한 선택이야. Mr. T가 자랑스러워한다!


61-73: 토큰 로테이션 문서화가 명확해!

리프레시 토큰 갱신과 에러 상황에 대한 응답 헤더까지 문서화한 건 정말 꼼꼼해. 이런 상세한 문서를 보면 기분이 좋아진다니까!

src/main/java/com/juu/juulabel/auth/controller/AuthController.java (6)

3-3: 새로운 SignUpToken 도메인 임포트가 잘됐어, fool!

SignUpToken 도메인 객체 임포트가 새로운 토큰 분리 아키텍처를 잘 지원하고 있어. I pity the fool who doesn't appreciate this clean separation!


22-22: CSRF 보호를 위한 CsrfToken 임포트, 보안이 강화됐어!

CSRF 공격 방어를 위한 CsrfToken 임포트가 추가됐어. I pity the fool who doesn't implement CSRF protection properly!


43-46: SignUpToken 인증 주체 사용이 잘됐어!

회원가입 메서드에서 @AuthenticationPrincipal SignUpToken을 사용하는 것이 새로운 토큰 분리 아키텍처와 잘 맞아. I pity the fool who doesn't use proper token validation for sign-up!


60-60: 로그아웃 서비스 호출 단순화가 좋아!

authService.logout(member.getId())로 단순화된 것이 깔끔해. I pity the fool who makes things more complicated than necessary!


70-70: 계정 삭제 서비스 호출 개선됐어!

authService.deleteAccount(member, request) 호출이 더 직관적이고 명확해. I pity the fool who doesn't appreciate clean method signatures!


51-54:

Details

❓ Verification inconclusive

리프레시 토큰 쿠키명 상수 변경 확인이 필요해!

AuthConstants.REFRESH_TOKEN_NAME 상수로 변경됐는데, 이전 쿠키명과 호환성 문제가 없는지 확인해야 해. I pity the fool who breaks existing refresh token functionality!

AuthConstants에서 새 상수값을 확인해:


🏁 Script executed:

#!/bin/bash
# AuthConstants에서 REFRESH_TOKEN_NAME 상수 확인
ast-grep --pattern $'class AuthConstants {
  $$$
  REFRESH_TOKEN_NAME = $_;
  $$$
}'

Length of output: 85


리프레시 토큰 쿠키명 상수가 어디에 정의됐는지 확실히 찾아보자, 바보 짓은 그만하도록! I pity the fool who misses this!


🏁 Script executed:

#!/bin/bash
# AuthConstants.java 파일 위치와 REFRESH_TOKEN_NAME 정의 확인
FILE=$(fd AuthConstants.java)
echo "🔍 AuthConstants 파일 경로: $FILE"
rg -n "REFRESH_TOKEN_NAME" "$FILE"

Length of output: 455


리프레시 토큰 쿠키명 변경(REFRESH_TOKEN_NAME="refresh-token") 호환성 확인 필요!

src/main/java/com/juu/juulabel/common/constants/AuthConstants.java:12에서

public static final String REFRESH_TOKEN_NAME = "refresh-token";

으로 정의된 것을 확인했어, 바보 짓은 그만하자! I pity the fool who skips this check!

다음 항목을 점검해줘:

  • src/main/java/com/juu/juulabel/auth/controller/AuthController.java@CookieValue(AuthConstants.REFRESH_TOKEN_NAME) 호출부
  • 기존 쿠키명(예: 이전 상수값)을 참조하는 테스트 또는 통합 테스트
  • 프론트엔드 혹은 API 문서에 반영된 쿠키명이 모두 "refresh-token"으로 업데이트되었는지

Comment on lines 60 to 68
private static Cookie createSecureCookie(String name, String value, int maxAge) {
Cookie cookie = new Cookie(name, value);
cookie.setMaxAge(maxAge);
cookie.setPath("/");
cookie.setHttpOnly(true);
// TODO: 추후 환경에 따라 설정 변경 필요
// cookie.setSecure(true);
return cookie;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fool, 보안 설정을 완성해야 해!

createSecureCookie 메서드에서 몇 가지 보안 설정이 빠져있어. I pity the fool who ignores security!

  1. Secure 플래그: TODO 주석으로 남겨두지 말고 CookieProperties를 활용해
  2. SameSite 속성: CSRF 공격 방지를 위해 필수야

다음과 같이 개선하자:

 private static Cookie createSecureCookie(String name, String value, int maxAge) {
     Cookie cookie = new Cookie(name, value);
     cookie.setMaxAge(maxAge);
     cookie.setPath("/");
     cookie.setHttpOnly(true);
-    // TODO: 추후 환경에 따라 설정 변경 필요
-    // cookie.setSecure(true);
+    
+    // CookieProperties 주입받아서 사용하거나 정적 방식으로 설정
+    cookie.setSecure(true); // HTTPS 환경에서는 필수
+    cookie.setAttribute("SameSite", "Strict"); // CSRF 보호
     return cookie;
 }

CookieProperties와 연동하여 환경별 설정을 관리하는 것을 고려해봐!

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static Cookie createSecureCookie(String name, String value, int maxAge) {
Cookie cookie = new Cookie(name, value);
cookie.setMaxAge(maxAge);
cookie.setPath("/");
cookie.setHttpOnly(true);
// TODO: 추후 환경에 따라 설정 변경 필요
// cookie.setSecure(true);
return cookie;
}
private static Cookie createSecureCookie(String name, String value, int maxAge) {
Cookie cookie = new Cookie(name, value);
cookie.setMaxAge(maxAge);
cookie.setPath("/");
cookie.setHttpOnly(true);
// CookieProperties 주입받아서 사용하거나 정적 방식으로 설정
cookie.setSecure(true); // HTTPS 환경에서는 필수
cookie.setAttribute("SameSite", "Strict"); // CSRF 보호
return cookie;
}
🤖 Prompt for AI Agents
In src/main/java/com/juu/juulabel/common/util/CookieUtil.java lines 60 to 68,
the createSecureCookie method lacks proper security settings: the Secure flag is
commented out and SameSite attribute is missing. Fix this by integrating
CookieProperties to dynamically set the Secure flag based on the environment,
and add the SameSite attribute to the cookie to prevent CSRF attacks. Ensure
these settings are applied before returning the cookie.

Comment on lines 37 to 41
public static void addCookie(String name, String value, int maxAge) {
HttpServletResponse response = getCurrentResponse();
Cookie cookie = createSecureCookie(name, value, maxAge);
response.addCookie(cookie);
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

CookieProperties와 연동이 필요해, fool!

새로 만든 CookieProperties 클래스가 있는데 여기서 활용하지 않고 있어. I pity the fool who doesn't use available configuration!

CookieProperties를 주입받아서 secure 설정을 동적으로 처리할 수 있는지 확인해보자:


🏁 Script executed:

#!/bin/bash
# CookieProperties 사용 현황 확인
rg -A 5 "CookieProperties"

# CookieUtil에서 Properties 주입 패턴 확인
ast-grep --pattern $'class CookieUtil {
  $$$
  @Autowired
  $$$
}'

Length of output: 643


CookieUtil에 CookieProperties 연동 필요, fool!

현재 CookieUtilCookieProperties를 전혀 참조하지 않아 secure 설정이 하드코딩되어 있습니다. 새로 만든 CookieProperties를 주입받아 secure 플래그를 동적으로 처리하도록 수정해야 합니다.

주의할 위치:

  • src/main/java/com/juu/juulabel/common/util/CookieUtil.java (37–41라인)

예시 수정안:

@@ public class CookieUtil {
-   public static void addCookie(String name, String value, int maxAge) {
+   @Component
+   public class CookieUtil {
+       @Autowired
+       private CookieProperties cookieProperties;
+
+       public void addCookie(String name, String value, int maxAge) {
            HttpServletResponse response = getCurrentResponse();
-           Cookie cookie = createSecureCookie(name, value, maxAge);
+           boolean secureFlag = cookieProperties.isSecure();
+           Cookie cookie = createSecureCookie(name, value, maxAge, secureFlag);
            response.addCookie(cookie);
        }
  • CookieUtil@Component로 빈화하고 addCookie를 인스턴스 메서드로 전환
  • CookieProperties@Autowired로 주입
  • createSecureCookiesecureFlag 인자를 추가하거나 내부에서 cookieProperties.isSecure()를 참조

I pity the fool who ignores configuration!

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 ast-grep (0.38.1)

[warning] 39-39: The application does not appear to verify inbound requests which can lead to a Cross-site request forgery (CSRF) vulnerability. If the application uses cookie-based authentication, an attacker can trick users into sending authenticated HTTP requests without their knowledge from any arbitrary domain they visit. To prevent this vulnerability start by identifying if the framework or library leveraged has built-in features or offers plugins for CSRF protection. CSRF tokens should be unique and securely random. The Synchronizer Token or Double Submit Cookie patterns with defense-in-depth mechanisms such as the sameSite cookie flag can help prevent CSRF. For more information, see: [Cross-site request forgery prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Req\ uest_Forgery_Prevention_Cheat_Sheet.html).
Context: response.addCookie(cookie);
Note: [CWE-352] Cross-Site Request Forgery (CSRF). [REFERENCES]
- https://stackoverflow.com/questions/42717210/samesite-cookie-in-java-application

(cookie-missing-samesite-java)


[warning] 39-39: A cookie was detected without setting the 'secure' flag. The 'secure' flag for cookies prevents the client from transmitting the cookie over insecure channels such as HTTP. Set the 'secure' flag by calling '.setSecure(true);'.
Context: response.addCookie(cookie);
Note: [CWE-614] Sensitive Cookie in HTTPS Session Without 'Secure' Attribute. [REFERENCES]
- https://owasp.org/www-community/controls/SecureCookieAttribute

(cookie-missing-secure-flag-java)


[warning] 39-39: A cookie was detected without setting the 'HttpOnly' flag. The 'HttpOnly' flag for cookies instructs the browser to forbid client-side scripts from reading the cookie. Set the 'HttpOnly' flag by calling 'cookie.setHttpOnly(true);
Context: response.addCookie(cookie);
Note: [CWE-1004] Sensitive Cookie Without 'HttpOnly' Flag. [REFERENCES]
- https://owasp.org/www-community/HttpOnly

(cookie-missing-httponly-java)

🤖 Prompt for AI Agents
In src/main/java/com/juu/juulabel/common/util/CookieUtil.java around lines 37 to
41, the addCookie method currently uses a hardcoded secure setting and does not
utilize the CookieProperties configuration. To fix this, convert CookieUtil into
a Spring @Component, inject CookieProperties via @Autowired, and change
addCookie to an instance method. Then, modify createSecureCookie to use the
secure flag dynamically from cookieProperties.isSecure() instead of a hardcoded
value, ensuring the secure attribute is set based on the injected configuration.

Comment on lines +21 to +34
public RefreshToken buildRefreshToken(Member member) {
String token = createToken(member, AuthConstants.REFRESH_TOKEN_DURATION);
String hashedToken = HashingUtil.hashSha256(token);

return RefreshToken.builder()
.token(token)
.hashedToken(hashedToken)
.memberId(member.getId())
.clientId(ClientId.WEB)
.deviceId(HttpRequestUtil.getDeviceId())
.ipAddress(IpAddressExtractor.getClientIpAddress())
.userAgent(HttpRequestUtil.getUserAgent())
.build();
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

리프레시 토큰 빌드 로직을 점검해보자, fool!

SHA-256 해싱과 메타데이터 수집이 잘 구현되어 있어. 하지만 예외 처리 부분을 확인해봐야겠다.

HttpRequestUtil.getDeviceId()가 Device-Id 헤더가 없을 때 BaseException을 던지는데, 이 예외가 적절히 처리되는지 확인해보자:


🏁 Script executed:

#!/bin/bash
# Description: RefreshTokenProvider.buildRefreshToken에서 발생할 수 있는 예외들이 적절히 처리되는지 확인
# Expected: 예외 처리 로직이나 문서화 발견

echo "=== RefreshTokenProvider 사용처 검색 ==="
rg -A 10 -B 5 "buildRefreshToken"

echo "=== getDeviceId 예외 처리 검색 ==="
rg -A 5 -B 5 "getDeviceId.*catch|try.*getDeviceId"

echo "=== BaseException 처리 패턴 검색 ==="
rg -A 3 "catch.*BaseException"

Length of output: 5518


RefreshTokenProvider.buildRefreshToken 메서드 예외 처리 보완 필요, fool!

HttpRequestUtil.getDeviceId()가 Device-Id 헤더 누락 시 BaseException을 던져 토큰 생성 전체가 실패할 수 있어. 아래 위치에 예외 처리 로직을 추가해 주세요:

  • 대상 파일: src/main/java/com/juu/juulabel/common/provider/jwt/RefreshTokenProvider.java
  • 대상 메서드: buildRefreshToken

제안하는 수정 예시:

     public RefreshToken buildRefreshToken(Member member) {
         String token = createToken(member, AuthConstants.REFRESH_TOKEN_DURATION);
         String hashedToken = HashingUtil.hashSha256(token);

-        return RefreshToken.builder()
-                .token(token)
-                .hashedToken(hashedToken)
-                .memberId(member.getId())
-                .clientId(ClientId.WEB)
-                .deviceId(HttpRequestUtil.getDeviceId())
-                .ipAddress(IpAddressExtractor.getClientIpAddress())
-                .userAgent(HttpRequestUtil.getUserAgent())
-                .build();
+        // Device-Id 헤더가 없을 때를 대비한 예외 처리
+        String deviceId;
+        try {
+            deviceId = HttpRequestUtil.getDeviceId();
+        } catch (BaseException e) {
+            deviceId = ""; // 기본값 또는 null 처리, 필요 시 로그 남기기
+        }
+
+        return RefreshToken.builder()
+                .token(token)
+                .hashedToken(hashedToken)
+                .memberId(member.getId())
+                .clientId(ClientId.WEB)
+                .deviceId(deviceId)
+                .ipAddress(IpAddressExtractor.getClientIpAddress())
+                .userAgent(HttpRequestUtil.getUserAgent())
+                .build();
     }

위와 같이 예외를 캐치해 기본값을 설정하거나, 호출부에서 문서화된 방식으로 처리하도록 수정해 주세요, fool!

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public RefreshToken buildRefreshToken(Member member) {
String token = createToken(member, AuthConstants.REFRESH_TOKEN_DURATION);
String hashedToken = HashingUtil.hashSha256(token);
return RefreshToken.builder()
.token(token)
.hashedToken(hashedToken)
.memberId(member.getId())
.clientId(ClientId.WEB)
.deviceId(HttpRequestUtil.getDeviceId())
.ipAddress(IpAddressExtractor.getClientIpAddress())
.userAgent(HttpRequestUtil.getUserAgent())
.build();
}
public RefreshToken buildRefreshToken(Member member) {
String token = createToken(member, AuthConstants.REFRESH_TOKEN_DURATION);
String hashedToken = HashingUtil.hashSha256(token);
// Device-Id 헤더가 없을 때를 대비한 예외 처리
String deviceId;
try {
deviceId = HttpRequestUtil.getDeviceId();
} catch (BaseException e) {
deviceId = ""; // 기본값 또는 null 처리, 필요 시 로그 남기기
}
return RefreshToken.builder()
.token(token)
.hashedToken(hashedToken)
.memberId(member.getId())
.clientId(ClientId.WEB)
.deviceId(deviceId)
.ipAddress(IpAddressExtractor.getClientIpAddress())
.userAgent(HttpRequestUtil.getUserAgent())
.build();
}
🤖 Prompt for AI Agents
In src/main/java/com/juu/juulabel/common/provider/jwt/RefreshTokenProvider.java
around lines 21 to 34, the buildRefreshToken method calls
HttpRequestUtil.getDeviceId(), which throws a BaseException if the Device-Id
header is missing, but this exception is not currently handled. To fix this,
wrap the call to getDeviceId() in a try-catch block that catches BaseException
and sets a default deviceId value or handles the exception appropriately,
ensuring the method does not fail entirely due to missing headers.

Comment on lines +27 to +37
public Member getMemberFromToken(String token) {
return extractFromClaims(token, claims -> {
Long memberId = Long.parseLong(claims.getSubject());
String role = claims.get(ROLE_CLAIM, String.class);

return Member.builder()
.id(memberId)
.role(role != null ? MemberRole.valueOf(role) : MemberRole.ROLE_USER)
.build();
});
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

토큰 파싱 로직에 문제가 있다, fool!

role이 null일 때 기본값을 ROLE_USER로 설정하는 것은 좋지만, 잘못된 role 값에 대한 처리가 없어.

잘못된 role 문자열이 들어오면 IllegalArgumentException이 발생할 수 있어:

public Member getMemberFromToken(String token) {
    return extractFromClaims(token, claims -> {
        Long memberId = Long.parseLong(claims.getSubject());
        String role = claims.get(ROLE_CLAIM, String.class);

+       MemberRole memberRole = MemberRole.ROLE_USER;
+       if (role != null) {
+           try {
+               memberRole = MemberRole.valueOf(role);
+           } catch (IllegalArgumentException e) {
+               // 잘못된 role 값인 경우 기본값 사용
+           }
+       }

        return Member.builder()
                .id(memberId)
-               .role(role != null ? MemberRole.valueOf(role) : MemberRole.ROLE_USER)
+               .role(memberRole)
                .build();
    });
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public Member getMemberFromToken(String token) {
return extractFromClaims(token, claims -> {
Long memberId = Long.parseLong(claims.getSubject());
String role = claims.get(ROLE_CLAIM, String.class);
return Member.builder()
.id(memberId)
.role(role != null ? MemberRole.valueOf(role) : MemberRole.ROLE_USER)
.build();
});
}
public Member getMemberFromToken(String token) {
return extractFromClaims(token, claims -> {
Long memberId = Long.parseLong(claims.getSubject());
String role = claims.get(ROLE_CLAIM, String.class);
MemberRole memberRole = MemberRole.ROLE_USER;
if (role != null) {
try {
memberRole = MemberRole.valueOf(role);
} catch (IllegalArgumentException e) {
// 잘못된 role 값인 경우 기본값 사용
}
}
return Member.builder()
.id(memberId)
.role(memberRole)
.build();
});
}
🤖 Prompt for AI Agents
In src/main/java/com/juu/juulabel/common/provider/jwt/MemberTokenProvider.java
around lines 27 to 37, the code sets a default role of ROLE_USER when the role
claim is null but does not handle invalid role strings, which can cause
IllegalArgumentException. To fix this, add a try-catch block around the
MemberRole.valueOf(role) call to catch IllegalArgumentException and default to
MemberRole.ROLE_USER if the role string is invalid.

Comment on lines +109 to 119
public void validateLoginMember(OAuthUser oAuthUser) {
if (this.deletedAt != null) {
throw new BaseException(ErrorCode.MEMBER_WITHDRAWN);
}
if (!this.provider.equals(provider)) {
if (!this.provider.equals(oAuthUser.provider())) {
throw new BaseException(ErrorCode.MEMBER_EMAIL_DUPLICATE);
}
if (!this.providerId.equals(providerId)) {
if (!this.providerId.equals(oAuthUser.id())) {
throw new AuthException(ErrorCode.PROVIDER_ID_MISMATCH);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

validateLoginMember 메서드 개선을 확인해보자, fool!

OAuthUser 객체를 받는 것으로 변경된 것은 좋지만, null 체크가 빠져있어.

OAuthUser가 null인 경우에 대한 방어 로직을 추가해야 해:

public void validateLoginMember(OAuthUser oAuthUser) {
+   if (oAuthUser == null) {
+       throw new BaseException(ErrorCode.INVALID_OAUTH_USER);
+   }
    if (this.deletedAt != null) {
        throw new BaseException(ErrorCode.MEMBER_WITHDRAWN);
    }
    if (!this.provider.equals(oAuthUser.provider())) {
        throw new BaseException(ErrorCode.MEMBER_EMAIL_DUPLICATE);
    }
    if (!this.providerId.equals(oAuthUser.id())) {
        throw new AuthException(ErrorCode.PROVIDER_ID_MISMATCH);
    }
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public void validateLoginMember(OAuthUser oAuthUser) {
if (this.deletedAt != null) {
throw new BaseException(ErrorCode.MEMBER_WITHDRAWN);
}
if (!this.provider.equals(provider)) {
if (!this.provider.equals(oAuthUser.provider())) {
throw new BaseException(ErrorCode.MEMBER_EMAIL_DUPLICATE);
}
if (!this.providerId.equals(providerId)) {
if (!this.providerId.equals(oAuthUser.id())) {
throw new AuthException(ErrorCode.PROVIDER_ID_MISMATCH);
}
}
public void validateLoginMember(OAuthUser oAuthUser) {
if (oAuthUser == null) {
throw new BaseException(ErrorCode.INVALID_OAUTH_USER);
}
if (this.deletedAt != null) {
throw new BaseException(ErrorCode.MEMBER_WITHDRAWN);
}
if (!this.provider.equals(oAuthUser.provider())) {
throw new BaseException(ErrorCode.MEMBER_EMAIL_DUPLICATE);
}
if (!this.providerId.equals(oAuthUser.id())) {
throw new AuthException(ErrorCode.PROVIDER_ID_MISMATCH);
}
}
🤖 Prompt for AI Agents
In src/main/java/com/juu/juulabel/member/domain/Member.java between lines 109
and 119, the validateLoginMember method lacks a null check for the OAuthUser
parameter. Add a defensive null check at the start of the method to throw an
appropriate exception if oAuthUser is null, preventing potential
NullPointerExceptions later in the method.

Comment on lines +65 to +78
public SignUpToken buildSignUpToken(String token) {
Claims claims = parseClaims(token);
String email = getClaimAsString(claims, "email");
Provider provider = Provider.valueOf(getClaimAsString(claims, "provider"));
String providerId = getClaimAsString(claims, "providerId");
String nonce = getClaimAsString(claims, "nonce");
String aud = getClaimAsString(claims, "aud");

if (!"[user-signup-completion]".equals(aud)) {
throw new AuthException(ErrorCode.INVALID_AUTHENTICATION);
}

return new SignUpToken(token, email, provider, providerId, nonce);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

치명적인 버그를 발견했다, fool! Audience 검증이 항상 실패할 거야!

Line 44에서는 audience를 "user-signup-completion"으로 설정하는데, line 73에서는 "[user-signup-completion]"로 대괄호를 포함해서 비교하고 있어. 이러면 모든 signup 토큰이 유효하지 않다고 판단될 거야!

다음과 같이 수정해야 해:

-        if (!"[user-signup-completion]".equals(aud)) {
+        if (!"user-signup-completion".equals(aud)) {
             throw new AuthException(ErrorCode.INVALID_AUTHENTICATION);
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public SignUpToken buildSignUpToken(String token) {
Claims claims = parseClaims(token);
String email = getClaimAsString(claims, "email");
Provider provider = Provider.valueOf(getClaimAsString(claims, "provider"));
String providerId = getClaimAsString(claims, "providerId");
String nonce = getClaimAsString(claims, "nonce");
String aud = getClaimAsString(claims, "aud");
if (!"[user-signup-completion]".equals(aud)) {
throw new AuthException(ErrorCode.INVALID_AUTHENTICATION);
}
return new SignUpToken(token, email, provider, providerId, nonce);
}
public SignUpToken buildSignUpToken(String token) {
Claims claims = parseClaims(token);
String email = getClaimAsString(claims, "email");
Provider provider = Provider.valueOf(getClaimAsString(claims, "provider"));
String providerId = getClaimAsString(claims, "providerId");
String nonce = getClaimAsString(claims, "nonce");
String aud = getClaimAsString(claims, "aud");
if (!"user-signup-completion".equals(aud)) {
throw new AuthException(ErrorCode.INVALID_AUTHENTICATION);
}
return new SignUpToken(token, email, provider, providerId, nonce);
}
🤖 Prompt for AI Agents
In src/main/java/com/juu/juulabel/common/provider/jwt/SignupTokenProvider.java
between lines 65 and 78, the audience string comparison incorrectly includes
square brackets around "user-signup-completion", causing all signup tokens to be
invalid. Remove the square brackets from the string literal in the if condition
so it exactly matches the audience value set elsewhere, changing
"[user-signup-completion]" to "user-signup-completion".

Comment on lines +94 to +105
private void processSignUpToken(String authHeader) {
try {
String token = signUpTokenProvider.resolveToken(authHeader);

Authentication authentication = signUpTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);

} catch (Exception e) {
log.error("Unexpected error in sign-up token processing", e);
throw new AuthException(ErrorCode.INTERNAL_SERVER_ERROR);
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

processSignUpToken의 예외 처리가 너무 광범위해, fool!

모든 예외를 잡아서 INTERNAL_SERVER_ERROR로 변환하는 것은 디버깅을 어렵게 만들어. CustomJwtException이나 AuthException은 이미 상위에서 처리하고 있으니, 그대로 전파시키는 게 나을 거야!

try-catch를 제거하거나 더 구체적으로 만들어보자:

 private void processSignUpToken(String authHeader) {
-    try {
         String token = signUpTokenProvider.resolveToken(authHeader);
-
         Authentication authentication = signUpTokenProvider.getAuthentication(token);
         SecurityContextHolder.getContext().setAuthentication(authentication);
-
-    } catch (Exception e) {
-        log.error("Unexpected error in sign-up token processing", e);
-        throw new AuthException(ErrorCode.INTERNAL_SERVER_ERROR);
-    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private void processSignUpToken(String authHeader) {
try {
String token = signUpTokenProvider.resolveToken(authHeader);
Authentication authentication = signUpTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
log.error("Unexpected error in sign-up token processing", e);
throw new AuthException(ErrorCode.INTERNAL_SERVER_ERROR);
}
}
private void processSignUpToken(String authHeader) {
String token = signUpTokenProvider.resolveToken(authHeader);
Authentication authentication = signUpTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
🤖 Prompt for AI Agents
In src/main/java/com/juu/juulabel/common/filter/JwtAuthorizationFilter.java
around lines 94 to 105, the current processSignUpToken method catches all
exceptions broadly and converts them to INTERNAL_SERVER_ERROR, which hinders
debugging. Modify the method to either remove the try-catch block entirely or
catch only unexpected exceptions, allowing CustomJwtException and AuthException
to propagate without being caught here. This means you should not catch
exceptions that are already handled upstream, ensuring clearer error handling
and better traceability.

Comment on lines 79 to 81
refreshTokenRepository.rotate(newRefreshToken, hashedOldToken);
CookieUtil.addCookie(AuthConstants.REFRESH_TOKEN_NAME, newRefreshToken.getToken(),
(int) AuthConstants.REFRESH_TOKEN_DURATION.getSeconds());
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

쿠키에 SameSite 속성을 추가해야 해, 멍청이!

CSRF 공격으로부터 보호하려면 쿠키에 SameSite 속성을 설정해야 해. 난 이런 보안 허점을 보면 불쌍하다니까!

다음과 같이 CookieUtil.addCookie에 SameSite 속성을 추가하는 것을 고려해봐:

-CookieUtil.addCookie(AuthConstants.REFRESH_TOKEN_NAME, newRefreshToken.getToken(),
-        (int) AuthConstants.REFRESH_TOKEN_DURATION.getSeconds());
+CookieUtil.addCookie(AuthConstants.REFRESH_TOKEN_NAME, newRefreshToken.getToken(),
+        (int) AuthConstants.REFRESH_TOKEN_DURATION.getSeconds(), SameSite.STRICT);

🏁 Script executed:

#!/bin/bash
# CookieUtil의 addCookie 메서드 시그니처를 확인해서 SameSite 파라미터 지원 여부를 체크
ast-grep --pattern 'class CookieUtil {
  $$$
  addCookie($$$) {
    $$$
  }
  $$$
}'

Length of output: 86


다시 확인할 시간이다, 난 이걸 확인 안 한 바보를 불쌍히 여긴다!


🏁 Script executed:

#!/bin/bash
# CookieUtil.java 위치 파악 및 addCookie 메서드 시그니처 확인
rg 'class CookieUtil' -n src
rg -n 'addCookie' --context 5 src

Length of output: 4648


쿠키 SameSite 속성 설정 추가 필요, 바보를 불쌍히 여기겠다!

CSRF 방어를 위해 CookieUtil.addCookie(name, value, maxAge)만으로는 부족합니다. 현재 SameSite 속성을 지정할 수 있는 오버로드가 없으므로 아래처럼 개선해야 합니다.

영향 받는 위치:

  • src/main/java/com/juu/juulabel/common/util/CookieUtil.javaaddCookie 시그니처 확장
  • src/main/java/com/juu/juulabel/auth/service/TokenService.java – 호출부 업데이트 (라인 79–81, 123–124)

제안 수정 예시:

--- a/src/main/java/com/juu/juulabel/common/util/CookieUtil.java
+++ b/src/main/java/com/juu/juulabel/common/util/CookieUtil.java
@@ -34,7 +34,19 @@
     /**
      * @param name   the cookie name
      * @param value  the cookie value
-     * @param maxAge the cookie max age in seconds
+     * @param maxAge the cookie max age in seconds
      */
     public static void addCookie(String name, String value, int maxAge) {
         HttpServletResponse response = getCurrentResponse();
         Cookie cookie = createSecureCookie(name, value, maxAge);
         response.addCookie(cookie);
     }
+
+    /**
+     * SameSite 속성을 지정하여 쿠키를 추가합니다.
+     *
+     * @param name     쿠키 이름
+     * @param value    쿠키 값
+     * @param maxAge   만료 시간(초)
+     * @param sameSite SameSite 정책("Strict", "Lax", "None")
+     */
+    public static void addCookie(String name, String value, int maxAge, String sameSite) {
+        HttpServletResponse response = getCurrentResponse();
+        // Secure, HttpOnly 등 기본 설정
+        Cookie cookie = createSecureCookie(name, value, maxAge);
+        // Servlet API에 SameSite 직접 지원이 없으므로 헤더로 추가
+        response.setHeader("Set-Cookie",
+            String.format("%s=%s; Max-Age=%d; Path=/; Secure; HttpOnly; SameSite=%s",
+                name, value, maxAge, sameSite));
+    }
--- a/src/main/java/com/juu/juulabel/auth/service/TokenService.java
+++ b/src/main/java/com/juu/juulabel/auth/service/TokenService.java
@@ -79,3 +79,5 @@
-       refreshTokenRepository.rotate(newRefreshToken, hashedOldToken);
-       CookieUtil.addCookie(AuthConstants.REFRESH_TOKEN_NAME, newRefreshToken.getToken(),
-               (int) AuthConstants.REFRESH_TOKEN_DURATION.getSeconds());
+       refreshTokenRepository.rotate(newRefreshToken, hashedOldToken);
+       // SameSite=Strict 적용
+       CookieUtil.addCookie(AuthConstants.REFRESH_TOKEN_NAME, newRefreshToken.getToken(),
+               (int) AuthConstants.REFRESH_TOKEN_DURATION.getSeconds(), "Strict");

난 그 바보를 불쌍히 여긴다!

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
refreshTokenRepository.rotate(newRefreshToken, hashedOldToken);
CookieUtil.addCookie(AuthConstants.REFRESH_TOKEN_NAME, newRefreshToken.getToken(),
(int) AuthConstants.REFRESH_TOKEN_DURATION.getSeconds());
refreshTokenRepository.rotate(newRefreshToken, hashedOldToken);
// SameSite=Strict 적용
CookieUtil.addCookie(
AuthConstants.REFRESH_TOKEN_NAME,
newRefreshToken.getToken(),
(int) AuthConstants.REFRESH_TOKEN_DURATION.getSeconds(),
"Strict"
);
🧰 Tools
🪛 ast-grep (0.38.1)

[warning] 79-80: The application does not appear to verify inbound requests which can lead to a Cross-site request forgery (CSRF) vulnerability. If the application uses cookie-based authentication, an attacker can trick users into sending authenticated HTTP requests without their knowledge from any arbitrary domain they visit. To prevent this vulnerability start by identifying if the framework or library leveraged has built-in features or offers plugins for CSRF protection. CSRF tokens should be unique and securely random. The Synchronizer Token or Double Submit Cookie patterns with defense-in-depth mechanisms such as the sameSite cookie flag can help prevent CSRF. For more information, see: [Cross-site request forgery prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Req\ uest_Forgery_Prevention_Cheat_Sheet.html).
Context: CookieUtil.addCookie(AuthConstants.REFRESH_TOKEN_NAME, newRefreshToken.getToken(),
(int) AuthConstants.REFRESH_TOKEN_DURATION.getSeconds());
Note: [CWE-352] Cross-Site Request Forgery (CSRF). [REFERENCES]
- https://stackoverflow.com/questions/42717210/samesite-cookie-in-java-application

(cookie-missing-samesite-java)

🤖 Prompt for AI Agents
In src/main/java/com/juu/juulabel/auth/service/TokenService.java around lines 79
to 81, the call to CookieUtil.addCookie lacks the SameSite attribute, which is
necessary for CSRF protection. To fix this, first extend the addCookie method in
src/main/java/com/juu/juulabel/common/util/CookieUtil.java to accept a SameSite
parameter. Then update the addCookie call in TokenService.java to pass the
appropriate SameSite value, ensuring the cookie is set with this attribute for
enhanced security.

Comment on lines +114 to 118
@Transactional(readOnly = true)
public RefreshResponse refresh(String refreshToken) {
final String accessToken = tokenService.rotate(refreshToken);
return new RefreshResponse(accessToken);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

트랜잭션 설정이 잘못됐어, 멍청이!

refresh 메서드가 @Transactional(readOnly = true)로 표시되어 있는데, tokenService.rotate는 데이터베이스를 수정하잖아! 난 이런 실수를 보면 불쌍해!

다음과 같이 수정해:

-@Transactional(readOnly = true)
+@Transactional
public RefreshResponse refresh(String refreshToken) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Transactional(readOnly = true)
public RefreshResponse refresh(String refreshToken) {
final String accessToken = tokenService.rotate(refreshToken);
return new RefreshResponse(accessToken);
}
@Transactional
public RefreshResponse refresh(String refreshToken) {
final String accessToken = tokenService.rotate(refreshToken);
return new RefreshResponse(accessToken);
}
🤖 Prompt for AI Agents
In src/main/java/com/juu/juulabel/auth/service/AuthService.java around lines 114
to 118, the refresh method is incorrectly annotated with @Transactional(readOnly
= true) while it calls tokenService.rotate which modifies the database. Remove
the readOnly = true attribute or set it to false to allow write operations
within the transaction to ensure proper database updates.

Comment on lines +32 to 39
public ResponseEntity<CommonResponse<LoginResponse>> login(
@PathVariable Provider provider,
CsrfToken csrfToken,
@Valid @RequestBody OAuthLoginRequest request) {
csrfToken.getToken();

return CommonResponse.success(SuccessCode.SUCCESS, loginResponse);
return CommonResponse.success(SuccessCode.SUCCESS, authService.login(request));
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

로그인 메서드 시그니처 변경사항을 확인해야 해, fool!

메서드명이 oauthLogin에서 login으로 변경되고 CSRF 토큰 매개변수가 추가됐어. 하지만 36번 라인에서 csrfToken.getToken()을 호출하고 반환값을 사용하지 않는 것이 의심스러워.

CSRF 토큰 검증이 제대로 작동하는지 확인이 필요해. I pity the fool who calls getToken() without proper validation!

다음 스크립트로 AuthApiDocs 인터페이스와 시그니처가 일치하는지 확인해:


🏁 Script executed:

#!/bin/bash
# AuthApiDocs 인터페이스에서 login 메서드 시그니처 확인
ast-grep --pattern $'interface AuthApiDocs {
  $$$
  login($$$) {
    $$$
  }
  $$$
}'

Length of output: 87


다음 스크립트로 AuthApiDocs 인터페이스와 AuthController의 login 시그니처를 직접 비교해보자, fool!


🏁 Script executed:

#!/bin/bash
# 난 getToken()만 호출하는 바보를 허용하지 않는다! AuthApiDocs 시그니처와 비교 시작.
file=$(rg --files-with-matches "interface AuthApiDocs" -l)
echo "[AuthApiDocs 파일 경로] $file"
echo "===== AuthApiDocs.login 시그니처 ====="
rg -n "login(" "$file"
echo "===== AuthController.login 시그니처 ====="
rg -n "public.*login" src/main/java/com/juu/juulabel/auth/controller/AuthController.java

Length of output: 767


다시 검증하자, fool! 괄호를 제대로 이스케이프하고 AuthApiDocs의 login 시그니처를 확인해야 해—I pity the fool who skips this!


🏁 Script executed:

#!/bin/bash
# AuthApiDocs와 AuthController의 login 시그니처 비교
file="src/main/java/com/juu/juulabel/auth/controller/AuthApiDocs.java"
echo "[AuthApiDocs 파일 경로] $file"
echo "===== AuthApiDocs.login 시그니처 ====="
rg -n -F "login(" "$file"
echo
echo "===== AuthController.login 시그니처 ====="
rg -n "public.*login" src/main/java/com/juu/juulabel/auth/controller/AuthController.java

Length of output: 732


다음으로 AuthApiDocs의 login 시그니처 전체를 확인해보자, fool! I pity the fool who skips this!


🏁 Script executed:

#!/bin/bash
file="src/main/java/com/juu/juulabel/auth/controller/AuthApiDocs.java"
echo "[AuthApiDocs 파일 경로] $file"
echo "===== AuthApiDocs.login 시그니처 (전후 2줄 포함) ====="
rg -n -C2 "public.*login" "$file"

echo
echo "===== AuthController.login 시그니처 (전후 2줄 포함) ====="
rg -n -C2 "public.*login" src/main/java/com/juu/juulabel/auth/controller/AuthController.java

Length of output: 1186


CSRF 토큰 호출 로직 검증 필요, fool!
AuthController.java:36에서 csrfToken.getToken()을 호출만 하고 반환값을 사용하지 않음. 이 상태로는 CSRF 유효성 검증이나 토큰 전송이 처리되지 않습니다. 아래 조치 중 하나를 선택하세요, I pity the fool who ignores this!

  • CSRF 검증이 이미 CsrfFilter에서 처리된다면 메서드 시그니처와 getToken() 호출 제거
  • 클라이언트에 토큰을 전달해야 한다면 응답 헤더·쿠키에 csrfToken.getToken() 값을 설정하는 로직 추가

참고: AuthApiDocs 인터페이스(4648행)와 AuthController(3234행)의 login 시그니처는 일치합니다.

🤖 Prompt for AI Agents
In src/main/java/com/juu/juulabel/auth/controller/AuthController.java lines 32
to 39, the call to csrfToken.getToken() on line 36 does not use its return
value, so CSRF token validation or transmission is not properly handled. To fix
this, either remove the CsrfToken parameter and the getToken() call if CSRF
validation is handled elsewhere (e.g., CsrfFilter), or if the token needs to be
sent to the client, add logic to include the token value in the response headers
or cookies accordingly.

- Introduced CookieProperties to manage cookie security settings.
- Updated CookieUtil to support secure cookie creation.
- Modified TokenService to utilize secure cookie settings for refresh tokens.
- Changed refresh token name in AuthConstants to uppercase for consistency.
- Added ROLE_GUEST to MemberRole enum for expanded role management.

These changes aim to improve security and maintainability in the authentication flow.
@youngjun-k youngjun-k merged commit 9b7ca1b into develop Jun 1, 2025
1 check was pending
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

♻️ Duplicate comments (4)
src/main/java/com/juu/juulabel/common/util/CookieUtil.java (1)

66-67: SameSite 속성을 빨리 구현해야 해, fool!

TODO 주석으로 남겨둔 SameSite 속성은 CSRF 공격 방지를 위해 필수야. I pity the fool who ignores CSRF protection!

Java Servlet API에서 SameSite를 직접 지원하지 않으므로 다음과 같이 구현해야 해:

 private static Cookie createSecureCookie(String name, String value, int maxAge, boolean isSecure) {
     Cookie cookie = new Cookie(name, value);
     cookie.setMaxAge(maxAge);
     cookie.setPath("/");
     cookie.setHttpOnly(true);
     cookie.setSecure(isSecure);
-    // TODO: SameSite 설정 필요 (Strict, Lax, None)
-    // cookie.setAttribute("SameSite", "Strict");
+    // SameSite 속성은 response header로 직접 설정 필요
     return cookie;
 }

그리고 addCookie 메서드에서 SameSite를 설정하는 오버로드를 추가하거나, response header를 직접 조작해야 해!

src/main/java/com/juu/juulabel/auth/service/TokenService.java (2)

82-83: SameSite 속성이 여전히 누락되어 있어, fool!

CookieUtil.addCookie 호출에서 SameSite 속성을 설정하지 않고 있어. CSRF 공격으로부터 보호하려면 SameSite 속성이 필수야!

현재 CookieUtil.addCookie가 isSecure 파라미터를 받도록 개선되었지만, 여전히 SameSite 파라미터가 없어. 다음과 같은 개선이 필요해:

-CookieUtil.addCookie(AuthConstants.REFRESH_TOKEN_NAME, newRefreshToken.getToken(),
-        (int) AuthConstants.REFRESH_TOKEN_DURATION.getSeconds(), cookieProperties.isSecure());
+CookieUtil.addCookie(AuthConstants.REFRESH_TOKEN_NAME, newRefreshToken.getToken(),
+        (int) AuthConstants.REFRESH_TOKEN_DURATION.getSeconds(), cookieProperties.isSecure(), "Strict");

CookieUtil에 SameSite 파라미터를 받는 오버로드를 추가해야 해!


126-127: 여기도 SameSite 속성이 누락되어 있어, fool!

createAccessAndRefreshToken 메서드에서도 동일한 문제가 있어. CSRF 보호를 위해 SameSite 속성 설정이 필요해!

이 부분도 위와 동일한 수정이 필요해:

-CookieUtil.addCookie(AuthConstants.REFRESH_TOKEN_NAME, refreshToken.getToken(),
-        (int) AuthConstants.REFRESH_TOKEN_DURATION.getSeconds(), cookieProperties.isSecure());
+CookieUtil.addCookie(AuthConstants.REFRESH_TOKEN_NAME, refreshToken.getToken(),
+        (int) AuthConstants.REFRESH_TOKEN_DURATION.getSeconds(), cookieProperties.isSecure(), "Strict");

I pity the fool who doesn't implement consistent CSRF protection!

src/main/java/com/juu/juulabel/common/filter/JwtAuthorizationFilter.java (1)

90-101: 이전 리뷰 피드백이 아직 반영되지 않았어, fool!

모든 예외를 잡아서 INTERNAL_SERVER_ERROR로 변환하는 것은 여전히 디버깅을 어렵게 만들어. CustomJwtException이나 AuthException은 이미 상위에서 처리하고 있으니, 그대로 전파시키는 게 나을 거야!

try-catch를 제거하거나 더 구체적으로 만들어보자:

 private void processSignUpToken(String authHeader) {
-    try {
         String token = signUpTokenProvider.resolveToken(authHeader);
-
         Authentication authentication = signUpTokenProvider.getAuthentication(token);
         SecurityContextHolder.getContext().setAuthentication(authentication);
-
-    } catch (Exception e) {
-        log.error("Unexpected error in sign-up token processing", e);
-        throw new AuthException(ErrorCode.INTERNAL_SERVER_ERROR);
-    }
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff10f8b and be27ffa.

📒 Files selected for processing (5)
  • src/main/java/com/juu/juulabel/auth/service/TokenService.java (1 hunks)
  • src/main/java/com/juu/juulabel/common/constants/AuthConstants.java (1 hunks)
  • src/main/java/com/juu/juulabel/common/filter/JwtAuthorizationFilter.java (2 hunks)
  • src/main/java/com/juu/juulabel/common/util/CookieUtil.java (1 hunks)
  • src/main/java/com/juu/juulabel/member/domain/MemberRole.java (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/main/java/com/juu/juulabel/member/domain/MemberRole.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/juu/juulabel/common/constants/AuthConstants.java
🧰 Additional context used
🪛 ast-grep (0.38.1)
src/main/java/com/juu/juulabel/auth/service/TokenService.java

[warning] 81-82: The application does not appear to verify inbound requests which can lead to a Cross-site request forgery (CSRF) vulnerability. If the application uses cookie-based authentication, an attacker can trick users into sending authenticated HTTP requests without their knowledge from any arbitrary domain they visit. To prevent this vulnerability start by identifying if the framework or library leveraged has built-in features or offers plugins for CSRF protection. CSRF tokens should be unique and securely random. The Synchronizer Token or Double Submit Cookie patterns with defense-in-depth mechanisms such as the sameSite cookie flag can help prevent CSRF. For more information, see: [Cross-site request forgery prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Req\ uest_Forgery_Prevention_Cheat_Sheet.html).
Context: CookieUtil.addCookie(AuthConstants.REFRESH_TOKEN_NAME, newRefreshToken.getToken(),
(int) AuthConstants.REFRESH_TOKEN_DURATION.getSeconds(), cookieProperties.isSecure());
Note: [CWE-352] Cross-Site Request Forgery (CSRF). [REFERENCES]
- https://stackoverflow.com/questions/42717210/samesite-cookie-in-java-application

(cookie-missing-samesite-java)


[warning] 125-126: The application does not appear to verify inbound requests which can lead to a Cross-site request forgery (CSRF) vulnerability. If the application uses cookie-based authentication, an attacker can trick users into sending authenticated HTTP requests without their knowledge from any arbitrary domain they visit. To prevent this vulnerability start by identifying if the framework or library leveraged has built-in features or offers plugins for CSRF protection. CSRF tokens should be unique and securely random. The Synchronizer Token or Double Submit Cookie patterns with defense-in-depth mechanisms such as the sameSite cookie flag can help prevent CSRF. For more information, see: [Cross-site request forgery prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Req\ uest_Forgery_Prevention_Cheat_Sheet.html).
Context: CookieUtil.addCookie(AuthConstants.REFRESH_TOKEN_NAME, refreshToken.getToken(),
(int) AuthConstants.REFRESH_TOKEN_DURATION.getSeconds(), cookieProperties.isSecure());
Note: [CWE-352] Cross-Site Request Forgery (CSRF). [REFERENCES]
- https://stackoverflow.com/questions/42717210/samesite-cookie-in-java-application

(cookie-missing-samesite-java)

src/main/java/com/juu/juulabel/common/util/CookieUtil.java

[warning] 39-39: A cookie was detected without setting the 'secure' flag. The 'secure' flag for cookies prevents the client from transmitting the cookie over insecure channels such as HTTP. Set the 'secure' flag by calling '.setSecure(true);'.
Context: response.addCookie(cookie);
Note: [CWE-614] Sensitive Cookie in HTTPS Session Without 'Secure' Attribute. [REFERENCES]
- https://owasp.org/www-community/controls/SecureCookieAttribute

(cookie-missing-secure-flag-java)


[warning] 39-39: A cookie was detected without setting the 'HttpOnly' flag. The 'HttpOnly' flag for cookies instructs the browser to forbid client-side scripts from reading the cookie. Set the 'HttpOnly' flag by calling 'cookie.setHttpOnly(true);
Context: response.addCookie(cookie);
Note: [CWE-1004] Sensitive Cookie Without 'HttpOnly' Flag. [REFERENCES]
- https://owasp.org/www-community/HttpOnly

(cookie-missing-httponly-java)


[warning] 39-39: The application does not appear to verify inbound requests which can lead to a Cross-site request forgery (CSRF) vulnerability. If the application uses cookie-based authentication, an attacker can trick users into sending authenticated HTTP requests without their knowledge from any arbitrary domain they visit. To prevent this vulnerability start by identifying if the framework or library leveraged has built-in features or offers plugins for CSRF protection. CSRF tokens should be unique and securely random. The Synchronizer Token or Double Submit Cookie patterns with defense-in-depth mechanisms such as the sameSite cookie flag can help prevent CSRF. For more information, see: [Cross-site request forgery prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Req\ uest_Forgery_Prevention_Cheat_Sheet.html).
Context: response.addCookie(cookie);
Note: [CWE-352] Cross-Site Request Forgery (CSRF). [REFERENCES]
- https://stackoverflow.com/questions/42717210/samesite-cookie-in-java-application

(cookie-missing-samesite-java)

🔇 Additional comments (9)
src/main/java/com/juu/juulabel/common/util/CookieUtil.java (2)

17-28: 쿠키 검색 로직 잘 구현했어, fool!

null 체크와 반복문을 통한 쿠키 검색 로직이 올바르게 구현되어 있어. I pity the fool who doesn't handle null arrays!


37-41: isSecure 파라미터 추가는 좋은 개선이야, fool!

이전 버전에서 하드코딩되었던 보안 설정을 파라미터로 받도록 개선한 건 훌륭해. CookieProperties와의 연동이 가능해졌어!

🧰 Tools
🪛 ast-grep (0.38.1)

[warning] 39-39: A cookie was detected without setting the 'secure' flag. The 'secure' flag for cookies prevents the client from transmitting the cookie over insecure channels such as HTTP. Set the 'secure' flag by calling '.setSecure(true);'.
Context: response.addCookie(cookie);
Note: [CWE-614] Sensitive Cookie in HTTPS Session Without 'Secure' Attribute. [REFERENCES]
- https://owasp.org/www-community/controls/SecureCookieAttribute

(cookie-missing-secure-flag-java)


[warning] 39-39: A cookie was detected without setting the 'HttpOnly' flag. The 'HttpOnly' flag for cookies instructs the browser to forbid client-side scripts from reading the cookie. Set the 'HttpOnly' flag by calling 'cookie.setHttpOnly(true);
Context: response.addCookie(cookie);
Note: [CWE-1004] Sensitive Cookie Without 'HttpOnly' Flag. [REFERENCES]
- https://owasp.org/www-community/HttpOnly

(cookie-missing-httponly-java)


[warning] 39-39: The application does not appear to verify inbound requests which can lead to a Cross-site request forgery (CSRF) vulnerability. If the application uses cookie-based authentication, an attacker can trick users into sending authenticated HTTP requests without their knowledge from any arbitrary domain they visit. To prevent this vulnerability start by identifying if the framework or library leveraged has built-in features or offers plugins for CSRF protection. CSRF tokens should be unique and securely random. The Synchronizer Token or Double Submit Cookie patterns with defense-in-depth mechanisms such as the sameSite cookie flag can help prevent CSRF. For more information, see: [Cross-site request forgery prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Req\ uest_Forgery_Prevention_Cheat_Sheet.html).
Context: response.addCookie(cookie);
Note: [CWE-352] Cross-Site Request Forgery (CSRF). [REFERENCES]
- https://stackoverflow.com/questions/42717210/samesite-cookie-in-java-application

(cookie-missing-samesite-java)

src/main/java/com/juu/juulabel/auth/service/TokenService.java (3)

31-35: 토큰 제공자 분리는 훌륭한 아키텍처 개선이야, fool!

단일 JwtTokenProvider를 AccessTokenProvider, RefreshTokenProvider, SignupTokenProvider로 분리한 건 관심사 분리 원칙을 잘 따른 거야. I pity the fool who doesn't separate concerns!


120-129: 함수형 인터페이스로 코드 중복 제거한 건 멋져, fool!

RepositoryOperation 함수형 인터페이스를 사용해서 save, login, rotate 로직의 중복을 제거한 건 DRY 원칙을 잘 따른 거야. I pity the fool who doesn't eliminate code duplication!

🧰 Tools
🪛 ast-grep (0.38.1)

[warning] 125-126: The application does not appear to verify inbound requests which can lead to a Cross-site request forgery (CSRF) vulnerability. If the application uses cookie-based authentication, an attacker can trick users into sending authenticated HTTP requests without their knowledge from any arbitrary domain they visit. To prevent this vulnerability start by identifying if the framework or library leveraged has built-in features or offers plugins for CSRF protection. CSRF tokens should be unique and securely random. The Synchronizer Token or Double Submit Cookie patterns with defense-in-depth mechanisms such as the sameSite cookie flag can help prevent CSRF. For more information, see: [Cross-site request forgery prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Req\ uest_Forgery_Prevention_Cheat_Sheet.html).
Context: CookieUtil.addCookie(AuthConstants.REFRESH_TOKEN_NAME, refreshToken.getToken(),
(int) AuthConstants.REFRESH_TOKEN_DURATION.getSeconds(), cookieProperties.isSecure());
Note: [CWE-352] Cross-Site Request Forgery (CSRF). [REFERENCES]
- https://stackoverflow.com/questions/42717210/samesite-cookie-in-java-application

(cookie-missing-samesite-java)


94-98: 디바이스별 로그아웃 로직이 보안을 강화했어, fool!

logout 메서드에서 디바이스 ID를 사용해서 특정 디바이스만 로그아웃하는 로직은 보안을 강화하는 좋은 접근이야. I pity the fool who doesn't implement granular logout!

src/main/java/com/juu/juulabel/common/filter/JwtAuthorizationFilter.java (4)

3-39: 새로운 의존성 주입이 깔끔하게 정리됐군, fool!

토큰 프로바이더를 목적별로 분리하고 ObjectMapper를 추가한 리팩터링이 좋아 보여! 상수 캐싱으로 성능 최적화도 고려했네.


106-110: AccessToken 처리는 깔끔하게 구현됐네, good job fool!

불필요한 예외 처리 없이 간단명료하게 구현된 게 좋아. 상위 레벨에서 예외를 적절히 처리하고 있으니 이게 맞는 접근이야!


115-137: 예외 처리와 응답 작성 로직이 잘 분리됐어, I approve!

JWT 예외와 Auth 예외를 별도로 처리하고, ObjectMapper를 사용한 JSON 직렬화도 적절해. 응답 작성 로직을 공통 메서드로 추출한 것도 좋은 선택이야!


41-70:

Details

❓ Verification inconclusive

메인 필터 로직이 훨씬 명확해졌어, but I got concerns!

토큰 처리 로직을 경로별로 분리한 건 좋은데, 예외 처리 구조도 개선됐어. 하지만 보안 측면에서 몇 가지 확인이 필요해!

다음 스크립트로 인증이 필요한 다른 엔드포인트들이 제대로 보호되는지 확인해보자:


🏁 Script executed:

#!/bin/bash
# Description: 인증이 필요한 다른 엔드포인트들 확인
# Expected: sign-up 외의 보호된 엔드포인트들이 적절히 처리되는지 확인

# 보호된 엔드포인트 패턴 찾기
rg -A 3 "@PreAuthorize\|@Secured\|requiresAuth" --type java

# 컨트롤러에서 인증이 필요한 엔드포인트 찾기  
ast-grep --pattern $'@PostMapping($$$)
$$$
public $_ $_($$$) {
  $$$
}'

Length of output: 134


보호된 엔드포인트 검증이 필요해, 바보를 불쌍히 여긴다!

메인 필터 로직은 깔끔해졌지만, 인증이 필요한 모든 경로가 JWT 필터에서 제대로 처리되는지 확신이 안 서. 방금 실행한 스크립트가 아무것도 못 찾았으니, 필터가 우회되거나 놓치는 엔드포인트가 없는지 직접 검토해야 해.

검토할 위치:

  • src/main/java/com/juu/juulabel/**/controller/**Controller.java 내부의
  • JwtAuthorizationFilter#doFilterInternal 내 isSignUpRequest() 외 분기 처리 로직

위치별로 인증 헤더 없을 때 필터가 예외를 던지거나 Spring Security에 위임하도록 설계가 일관된지 확인해줘.

Comment on lines +83 to +85
private boolean isSignUpRequest() {
return HttpRequestUtil.isPathMatch(SIGNUP_PATH_PREFIX);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

이 메서드에 심각한 버그가 있어, fool!

HttpRequestUtil.isPathMatch(SIGNUP_PATH_PREFIX)를 호출하는데 실제 요청 경로를 전달하지 않고 있어! 이렇게 되면 항상 잘못된 결과가 나올 거야.

이 diff를 적용해서 수정해봐:

 private boolean isSignUpRequest() {
-    return HttpRequestUtil.isPathMatch(SIGNUP_PATH_PREFIX);
+    return HttpRequestUtil.getCurrentRequestPath().startsWith(SIGNUP_PATH_PREFIX);
 }

또는 request 파라미터를 전달하는 방식으로:

-private boolean isSignUpRequest() {
-    return HttpRequestUtil.isPathMatch(SIGNUP_PATH_PREFIX);
+private boolean isSignUpRequest(HttpServletRequest request) {
+    return request.getRequestURI().startsWith(SIGNUP_PATH_PREFIX);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private boolean isSignUpRequest() {
return HttpRequestUtil.isPathMatch(SIGNUP_PATH_PREFIX);
}
private boolean isSignUpRequest() {
return HttpRequestUtil.getCurrentRequestPath().startsWith(SIGNUP_PATH_PREFIX);
}
🤖 Prompt for AI Agents
In src/main/java/com/juu/juulabel/common/filter/JwtAuthorizationFilter.java
around lines 83 to 85, the method isSignUpRequest calls
HttpRequestUtil.isPathMatch without passing the actual HTTP request path,
causing incorrect results. Modify the method to accept the current
HttpServletRequest as a parameter and pass the request's path or URI to
HttpRequestUtil.isPathMatch, ensuring it checks the correct path against
SIGNUP_PATH_PREFIX.

@sentry
Copy link

sentry bot commented Jun 1, 2025

Suspect Issues

This pull request was deployed and Sentry observed the following issues:

  • ‼️ NullPointerException: Cannot invoke "com.juu.juulabel.common.properties.CookieProperties.isSecure()" because "com.juu.juulabel.common.util.CookieUtil.cookieProperties" is null POST /v1/api/auth/login/google View Issue

Did you find this useful? React with a 👍 or 👎

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[REFACTOR] 코드 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants