Skip to content

feat(User,Token): 리프레시 토큰 중복 저장 로직 수정#70

Merged
HyemIin merged 1 commit intodevelopfrom
feature/#69-리프레시토큰중복저장로직수정
Jul 8, 2025

Hidden character warning

The head ref may contain hidden characters: "feature/#69-\ub9ac\ud504\ub808\uc2dc\ud1a0\ud070\uc911\ubcf5\uc800\uc7a5\ub85c\uc9c1\uc218\uc815"
Merged

feat(User,Token): 리프레시 토큰 중복 저장 로직 수정#70
HyemIin merged 1 commit intodevelopfrom
feature/#69-리프레시토큰중복저장로직수정

Conversation

@HyemIin
Copy link
Copy Markdown
Member

@HyemIin HyemIin commented Jul 8, 2025

작업 요약

Issue Link

문제점 및 어려움

해결 방안

Reference

Summary by CodeRabbit

  • 신규 기능

    • 리프레시 토큰이 쿠키를 통해 전달되도록 로그인 및 토큰 재발급 플로우가 개선되었습니다.
    • 리프레시 토큰과 엑세스 토큰이 모두 응답에 반환됩니다.
  • 버그 수정

    • 기존에 사용하지 않던 코드 및 불필요한 import가 정리되었습니다.
  • 리팩토링

    • 토큰 관리 로직이 개선되어, 기존 토큰이 있으면 갱신하고 없으면 새로 생성하는 방식으로 동작합니다.
    • 토큰 엔티티와 저장소가 확장되어 사용자 기준으로 토큰을 조회할 수 있습니다.
    • 토큰과 사용자 간의 관계가 데이터베이스에서 1:1로 강제됩니다.
  • 보안

    • 리프레시 토큰이 HttpOnly 및 SameSite 속성이 적용된 쿠키로 저장되어 보안이 강화되었습니다.

@HyemIin HyemIin self-assigned this Jul 8, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jul 8, 2025

Walkthrough

이번 변경에서는 관리자 및 사용자 API에서 리프레시 토큰의 발급 및 갱신 방식을 개선하였으며, 토큰 엔티티의 일대일 관계를 데이터베이스 수준에서 보장하도록 수정했습니다. 또한, 토큰 쿠키를 HTTP 응답 헤더에 안전하게 설정하고, 관련 서비스 및 컨트롤러의 메서드 시그니처와 동작을 일관성 있게 변경했습니다.

Changes

파일/경로 그룹 변경 요약
nowait-app-admin-api/.../order/service/OrderService.java 사용하지 않는 import(메뉴, 매장 관련) 제거
nowait-app-admin-api/.../token/controller/TokenController.java
nowait-app-user-api/.../token/controller/TokenController.java
refreshToken 엔드포인트가 리프레시 토큰을 요청 본문이 아닌 쿠키에서 읽도록 변경, 쿠키 없을 시 401 반환, 토큰 갱신 로직 및 응답 포맷 수정, 메서드 시그니처 변경
nowait-app-admin-api/.../user/serivce/UserService.java login 메서드가 ResponseEntity 반환, 토큰 생성 및 쿠키 설정, 토큰 저장/업데이트 로직 추가, TokenRepository 필드 추가
nowait-app-user-api/.../oauth/oauth2/OAuth2LoginSuccessHandler.java onAuthenticationSuccess에서 토큰 중복 저장 방지, 쿠키 생성 방식 변경, @transactional 추가, 불필요한 import 제거
nowait-domain/domain-core-rdb/.../token/entity/Token.java user 필드 @joincolumn에 unique=true 추가(1:1 관계 보장), updateRefreshToken 메서드 추가
nowait-domain/domain-core-rdb/.../token/repository/TokenRepository.java findByUser(User user) 메서드 추가, User import 추가

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller
    participant Service
    participant TokenRepository
    participant DB

    Client->>Controller: 로그인 요청 (ID, PW)
    Controller->>Service: login 호출
    Service->>TokenRepository: 사용자 토큰 조회(findByUser)
    TokenRepository-->>Service: 기존 토큰 or 없음
    Service->>TokenRepository: 토큰 저장/업데이트
    TokenRepository-->>Service: 저장 완료
    Service->>Controller: ResponseEntity(로그인 정보, accessToken)
    Controller->>Client: accessToken + refreshToken 쿠키

    Client->>Controller: refreshToken 요청 (refreshToken 쿠키 포함)
    Controller->>Service: refreshToken 검증 및 갱신
    Service->>TokenRepository: refreshToken 유효성 확인 및 갱신
    TokenRepository-->>Service: 토큰 정보 반환
    Service->>Controller: 새로운 accessToken, refreshToken
    Controller->>Client: accessToken + refreshToken 쿠키
