-
Notifications
You must be signed in to change notification settings - Fork 0
Refactor: Auth 도메인 리팩토링 #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Caution Review failedThe pull request is closed. Walkthrough인증 모듈의 DTO/이벤트 패키지를 요청/응답/vo/event로 재구성하고 관련 import를 정리했으며, 이벤트 리스너의 트랜잭션 시점을 커밋 이후로 변경하고 Auth/OAuth 영역에 Reader/Policy/Resolver/UserInfoService 등 신규 서비스를 추가해 기존 서비스 로직을 이들로 위임하도록 리팩터링했다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant C as AuthController
participant S as AuthService
participant R as AuthReader
participant P as AuthPolicyService
participant E as AppEventPublisher
participant L as Email/Password Reset Listener
rect rgb(245,248,255)
note over C,S: 이메일 인증 코드 발송
C->>S: sendEmailVerificationCode(email)
S->>P: validateEmailDuplicate(email)
S->>P: validateVerificationCodeNotExists(email)
S->>E: publish EmailVerificationSendEvent
E-->>L: AFTER_COMMIT
L-->>L: 메일 전송 처리
S-->>C: 200 OK
end
rect rgb(245,255,245)
note over C,S: 비밀번호 재설정 요청
C->>S: requestPasswordReset(email)
S->>P: validatePasswordResetTokenNotExists(email)
S->>E: publish PasswordResetCreateEvent
E-->>L: AFTER_COMMIT
L-->>L: 메일 전송 처리
S-->>C: 200 OK
end
rect rgb(255,248,245)
note over C,S: 비밀번호 변경
C->>S: changePassword(authId, oldPw, newPw)
S->>R: findActiveAuthAccount(authId)
S->>P: validatePasswordMatch(oldPw, encodedPw)
S-->>C: 200 OK
end
sequenceDiagram
autonumber
participant C as OAuthController
participant S as OAuthService
participant R as OAuthReader
participant PR as OAuthProviderResolver
participant UI as OAuthUserInfoService
participant POL as OAuthPolicyService
participant JWT as JwtComponent
rect rgb(245,248,255)
note over C,S: OAuth 인증/연동 플로우
C->>S: authorize(provider, ...)
S->>PR: getProvider(provider)
S-->>C: AuthorizationRedirect (VO)
C->>S: callback(code, state, ...)
S->>UI: getOAuth2UserInfo(provider, code, verifier, request)
S->>R: findOAuthLinkByProviderOrThrow(...)
S->>POL: validateLinkNotExists(authId, provider, providerId) [연동 시]
S->>JWT: generateTokenPair(...)
S-->>C: TokenPair (VO)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (3)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/main/java/project/flipnote/auth/controller/OAuthController.java (2)
101-107: 액세스 토큰을 URL 쿼리로 노출하는 것은 심각한 보안 위험쿼리스트링은 서버/프록시 로그, 브라우저 히스토리, Referer 등에 남습니다. 액세스 토큰을 쿼리로 전달하지 마세요. 성공 리다이렉트는 토큰 없이 고정 경로로 보내고, 프론트는 리다이렉트 후 별도 엔드포인트로 AT를 교환(예: RT 쿠키 기반)하도록 전환을 권장합니다.
제안 수정:
@@ - TokenPair tokenPair = oAuthService.socialLogin(provider, code, codeVerifier, request); - location = buildLoginSuccessRedirectUri(tokenPair.accessToken()); - refreshTokenCookie = createRefreshTokenCookie(tokenPair.refreshToken()); + TokenPair tokenPair = oAuthService.socialLogin(provider, code, codeVerifier, request); + location = buildLoginSuccessRedirectUri(); + refreshTokenCookie = createRefreshTokenCookie(tokenPair.refreshToken()); @@ - private URI buildLoginSuccessRedirectUri(String accessToken) { - return UriComponentsBuilder - .fromUriString(clientProperties.buildUrl(ClientProperties.PathKey.SOCIAL_LOGIN_SUCCESS)) - .queryParam("accessToken", accessToken) - .build(true) - .toUri(); - } + private URI buildLoginSuccessRedirectUri() { + return UriComponentsBuilder + .fromUriString(clientProperties.buildUrl(ClientProperties.PathKey.SOCIAL_LOGIN_SUCCESS)) + .build(true) + .toUri(); + }비고: 필요 시 프론트는 리다이렉트 후 “/auth/token” 같은 엔드포인트를 호출하여 AT를 교환하도록 흐름을 맞추세요(서버는 RT HttpOnly 쿠키만 설정).
Also applies to: 146-151
126-133: 쿠키 maxAge 변환 시 오버플로 가능성
Math.toIntExact는 값이Integer.MAX_VALUE를 초과하면 예외를 던집니다. 설정값에 따라 런타임 오류가 될 수 있으니 안전 변환으로 교체하세요.private ResponseCookie createRefreshTokenCookie(String token) { - long expirationSeconds = jwtProperties.getRefreshTokenExpiration().toSeconds(); + long expirationSeconds = jwtProperties.getRefreshTokenExpiration().toSeconds(); + int maxAge = expirationSeconds > Integer.MAX_VALUE + ? Integer.MAX_VALUE + : (int) expirationSeconds; return cookieUtil.createCookie( JwtConstants.REFRESH_TOKEN, token, - Math.toIntExact(expirationSeconds) + maxAge ); }src/main/java/project/flipnote/auth/service/OAuthService.java (1)
47-66: 보안: 로그인 흐름에 state 미사용 — 즉시 수정 필요현재 state는 소셜 계정 연동에서만 생성/검증되고 일반 소셜 로그인은 state가 null로 처리되어 콜백에서 검증되지 않습니다(OAuthController.handleCallback가 state 유무로 분기, OAuthService.socialLogin 시 state 없음). 이로 인해 CSRF(응답 위조) 위험이 존재합니다.
수정(필수):
- src/main/java/project/flipnote/auth/service/OAuthService.java — getAuthorizationUri(...) : 로그인 케이스에도 항상 state 생성·저장하도록 변경(소셜 연동과 동일한 방식 또는 별도 네임스페이스 사용).
- src/main/java/project/flipnote/auth/controller/OAuthController.java — handleCallback(...) : 로그인 경로에도 state를 전달하도록 변경하고 socialLogin 호출부를 state 인자를 받도록 수정.
- src/main/java/project/flipnote/auth/service/OAuthService.java — socialLogin(...) : 시그니처에 state 추가하고, Redis에서 state 검증 후 즉시 삭제(검증 실패 시 예외 처리).
권고:
- state 토큰은 연동/로그인 네임스페이스 분리 및 만료설정 적용, 클라이언트가 state를 전달/보관하도록 프론트 수정, 관련 단위/통합 테스트 추가.
src/main/java/project/flipnote/auth/service/AuthService.java (1)
143-153: 비밀번호 재설정 시 기존 토큰 무효화 누락 (보안 이슈)비밀번호 재설정 후에도 기존 액세스/리프레시 토큰이 유효하게 남습니다.
changePassword에서는tokenVersionService.incrementTokenVersion으로 무효화하는 반면,resetPassword에는 동일 조치가 없습니다. 모든 세션 강제 로그아웃을 위해 토큰 버전을 증가시키세요.적용 diff:
@Transactional public void resetPassword(PasswordResetRequest req) { String token = req.token(); String email = authReader.findActivePasswordResetToken(token); String encodedPassword = passwordEncoder.encode(req.password()); userAuthRepository.updatePassword(email, encodedPassword); + // 새 비밀번호 적용 후 모든 기존 토큰 무효화 + UserAuth userAuth = authReader.findActiveAuthAccountByEmail(email); + tokenVersionService.incrementTokenVersion(userAuth.getId()); + passwordResetRedisRepository.deleteToken(email, token); }
🧹 Nitpick comments (15)
src/main/java/project/flipnote/like/service/LikePolicyService.java (2)
30-34: DB 유니크 제약에 맡기고 exists 선검사 제거 추천LikeService의 save 블록(라인 70–74)에서 DataIntegrityViolationException을 캐치해 LikeErrorCode.ALREADY_LIKED로 매핑하고 있으므로, LikePolicyService(src/main/java/project/flipnote/like/service/LikePolicyService.java 라인 30–34)의 existsBy... 선검사는 경쟁조건에서 완전하지 않습니다. 선검사는 빠른 실패용으로만 유지하거나 삭제하고 실제 중복 방지는 DB 유니크 제약 + 예외 매핑에 맡기세요.
19-28: 미지원 LikeTargetType에 대해 명시적 예외 처리 추가검증 결과 LikeTargetType에는 현재 CARD_SET만 정의되어 있으나, enum 확장 시 NOT_FOUND 대신 명시적 예외를 던지도록 default 분기 추가를 권장합니다.
public void validateTargetExists(LikeTargetType targetType, Long targetId) { boolean targetExists = false; switch (targetType) { case CARD_SET -> targetExists = cardSetService.existsById(targetId); + default -> throw new IllegalArgumentException("Unsupported LikeTargetType: " + targetType); } if (!targetExists) { throw new BizException(LikeErrorCode.LIKE_TARGET_NOT_FOUND); } }도메인 에러코드가 있다면 IllegalArgumentException 대신 교체하세요.
src/main/java/project/flipnote/like/repository/LikeRepository.java (1)
12-18: Like 엔티티: 유니크 제약 확인 — (target_type, user_id) 인덱스 추가 권장src/main/java/project/flipnote/like/entity/Like.java 에서 uniqueConstraints(uk_likes_targettype_targetid_userid)와 @Index(idx_likes_targettype_targetid_userid — target_type, target_id, user_id)가 선언되어 있습니다. findByTargetTypeAndUserId(Pageable) 성능을 위해 (target_type, user_id) 복합 인덱스(예: idx_likes_targettype_userid)를 추가하세요; (target_type, target_id)는 현재 3컬럼 인덱스의 접두사로 이미 커버됩니다.
src/main/java/project/flipnote/auth/model/request/UserRegisterRequest.java (1)
27-33: 전화번호 null 처리 또는 유효성 보강 필요
@ValidPhone가 null/blank를 보장하지 않으면getNormalizedPhone()에서 NPE 위험이 있습니다. 선택지 중 하나를 적용하세요.옵션 A) null‑safe 처리:
- public String getNormalizedPhone() { - return PhoneUtil.normalize(phone); - } + public String getNormalizedPhone() { + return phone == null ? null : PhoneUtil.normalize(phone); + }옵션 B) 입력 유효성 강화(필수 입력이라면):
- @ValidPhone + @NotBlank + @ValidPhone String phonesrc/main/java/project/flipnote/auth/service/OAuthPolicyService.java (1)
17-17: 인덱스 추가 권장 — oauth_link 테이블에 (auth_id, providerId) 복합 인덱스 생성현재 OAuthLink 엔티티에는 @Index(name = "idx_provider_provider_id", columnList = "provider, providerId")만 정의되어 있어, existsByUserAuth_IdAndProviderId(...) 쿼리(조회 조건: auth_id + providerId)에 맞는 복합 인덱스가 없습니다. src/main/java/project/flipnote/auth/entity/OAuthLink.java 파일에 (auth_id, providerId) 복합 인덱스를 @table(indexes=...)로 추가하거나 DB 마이그레이션으로 인덱스를 생성하세요.
src/main/java/project/flipnote/auth/service/OAuthReader.java (1)
12-16: Reader 계층은 읽기 전용 트랜잭션으로 고정하세요조회 전용 서비스에
@Transactional(readOnly = true)를 부여해 성능 최적화와 일관된 트랜잭션 정책을 보장하는 것을 권장합니다.@RequiredArgsConstructor -@Service +@Service +@org.springframework.transaction.annotation.Transactional(readOnly = true) public class OAuthReader {필요 시 import:
import org.springframework.transaction.annotation.Transactional;src/main/java/project/flipnote/auth/service/OAuthUserInfoService.java (1)
26-30: UserInfo 생성 시 providerName 정규화 일치
getProvider는 소문자 정규화로 조회하지만,createUserInfo에는 원문을 전달합니다. 내부 매핑이 케이스 민감하면 오류가 납니다. 동일한 정규화(예: Locale.ROOT 소문자)로 전달하는 것을 권장합니다.- return oAuthApiClient.createUserInfo(providerName, userInfoAttributes); + return oAuthApiClient.createUserInfo(providerName.toLowerCase(java.util.Locale.ROOT), userInfoAttributes);필요한 import:
import java.util.Locale;src/main/java/project/flipnote/auth/service/AuthReader.java (1)
14-16: Reader 클래스에 읽기 전용 트랜잭션 부여 권장조회만 수행하므로 클래스 레벨
@Transactional(readOnly = true)적용을 권장합니다.@RequiredArgsConstructor -@Service +@Service +@org.springframework.transaction.annotation.Transactional(readOnly = true) public class AuthReader {필요 시 import:
import org.springframework.transaction.annotation.Transactional;src/main/java/project/flipnote/auth/service/AuthPolicyService.java (2)
65-69: 메서드명 의미 불일치: 존재 확인이 아니라 블랙리스트 부재 확인입니다현재 로직은 블랙리스트에 존재하면 예외를 던지므로, 메서드명
validateRefreshTokenExists는 동작과 반대 의미입니다. 오해를 줄이기 위해 이름을validateRefreshTokenNotBlacklisted(또는validateRefreshTokenNotRevoked)로 변경하는 것을 권장합니다. 이 변경은 호출부(AuthService.refreshToken)도 함께 수정되어야 합니다.적용 diff:
- public void validateRefreshTokenExists(String refreshToken) { + public void validateRefreshTokenNotBlacklisted(String refreshToken) { if (tokenBlacklistRedisRepository.exist(refreshToken)) { throw new BizException(AuthErrorCode.INVALID_REFRESH_TOKEN); } }
53-57: 인증 코드 비교는 상수 시간 비교 사용 권장타이밍 기반 추측을 줄이려면
Objects.equals대신 상수 시간 비교를 사용하는 것이 더 안전합니다. (실무 리스크는 낮지만 인증/보안 코드에서는 습관화 권장)적용 diff:
- public void validateVerificationCode(String inputCode, String savedCode) { - if (!Objects.equals(inputCode, savedCode)) { - throw new BizException(AuthErrorCode.INVALID_VERIFICATION_CODE); - } - } + public void validateVerificationCode(String inputCode, String savedCode) { + if (!java.security.MessageDigest.isEqual( + inputCode.getBytes(java.nio.charset.StandardCharsets.UTF_8), + savedCode.getBytes(java.nio.charset.StandardCharsets.UTF_8))) { + throw new BizException(AuthErrorCode.INVALID_VERIFICATION_CODE); + } + }추가 import 없이 FQCN으로 처리해 import 충돌 리스크를 피했습니다.
src/main/java/project/flipnote/auth/service/AuthService.java (3)
117-126: 리프레시 토큰 갱신 흐름 정교화 + 메서드명 반영
- 블랙리스트 검증 메서드명을 정책 서비스 변경안에 맞춰 교체하세요.
- 토큰 파싱/검증(
extractUserAuthFromToken)을 먼저 수행하고, 만료시각 추출 및 블랙리스트 저장을 그 다음에 수행하는 편이 실패 시 낭비 저장을 줄이고 예외 흐름이 명확합니다.적용 diff:
public TokenPair refreshToken(String refreshToken) { - authPolicyService.validateRefreshTokenExists(refreshToken); - - long expirationMillis = jwtComponent.getExpirationMillis(refreshToken); - tokenBlacklistRedisRepository.save(refreshToken, expirationMillis); - - AuthPrinciple userAuth = jwtComponent.extractUserAuthFromToken(refreshToken); - - return jwtComponent.generateTokenPair(userAuth); + authPolicyService.validateRefreshTokenNotBlacklisted(refreshToken); + + // 유효성/서명 검증 및 사용자 정보 추출 + AuthPrinciple userAuth = jwtComponent.extractUserAuthFromToken(refreshToken); + + // 만료시각 추출 후 즉시 리프레시 토큰 폐기(블랙리스트) + long expirationMillis = jwtComponent.getExpirationMillis(refreshToken); + tokenBlacklistRedisRepository.save(refreshToken, expirationMillis); + + return jwtComponent.generateTokenPair(userAuth); }예외 처리 정책(예: 만료/서명 오류 시 에러 코드 매핑)이 있다면 전역 핸들러와 일치하는지 확인 바랍니다.
93-105: 인증번호 재발급 쿨다운/레이트 리밋 권장
validateVerificationCodeNotExists만으로는 동일인이 여러 이메일에 대해 잦은 발송을 시도할 수 있습니다. IP/이메일 기준 쿨다운(예: N분당 1회) 또는 시도 횟수 제한을 저장소 레이어에서 지원하는 것을 권장합니다.
172-177: 소셜 연동 해제 시 접근성 상실 방지 체크유저가 마지막 로그인 수단(비밀번호 미설정 + 단일 소셜 계정)인 상태에서 삭제하면 계정 접근을 상실합니다. 최소 한 개 로그인 수단이 남는지 검사하거나, 비밀번호 설정 유도/차단 로직을 추가하세요.
src/main/java/project/flipnote/auth/controller/docs/AuthControllerDocs.java (2)
36-38: refreshToken 파라미터 전달 방식 명시 필요문서 인터페이스 기준으로는 리프레시 토큰이 Header/Body/Query 중 어디에서 오는지 불명확합니다. Swagger 스펙에 @parameter(in = HEADER, name = "refresh-token") 또는 실제 컨트롤러 메서드 파라미터에 명시적 어노테이션을 추가해 주세요.
예시:
@Operation(summary = "토큰 갱신") ResponseEntity<UserLoginResponse> refreshToken( @Parameter(in = ParameterIn.HEADER, name = "refresh-token") String refreshToken);
27-29: 로그아웃도 리프레시 토큰 전달 위치를 문서에 명시하세요현 시그니처만으로는 클라이언트가 어떤 위치에 리프레시 토큰을 넣어야 하는지 알기 어렵습니다. 위와 동일하게 @parameter로 표현하는 것을 권장합니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (29)
src/main/java/project/flipnote/auth/controller/AuthController.java(1 hunks)src/main/java/project/flipnote/auth/controller/OAuthController.java(1 hunks)src/main/java/project/flipnote/auth/controller/docs/AuthControllerDocs.java(1 hunks)src/main/java/project/flipnote/auth/listener/EmailVerificationEventListener.java(2 hunks)src/main/java/project/flipnote/auth/listener/PasswordResetCreateEventListener.java(2 hunks)src/main/java/project/flipnote/auth/model/event/EmailVerificationSendEvent.java(1 hunks)src/main/java/project/flipnote/auth/model/event/PasswordResetCreateEvent.java(1 hunks)src/main/java/project/flipnote/auth/model/request/ChangePasswordRequest.java(1 hunks)src/main/java/project/flipnote/auth/model/request/EmailVerificationRequest.java(1 hunks)src/main/java/project/flipnote/auth/model/request/EmailVerifyRequest.java(1 hunks)src/main/java/project/flipnote/auth/model/request/PasswordResetCreateRequest.java(1 hunks)src/main/java/project/flipnote/auth/model/request/PasswordResetRequest.java(1 hunks)src/main/java/project/flipnote/auth/model/request/UserLoginRequest.java(1 hunks)src/main/java/project/flipnote/auth/model/request/UserRegisterRequest.java(1 hunks)src/main/java/project/flipnote/auth/model/response/UserLoginResponse.java(1 hunks)src/main/java/project/flipnote/auth/model/response/UserRegisterResponse.java(1 hunks)src/main/java/project/flipnote/auth/model/vo/AuthorizationRedirect.java(1 hunks)src/main/java/project/flipnote/auth/model/vo/TokenPair.java(1 hunks)src/main/java/project/flipnote/auth/service/AuthPolicyService.java(1 hunks)src/main/java/project/flipnote/auth/service/AuthReader.java(1 hunks)src/main/java/project/flipnote/auth/service/AuthService.java(8 hunks)src/main/java/project/flipnote/auth/service/OAuthPolicyService.java(1 hunks)src/main/java/project/flipnote/auth/service/OAuthProviderResolver.java(1 hunks)src/main/java/project/flipnote/auth/service/OAuthReader.java(1 hunks)src/main/java/project/flipnote/auth/service/OAuthService.java(3 hunks)src/main/java/project/flipnote/auth/service/OAuthUserInfoService.java(1 hunks)src/main/java/project/flipnote/common/security/jwt/JwtComponent.java(1 hunks)src/main/java/project/flipnote/like/repository/LikeRepository.java(1 hunks)src/main/java/project/flipnote/like/service/LikePolicyService.java(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
src/main/java/project/flipnote/auth/listener/EmailVerificationEventListener.java (4)
src/main/java/project/flipnote/common/config/AsyncConfig.java (1)
RequiredArgsConstructor(12-29)src/main/java/project/flipnote/auth/listener/PasswordResetCreateEventListener.java (1)
Slf4j(18-40)src/main/java/project/flipnote/auth/service/AuthService.java (1)
Slf4j(42-178)src/main/java/project/flipnote/infra/email/ResendEmailService.java (1)
Slf4j(16-93)
src/main/java/project/flipnote/auth/service/AuthReader.java (1)
src/main/java/project/flipnote/auth/service/AuthPolicyService.java (1)
RequiredArgsConstructor(17-76)
src/main/java/project/flipnote/auth/service/OAuthReader.java (1)
src/main/java/project/flipnote/auth/service/OAuthPolicyService.java (1)
RequiredArgsConstructor(10-21)
src/main/java/project/flipnote/auth/service/OAuthProviderResolver.java (2)
src/main/java/project/flipnote/auth/service/OAuthUserInfoService.java (1)
RequiredArgsConstructor(13-31)src/main/java/project/flipnote/auth/service/OAuthService.java (1)
Slf4j(29-109)
src/main/java/project/flipnote/auth/service/AuthPolicyService.java (1)
src/main/java/project/flipnote/auth/service/AuthReader.java (1)
RequiredArgsConstructor(14-41)
src/main/java/project/flipnote/auth/service/OAuthUserInfoService.java (2)
src/main/java/project/flipnote/auth/service/OAuthPolicyService.java (1)
RequiredArgsConstructor(10-21)src/main/java/project/flipnote/auth/service/OAuthReader.java (1)
RequiredArgsConstructor(12-28)
src/main/java/project/flipnote/auth/service/OAuthPolicyService.java (1)
src/main/java/project/flipnote/auth/service/OAuthReader.java (1)
RequiredArgsConstructor(12-28)
src/main/java/project/flipnote/auth/listener/PasswordResetCreateEventListener.java (3)
src/main/java/project/flipnote/auth/listener/EmailVerificationEventListener.java (1)
Slf4j(18-40)src/main/java/project/flipnote/auth/service/AuthService.java (1)
Slf4j(42-178)src/main/java/project/flipnote/infra/email/ResendEmailService.java (1)
Slf4j(16-93)
🔇 Additional comments (24)
src/main/java/project/flipnote/like/repository/LikeRepository.java (1)
10-10: 임포트 정리 LGTM기능 변화 없이 임포트 순서만 조정된 것으로 보입니다.
src/main/java/project/flipnote/like/service/LikePolicyService.java (1)
8-8: 임포트 정리 LGTM기능 변화 없이 임포트 위치만 조정되었습니다.
src/main/java/project/flipnote/auth/model/request/UserLoginRequest.java (1)
7-15: 패키지 구조 정리는 적절합니다.구조 재정렬에 따른 import 변경만 있으며 기능 영향 없음으로 보입니다.
src/main/java/project/flipnote/auth/model/request/PasswordResetCreateRequest.java (1)
6-10: LGTM패키지 이동 외 변경 없음. 요청 스키마도 타당합니다.
src/main/java/project/flipnote/auth/model/vo/TokenPair.java (1)
1-11: VO 패키지로의 이동 적절의미상 VO로의 이동이 일관성을 높입니다. 기능 영향 없음.
src/main/java/project/flipnote/auth/model/request/EmailVerifyRequest.java (1)
8-17: LGTM고정 길이 코드 검증과 이메일 검증 조합 적절합니다.
src/main/java/project/flipnote/auth/model/vo/AuthorizationRedirect.java (1)
5-9: LGTMVO로의 리팩토링 방향성에 부합하며 변경 위험도 낮습니다.
src/main/java/project/flipnote/auth/model/request/PasswordResetRequest.java (1)
6-14: LGTM패키지 이동 외 변경 없음. 스키마도 타당합니다.
src/main/java/project/flipnote/auth/model/event/PasswordResetCreateEvent.java (1)
1-1: 패키지 구조 리팩토링 승인이벤트 모델을
model.event패키지로 이동한 것은 도메인 구조를 더 명확하게 하는 좋은 변경입니다.src/main/java/project/flipnote/auth/model/request/EmailVerificationRequest.java (1)
1-1: 패키지 구조 리팩토링 승인Request 모델을
model.request패키지로 이동한 것은 코드 구조를 더 체계적으로 만드는 좋은 변경입니다.src/main/java/project/flipnote/auth/model/request/ChangePasswordRequest.java (1)
1-1: 패키지 구조 리팩토링 승인Request 모델을
model.request패키지로 이동한 것은 일관성 있는 패키지 구조를 만드는 좋은 변경입니다.src/main/java/project/flipnote/common/security/jwt/JwtComponent.java (1)
17-17: 리팩토링된 패키지 경로 사용 확인TokenPair가
model.vo패키지로 이동된 새로운 경로를 올바르게 참조하고 있습니다.src/main/java/project/flipnote/auth/listener/EmailVerificationEventListener.java (1)
8-9: 트랜잭션 이벤트 리스너 개선 승인
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)사용은 이메일 발송이 트랜잭션 커밋 이후에 실행되도록 보장하여 데이터 정합성을 개선합니다. 패키지 경로 업데이트도 적절합니다.Also applies to: 14-14, 31-31
src/main/java/project/flipnote/auth/model/response/UserLoginResponse.java (1)
1-1: 패키지 구조 리팩토링 승인Response 모델을
model.response패키지로 이동한 것은 패키지 구조를 더 명확하게 만드는 좋은 변경입니다.src/main/java/project/flipnote/auth/service/OAuthPolicyService.java (1)
16-20: OAuth 링크 검증 로직 구현 승인OAuth 연동 중복을 방지하는 정책 서비스가 적절하게 구현되었습니다. 명확한 예외 처리와 함께 단일 책임 원칙을 잘 따르고 있습니다.
src/main/java/project/flipnote/auth/controller/AuthController.java (1)
21-30: 패키지 구조 리팩토링 승인모든 모델 클래스들이 새로운 패키지 구조(
model.request,model.response,model.vo)로 올바르게 이동되어 import 경로가 업데이트되었습니다. 기능적 변경 없이 구조만 개선된 좋은 리팩토링입니다.src/main/java/project/flipnote/auth/model/response/UserRegisterResponse.java (1)
1-10: LGTM – 패키지 리팩토링 및 레코드 유지 적절합니다.변경된 FQCN 반영만으로 충분하며, 정적 팩터리 메서드도 일관성 있게 유지됐습니다.
src/main/java/project/flipnote/auth/model/event/EmailVerificationSendEvent.java (1)
1-7: LGTM – 이벤트 타입의 패키지 이동 타당합니다.이동 외 변경 없음. 리스너들의 import 경로만 맞춰주면 됩니다.
src/main/java/project/flipnote/auth/service/AuthReader.java (1)
22-30: INVALID_CREDENTIALS 사용 일관성 OK — 이메일 정규화(소문자/트림) 확인 권장
- INVALID_CREDENTIALS로 사용자 존재/상태 비공개 처리 일관성 좋음.
- 레포지토리 조회는 findByEmailAndStatus / existsByEmail 등 프로퍼티 기반 메서드(쿼리 내 lower/trim 없음)로 정의되어 있습니다. 저장 시 이메일을 소문자화/트림하거나 DB 컬레이션(case-insensitive) 설정을 보장하세요.
위치: src/main/java/project/flipnote/auth/repository/UserAuthRepository.java (lines 15, 17), src/main/java/project/flipnote/auth/service/AuthReader.java (line 23)
src/main/java/project/flipnote/auth/service/OAuthService.java (1)
52-59: null state 처리 확인 — 안전합니다
OAuthApiClient.buildAuthorizeUri(...)에서 state가 null일 경우 queryParam을 추가하지 않으므로 NPE나 잘못된 URL 생성 위험이 없습니다 (src/main/java/project/flipnote/infra/oauth/OAuthApiClient.java).src/main/java/project/flipnote/auth/service/AuthPolicyService.java (2)
17-19: 정책(Policy) 분리 방향 좋습니다검증 규칙을 서비스에서 분리해 재사용·테스트 용이성이 향상됐습니다.
41-45: 확인: user_auth.email에 유니크 제약이 선언되어 있습니다
src/main/java/project/flipnote/auth/entity/UserAuth.java의 email 필드에 @column(unique = true, nullable = false)로 명시되어 있습니다.src/main/java/project/flipnote/auth/service/AuthService.java (2)
63-83: 이메일 유니크 제약 관련 중복 코멘트회원가입 경합 방지는 DB 유니크 인덱스 확인 코멘트를 참조해 주세요.
60-62: Reader/Policy 의존성 주입으로 역할 분리가 명확해졌습니다조회(AuthReader)와 정책(AuthPolicyService) 분리가 응집도를 높입니다. 방향성 좋습니다.
src/main/java/project/flipnote/auth/listener/PasswordResetCreateEventListener.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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
🧹 Nitpick comments (2)
src/main/java/project/flipnote/auth/service/OAuthService.java (2)
87-87: DB 유니크 제약과 경쟁 조건 대응정책 체크 이후에도 경쟁 조건으로 동일 링크가 거의 동시에 저장될 수 있습니다. DB에 (provider, providerId) 유니크 제약을 두고 DataIntegrityViolationException을 도메인 에러로 변환하는 처리(트랜잭션 경계 내)도 추가를 권장합니다.
99-107: 읽기 전용 트랜잭션 컨텍스트에서의 Redis 쓰기 — 경계 명시 권장클래스 레벨 readOnly=true 하에서 Redis save가 수행됩니다. JPA에는 영향 없으나 의미적 혼란을 줄 수 있습니다. 본 메서드는 트랜잭션을 사용하지 않거나, propagation=NOT_SUPPORTED/SUPPORTS 등으로 명시하는 것을 고려해주세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/main/java/project/flipnote/auth/listener/PasswordResetCreateEventListener.java(2 hunks)src/main/java/project/flipnote/auth/service/OAuthProviderResolver.java(1 hunks)src/main/java/project/flipnote/auth/service/OAuthService.java(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/main/java/project/flipnote/auth/listener/PasswordResetCreateEventListener.java
- src/main/java/project/flipnote/auth/service/OAuthProviderResolver.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (6)
src/main/java/project/flipnote/auth/service/OAuthService.java (6)
14-15: VO로의 타입 이동 적절AuthorizationRedirect/TokenPair를 VO로 분리해 가져오는 변경은 도메인 경계를 명확히 합니다. API 표면의 의도가 더 분명해졌습니다.
37-43: 협력자 분리/위임 리팩터링 방향 좋습니다Reader/Resolver/UserInfoService/PolicyService로 책임을 위임한 구성이 바람직합니다. 필드 final 주입도 적절합니다.
50-50: providerName 정규화/검증 실패 시 예외 매핑을 명확히Resolver 사용은 좋습니다. 단, 미지원 providerName에 대한 예외가 컨트롤러 레벨에서 일관된 에러로 매핑되는지 확인해주세요(예: 400 Bad Request, 도메인 에러 코드).
57-63: PKCE code_verifier 쿠키의 보안 속성 확인ResponseCookie 생성 시 HttpOnly, Secure, SameSite 설정이 적절히 적용되는지 CookieUtil에서 확인 바랍니다. code_verifier는 JS에서 접근 불가(HttpOnly)여야 하며, 크로스 사이트 요청이 필요한 경우 SameSite=None+Secure 조합이 필요합니다.
74-76: state 유효성 및 TTL 보장 확인
- state가 null/만료된 경우 findAuthIdByTokenOrThrow가 명확한 도메인 예외를 던지는지 확인해주세요.
- Redis에 저장된 state의 TTL이 OAuth 허용 시간(예: 수 분)과 일치하는지도 점검이 필요합니다.
93-95: 저장/조회 시 동일 provider 소스 사용으로 정합성 확보(LGTM)저장과 동일하게 userInfo.getProvider()로 조회하도록 수정된 점 좋습니다. 이전 리뷰 지적 사항이 해결되었습니다.
📝 변경 내용
✅ 체크리스트
💬 기타 참고 사항
Summary by CodeRabbit