feat(User,Token): 리프레시 토큰 중복 저장 로직 수정#70
Conversation
Walkthrough이번 변경에서는 관리자 및 사용자 API에서 리프레시 토큰의 발급 및 갱신 방식을 개선하였으며, 토큰 엔티티의 일대일 관계를 데이터베이스 수준에서 보장하도록 수정했습니다. 또한, 토큰 쿠키를 HTTP 응답 헤더에 안전하게 설정하고, 관련 서비스 및 컨트롤러의 메서드 시그니처와 동작을 일관성 있게 변경했습니다. Changes
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 쿠키
Suggested reviewers
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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); |
There was a problem hiding this comment.
🛠️ 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일 |
There was a problem hiding this comment.
🛠️ 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.
| 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.
작업 요약
Issue Link
문제점 및 어려움
해결 방안
Reference
Summary by CodeRabbit
신규 기능
버그 수정
리팩토링
보안