Loading

Suggested reviewers

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

@github-actions github-actions Bot requested a review from Jjiggu July 8, 2025 07:43
Copy link
Copy Markdown

@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: 2

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a68a42f and e432706.

📒 Files selected for processing (7)
  • nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java (0 hunks)
  • nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/token/controller/TokenController.java (2 hunks)
  • nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/serivce/UserService.java (4 hunks)
  • nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java (3 hunks)
  • nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java (2 hunks)
  • nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/token/entity/Token.java (2 hunks)
  • nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/token/repository/TokenRepository.java (1 hunks)
💤 Files with no reviewable changes (1)
  • nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java
🧰 Additional context used
🪛 ast-grep (0.38.1)
nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java

[warning] 69-69: 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.setHeader("Set-Cookie", refreshTokenCookie.toString());
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 (23)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/token/repository/TokenRepository.java (2)

9-9: User 엔티티 import 추가 확인

새로운 findByUser 메서드를 지원하기 위한 필요한 import가 올바르게 추가되었습니다.


15-15: 사용자 엔티티 기반 토큰 조회 메서드 추가

User 엔티티를 직접 매개변수로 받는 조회 메서드가 추가되어 기존 findByUserId 메서드와 함께 다양한 상황에서 유연하게 사용할 수 있게 되었습니다. 코드의 가독성과 사용성이 향상되었습니다.

nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/token/entity/Token.java (2)

31-31: 사용자당 토큰 중복 방지를 위한 unique 제약조건 추가

unique = true 제약조건 추가로 한 사용자당 하나의 토큰만 존재할 수 있도록 데이터베이스 수준에서 보장하게 되었습니다. 이는 리프레시 토큰 중복 저장 문제를 근본적으로 해결하는 중요한 변경사항입니다.


55-58: 토큰 업데이트 메서드 추가

기존 토큰 엔티티의 리프레시 토큰과 만료 시간을 업데이트할 수 있는 메서드가 추가되어, 토큰 갱신 시 삭제/생성 대신 업데이트 방식으로 효율적으로 처리할 수 있게 되었습니다.

nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/token/controller/TokenController.java (3)

6-6: 쿠키 기반 토큰 처리를 위한 import 추가

쿠키에서 리프레시 토큰을 읽어오기 위한 @CookieValue 어노테이션 import가 추가되었습니다.


37-42: 보안 강화를 위한 쿠키 기반 토큰 처리

리프레시 토큰을 요청 바디 대신 HTTP-only 쿠키에서 읽어오도록 변경되어 XSS 공격으로부터 보호할 수 있게 되었습니다. 쿠키가 없는 경우 적절한 401 응답을 반환하는 검증 로직도 포함되어 있습니다.


54-54: 토큰 로테이션을 위한 새로운 토큰 반환

새로운 액세스 토큰과 리프레시 토큰을 모두 반환하도록 변경되어 토큰 로테이션을 구현하였습니다. 이는 보안 측면에서 권장되는 방식입니다.

nowait-app-user-api/src/main/java/com/nowait/applicationuser/oauth/oauth2/OAuth2LoginSuccessHandler.java (4)

5-5: 트랜잭션 처리를 위한 import 및 어노테이션 추가

@Transactional 어노테이션 추가로 토큰 저장/업데이트 작업의 원자성을 보장하게 되었습니다. 이는 데이터 일관성을 위한 중요한 개선사항입니다.

Also applies to: 11-11, 37-37


7-7: ResponseCookie 사용을 위한 import 추가

기존 Cookie 클래스 대신 Spring의 ResponseCookie를 사용하여 더 세밀한 쿠키 설정이 가능해졌습니다.


50-58: 토큰 중복 저장 방지 로직 구현

기존 토큰 존재 여부를 확인하여 업데이트 또는 새로 생성하는 로직으로 변경되어 토큰 중복 저장 문제를 해결하였습니다. 이는 PR 목표와 정확히 일치하는 개선사항입니다.


61-70: CSRF 보호 설정 검증 필요
OAuth2LoginSuccessHandler에서 Refresh Token을 HttpOnly 쿠키로 안전하게 설정하셨습니다. 하지만 CSRF 공격 방지를 위해 애플리케이션 전체에서 CSRF 보호 기능이 제대로 활성화되어 있는지 반드시 검토해야 합니다.

점검해야 할 항목:

  • SecurityConfig (또는 WebSecurityConfigurerAdapter를 확장한 클래스)에서 csrf().disable() 호출 여부
  • @EnableWebSecurity 혹은 SecurityFilterChain 빈 설정 내 http.csrf() 사용 유무
  • OAuth2LoginSuccessHandler가 포함된 모듈의 보안 설정 파일 위치 및 내용

위 설정이 확인되지 않으면 CSRF 보호를 활성화 하거나, 필요 시 추가적인 CSRF 토큰 검증 로직을 도입하세요.

nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java (4)

6-6: 쿠키 기반 토큰 처리를 위한 import 추가

관리자 API와 일관되게 쿠키에서 리프레시 토큰을 읽어오기 위한 @CookieValue 어노테이션 import가 추가되었습니다.


37-42: 보안 강화를 위한 쿠키 기반 토큰 처리

관리자 API와 동일한 방식으로 리프레시 토큰을 HTTP-only 쿠키에서 읽어오도록 변경되어 XSS 공격으로부터 보호할 수 있게 되었습니다.


54-54: 토큰 로테이션을 위한 새로운 토큰 반환

새로운 액세스 토큰과 리프레시 토큰을 모두 반환하도록 변경되어 관리자 API와 일관된 토큰 로테이션을 구현하였습니다.


49-49: SUPER_ADMIN 역할 토큰 만료 처리 로직 제거 확인 필요

TokenController( nowait-app-user-api/src/main/java/com/nowait/applicationuser/token/controller/TokenController.java 49번 라인)에서 모든 역할에 동일한 accessTokenExpiration을 사용하도록 변경되었습니다.
이전에는 SUPER_ADMIN에 대해 별도 만료 시간을 적용했으나, user-api 쪽에서는 해당 로직이 완전히 제거된 상태입니다.

확인 요청 사항:

  • TokenController.java 49번 라인:
    String newAccessToken = jwtUtil.createAccessToken("accessToken", userId, role, accessTokenExpiration);
  • nowait-app-user-api 내에 SUPER_ADMIN 전용 만료 시간 처리 로직이 더 이상 존재하지 않습니다.
  • nowait-app-admin-api 쪽 서비스에서는 여전히 SUPER_ADMIN 전용 만료 시간이 설정되어 있습니다.

위 변경이 의도된 것인지 검토 부탁드립니다.

nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/serivce/UserService.java (8)

3-4: 새로운 임포트가 올바르게 추가되었습니다.

리프레시 토큰 기능을 위해 필요한 임포트가 적절히 추가되었습니다.


7-9: HTTP 관련 임포트가 적절히 추가되었습니다.

ResponseEntity 반환 타입과 쿠키 설정을 위해 필요한 임포트가 올바르게 추가되었습니다.


22-23: 토큰 관련 임포트가 올바르게 추가되었습니다.

토큰 엔티티와 리포지토리 사용을 위해 필요한 임포트가 적절히 추가되었습니다.


36-36: TokenRepository 필드가 올바르게 추가되었습니다.

토큰 데이터베이스 작업을 위해 필요한 의존성이 적절히 추가되었습니다.


98-104: 쿠키 보안 설정이 적절합니다.

HTTP-only, secure, SameSite 설정이 올바르게 구성되어 XSS 및 CSRF 공격을 방지합니다.


84-97: 토큰 업데이트 로직이 올바르게 구현되었습니다.

기존 토큰 존재 여부를 확인하고 업데이트하거나 새로 생성하는 로직이 적절히 구현되었습니다.


106-108: ResponseEntity 반환 타입 변경이 적절합니다.

쿠키 설정을 위해 ResponseEntity로 변경한 것이 올바른 접근입니다.


85-85: 토큰 리포지토리 메서드 시그니처는 유효합니다.
TokenRepository 인터페이스에 findByUserId(Long)findByUser(User)가 모두 선언되어 있어, 현재 코드에서 findByUserId(user.getId()) 호출은 올바릅니다. AI 요약에서 findByUser(User)만 언급된 부분은 업데이트해주세요.

하드코딩된 30일 만료 값은 Duration.ofDays(30) 형태로는 코드베이스에서 발견되지 않았습니다. 만약 plusDays(30) 또는 ChronoUnit.DAYS 등을 이용해 직접 만료 기간을 계산하는 로직이 있다면, 아래 위치들을 중심으로 수동 확인 후 설정(ex. application.yml/property 혹은 상수)으로 추출하는 리팩터링을 고려해주세요.

  • 확인 대상
    • TokenRepository 정의:
      nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/token/repository/TokenRepository.java
    • 토큰/세션 생성 및 만료 기간 계산 로직 전체


String accessToken = jwtUtil.createAccessToken("accessToken", user.getId(), String.valueOf(user.getRole()), currentAccessTokenExpiration);
return ManagerLoginResponseDto.fromEntity(user,accessToken);
String refreshToken = jwtUtil.createRefreshToken("refreshToken", user.getId(), 30L * 24 * 60 * 60 * 1000L);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

하드코딩된 만료 시간을 설정값으로 추출하세요.

30일 만료 시간이 여러 곳에 하드코딩되어 있습니다. 유지보수성을 위해 설정값으로 추출하는 것을 권장합니다.

설정 파일에 다음을 추가하세요:

jwt.refresh-token-expiration-days=30

그리고 다음과 같이 수정하세요:

+@Value("${jwt.refresh-token-expiration-days}")
+private long refreshTokenExpirationDays;

-String refreshToken = jwtUtil.createRefreshToken("refreshToken", user.getId(), 30L * 24 * 60 * 60 * 1000L);
+String refreshToken = jwtUtil.createRefreshToken("refreshToken", user.getId(), refreshTokenExpirationDays * 24 * 60 * 60 * 1000L);

-token.updateRefreshToken(refreshToken, LocalDateTime.now().plusDays(30L));
+token.updateRefreshToken(refreshToken, LocalDateTime.now().plusDays(refreshTokenExpirationDays));

-expiredDate(LocalDateTime.now().plusDays(30L))
+expiredDate(LocalDateTime.now().plusDays(refreshTokenExpirationDays))

-maxAge(30L * 24 * 60 * 60)
+maxAge(refreshTokenExpirationDays * 24 * 60 * 60)

Also applies to: 88-88, 94-94, 102-102

🤖 Prompt for AI Agents
In
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/serivce/UserService.java
at lines 82, 88, 94, and 102, the refresh token expiration time is hardcoded as
30 days in milliseconds. To improve maintainability, extract this hardcoded
value into a configuration property by adding
'jwt.refresh-token-expiration-days=30' to the properties file. Then, modify the
code to read this value from the configuration, convert it to milliseconds, and
use it instead of the hardcoded literal.

long currentAccessTokenExpiration = accessTokenExpiration;
if (user.getRole() == com.nowait.common.enums.Role.SUPER_ADMIN) {
currentAccessTokenExpiration = 7L * 24 * 60 * 60 * 1000L; // 7일
currentAccessTokenExpiration = 100L * 24 * 60 * 60 * 1000L; // 100일
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

SUPER_ADMIN 토큰 만료 시간이 너무 길 수 있습니다.

100일의 액세스 토큰 만료 시간은 보안상 위험할 수 있습니다. 더 짧은 기간(예: 7일)을 고려해보세요.

-			currentAccessTokenExpiration = 100L * 24 * 60 * 60 * 1000L; // 100일
+			currentAccessTokenExpiration = 7L * 24 * 60 * 60 * 1000L; // 7일
📝 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
currentAccessTokenExpiration = 100L * 24 * 60 * 60 * 1000L; // 100일
currentAccessTokenExpiration = 7L * 24 * 60 * 60 * 1000L; // 7일
🤖 Prompt for AI Agents
In
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/user/serivce/UserService.java
at line 78, the currentAccessTokenExpiration is set to 100 days, which is too
long for a SUPER_ADMIN token. Change the expiration time to a shorter duration
such as 7 days by adjusting the multiplier accordingly to reduce security risks.

@HyemIin HyemIin merged commit fe7a7cb into develop Jul 8, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant