-
Notifications
You must be signed in to change notification settings - Fork 0
Feat: [FN-4] 회원가입 #5
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
Walkthrough이 변경사항은 이메일 인증 기능, AES 암호화, 비밀번호/휴대전화 유효성 검사, Redis 연동, Resend 이메일 API 연동, 비동기 및 재시도 처리, 신규 DTO 및 테스트 코드 추가 등 인증 및 회원가입 전반을 대대적으로 확장합니다. 환경설정, 시큐리티, 엔티티, 컨트롤러, 서비스, 예외, 테스트 코드 등 프로젝트 전반에 걸쳐 파일이 추가·수정·삭제되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant AuthController
participant AuthService
participant EmailVerificationRedisRepository
participant ApplicationEventPublisher
participant EmailVerificationEventListener
participant EmailService
Client->>AuthController: POST /v1/auth/email (이메일 인증코드 요청)
AuthController->>AuthService: sendEmailVerificationCode(req)
AuthService->>EmailVerificationRedisRepository: 이메일 중복/코드 발급 여부 확인 및 저장
AuthService->>ApplicationEventPublisher: EmailVerificationSendEvent 발행
ApplicationEventPublisher->>EmailVerificationEventListener: handleEmailVerificationSendEvent
EmailVerificationEventListener->>EmailService: sendEmailVerificationCode(to, code, ttl)
EmailService-->>EmailVerificationEventListener: 이메일 발송 (Resend API)
Client->>AuthController: POST /v1/auth/email/confirm (인증코드 확인)
AuthController->>AuthService: confirmEmailVerificationCode(req)
AuthService->>EmailVerificationRedisRepository: 인증코드 조회 및 검증, 코드 삭제, 이메일 verified 마킹
sequenceDiagram
participant Client
participant UserController
participant UserService
participant AuthService
participant UserRepository
Client->>UserController: POST /v1/users (회원가입)
UserController->>UserService: register(req)
UserService->>UserRepository: 이메일/휴대전화 중복 체크
UserService->>AuthService: validateEmail(email)
UserService->>UserRepository: User 저장 (휴대전화 AES 암호화)
UserService->>AuthService: deleteVerifiedEmail(email)
UserService-->>UserController: UserRegisterResponse 반환
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches
🪧 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 12
🔭 Outside diff range comments (1)
src/main/java/project/flipnote/user/service/UserService.java (1)
52-54: 전화번호 검증 메서드도 정리된 전화번호를 사용해야 합니다중복 검증 시 정리된 전화번호를 사용하도록 수정이 필요합니다.
private void validatePhoneDuplicate(String phone) { - if (userRepository.existsByPhone(phone)) { + String cleanedPhone = phone == null ? null : phone.replaceAll("-", ""); + if (userRepository.existsByPhone(cleanedPhone)) { throw new BizException(UserErrorCode.DUPLICATE_PHONE); } }
🧹 Nitpick comments (14)
src/main/java/project/flipnote/infra/email/EmailService.java (1)
3-6: 인터페이스 문서화 개선 제안이메일 서비스 인터페이스가 잘 설계되었습니다. 다음과 같은 개선을 고려해보세요:
+/** + * 이메일 서비스 인터페이스 + */ public interface EmailService { + /** + * 이메일 인증 코드를 전송합니다. + * @param to 수신자 이메일 주소 + * @param code 인증 코드 + * @param ttl 인증 코드 유효시간 (초) + * @throws EmailSendException 이메일 전송 실패 시 + */ void sendEmailVerificationCode(String to, String code, int ttl); }docker-compose.local.yml (1)
1-17: Docker Compose 설정 개선 제안기본적인 설정은 잘 구성되어 있습니다. 다음과 같은 개선을 고려해보세요:
- 버전 고정:
latest태그 대신 특정 버전 사용- 데이터 지속성: 볼륨 마운트 추가
- 보안: 환경 변수 외부화
services: flipnote-mysql: - image: mysql:latest + image: mysql:8.0 container_name: flipnote-mysql ports: - "3306:3306" + volumes: + - flipnote-mysql-data:/var/lib/mysql environment: MYSQL_USERNAME: root MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: flipnote TZ: Asia/Seoul flipnote-redis: - image: redis:latest + image: redis:7-alpine container_name: flipnote-redis ports: - "6379:6379" + volumes: + - flipnote-redis-data:/data + +volumes: + flipnote-mysql-data: + flipnote-redis-data:src/main/java/project/flipnote/common/exception/EmailSendException.java (1)
3-7: 예외 클래스 구현 승인 및 확장 제안예외 클래스가 올바르게 구현되어 있습니다. 더 유연한 사용을 위해 추가 생성자를 고려해보세요:
public class EmailSendException extends RuntimeException { + public EmailSendException(String message) { + super(message); + } + + public EmailSendException(String message, Throwable cause) { + super(message, cause); + } + public EmailSendException(Throwable cause) { super(cause); } }src/main/java/project/flipnote/auth/event/EmailVerificationSendEvent.java (1)
4-5: 필드 검증 및 문서화를 고려해보세요.이메일 주소와 인증 코드에 대한 기본적인 검증이나 JavaDoc 문서화를 추가하는 것을 고려해보세요.
+/** + * 이메일 인증 코드 발송 이벤트 + * @param to 수신자 이메일 주소 + * @param code 인증 코드 + */ public record EmailVerificationSendEvent( + @Email @NotBlank String to, + @NotBlank String code ) {src/main/java/project/flipnote/common/validator/PhoneConstraintValidator.java (1)
11-11: 성능 최적화를 위해 Pattern 미리 컴파일을 고려해보세요.PasswordConstraintValidator처럼 Pattern을 미리 컴파일하여 성능을 개선할 수 있습니다.
+ private static final Pattern PHONE_PATTERN = Pattern.compile("^010-\\d{4}-\\d{4}$"); - private static final String PHONE_PATTERN = "^010-\\d{4}-\\d{4}$";그리고
isValid메소드에서:- return phone.matches(PHONE_PATTERN); + return PHONE_PATTERN.matcher(phone).matches();src/test/java/project/flipnote/common/validator/PasswordConstraintValidatorTest.java (1)
22-28: 유효한 비밀번호 테스트 케이스 추가 확인테스트 케이스가 잘 구성되어 있습니다. 허용되는 특수문자 목록을 주석으로 명시하면 테스트의 가독성이 더 좋아질 것입니다.
@Test @DisplayName("유효한 비밀번호는 true를 반환한다") void validPassword() { + // 허용되는 특수문자: ! @ # $ ^ & * ( ) - _ + = [ ] { } | \ : ; " ' < > , . / assertThat(validator.isValid("Abc12345!", context)).isTrue(); assertThat(validator.isValid("A1b2c3d4@", context)).isTrue(); assertThat(validator.isValid("aB1!abcd", context)).isTrue(); }src/main/java/project/flipnote/infra/config/ResendProperties.java (1)
9-16: 설정 프로퍼티 클래스가 올바르게 구현되었습니다.Spring Boot ConfigurationProperties 패턴을 올바르게 따르고 있으며, 기존 AsyncProperties와 일관된 구조입니다.
API 키는 민감 정보이므로 프로덕션 환경에서는 환경 변수나 암호화된 설정 파일로 관리하는 것을 권장합니다.
src/main/java/project/flipnote/auth/repository/EmailVerificationRedisRepository.java (2)
49-49: 매직 스트링 대신 상수를 사용하세요"1"과 같은 매직 스트링보다는 의미 있는 상수를 사용하는 것이 좋습니다.
+private static final String VERIFIED_MARKER = "verified"; public void markAsVerified(String email) { String key = AuthRedisKey.EMAIL_VERIFIED.key(email); Duration ttl = AuthRedisKey.EMAIL_VERIFIED.getTtl(); - emailRedisTemplate.opsForValue().set(key, "1", ttl); + emailRedisTemplate.opsForValue().set(key, VERIFIED_MARKER, ttl); }
25-29: hasKey() 결과에 대한 null 안전성 개선
hasKey()메서드는 null을 반환할 수 있으므로 Boolean.TRUE.equals()를 사용하는 것이 더 안전합니다.public boolean existCode(String email) { String key = AuthRedisKey.EMAIL_CODE.key(email); - return emailRedisTemplate.hasKey(key); + return Boolean.TRUE.equals(emailRedisTemplate.hasKey(key)); } public boolean isVerified(String email) { String key = AuthRedisKey.EMAIL_VERIFIED.key(email); - return emailRedisTemplate.hasKey(key); + return Boolean.TRUE.equals(emailRedisTemplate.hasKey(key)); }Also applies to: 52-56
src/main/java/project/flipnote/auth/service/AuthService.java (2)
89-93: 인증 코드 생성 로직 개선 필요현재 구현은 큰 length 값에서 정수 오버플로우가 발생할 수 있습니다. 또한 더 간단한 방법으로 구현할 수 있습니다.
private String generateVerificationCode(int length) { - int origin = (int)Math.pow(10, length - 1); - int bound = (int)Math.pow(10, length); - return String.valueOf(random.nextInt(origin, bound)); + if (length <= 0 || length > 9) { + throw new IllegalArgumentException("Code length must be between 1 and 9"); + } + StringBuilder code = new StringBuilder(length); + for (int i = 0; i < length; i++) { + code.append(RANDOM.nextInt(10)); + } + // 첫 자리가 0이 아니도록 보장 + if (code.charAt(0) == '0') { + code.setCharAt(0, (char)('1' + RANDOM.nextInt(9))); + } + return code.toString(); }
71-71: 보안: 타이밍 공격 방지를 위한 안전한 문자열 비교인증 코드 비교 시 타이밍 공격을 방지하기 위해 상수 시간 비교를 사용하는 것이 좋습니다.
+import java.security.MessageDigest; + -if (!Objects.equals(req.code(), code)) { +if (!MessageDigest.isEqual(req.code().getBytes(), code.getBytes())) { throw new BizException(AuthErrorCode.INVALID_VERIFICATION_CODE); }src/test/java/project/flipnote/user/service/UserServiceTest.java (3)
3-4: 정적 임포트 정리 필요AssertionsForClassTypes와 Assertions를 모두 임포트하고 있습니다. 하나로 통일하는 것이 좋습니다.
-import static org.assertj.core.api.AssertionsForClassTypes.*; -import static org.junit.jupiter.api.Assertions.*; +import static org.assertj.core.api.Assertions.*;
66-68: 테스트 데이터 상수 추출 제안중복되는 테스트 데이터를 상수로 추출하면 유지보수가 더 쉬워집니다.
클래스 상단에 다음 상수들을 추가하세요:
private static final String TEST_EMAIL = "test@test.com"; private static final String TEST_PASSWORD = "testPass"; private static final String TEST_NAME = "테스트"; private static final String TEST_NICKNAME = "테스트"; private static final String TEST_PHONE = "010-1234-5678";Also applies to: 86-88, 102-104, 118-120
75-81: 테스트 개선: 실제 인자 검증
any(String.class)대신 실제 이메일 값을 검증하는 것이 더 정확한 테스트입니다.-verify(authService, times(1)).validateEmail(any(String.class)); -verify(authService, times(1)).deleteVerifiedEmail(any(String.class)); +verify(authService, times(1)).validateEmail(req.email()); +verify(authService, times(1)).deleteVerifiedEmail(req.email());
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (40)
build.gradle(1 hunks)docker-compose.local.yml(1 hunks)src/main/java/project/flipnote/auth/constants/AuthRedisKey.java(1 hunks)src/main/java/project/flipnote/auth/constants/VerificationConstants.java(1 hunks)src/main/java/project/flipnote/auth/controller/AuthController.java(2 hunks)src/main/java/project/flipnote/auth/event/EmailVerificationSendEvent.java(1 hunks)src/main/java/project/flipnote/auth/exception/AuthErrorCode.java(1 hunks)src/main/java/project/flipnote/auth/listener/EmailVerificationEventListener.java(1 hunks)src/main/java/project/flipnote/auth/model/EmailVerificationConfirmDto.java(1 hunks)src/main/java/project/flipnote/auth/model/EmailVerificationDto.java(1 hunks)src/main/java/project/flipnote/auth/model/UserLoginDto.java(1 hunks)src/main/java/project/flipnote/auth/repository/EmailVerificationRedisRepository.java(1 hunks)src/main/java/project/flipnote/auth/service/AuthService.java(3 hunks)src/main/java/project/flipnote/common/annotation/ValidPassword.java(1 hunks)src/main/java/project/flipnote/common/annotation/ValidPhone.java(1 hunks)src/main/java/project/flipnote/common/config/AsyncConfig.java(1 hunks)src/main/java/project/flipnote/common/config/AsyncProperties.java(1 hunks)src/main/java/project/flipnote/common/config/RedisConfig.java(1 hunks)src/main/java/project/flipnote/common/constants/RedisKeys.java(1 hunks)src/main/java/project/flipnote/common/exception/EmailSendException.java(1 hunks)src/main/java/project/flipnote/common/exception/GlobalExceptionHandler.java(1 hunks)src/main/java/project/flipnote/common/response/ApiResponse.java(1 hunks)src/main/java/project/flipnote/common/security/config/SecurityConfig.java(1 hunks)src/main/java/project/flipnote/common/security/filter/ExceptionHandlerFilter.java(1 hunks)src/main/java/project/flipnote/common/util/CookieUtil.java(1 hunks)src/main/java/project/flipnote/common/validator/PasswordConstraintValidator.java(1 hunks)src/main/java/project/flipnote/common/validator/PhoneConstraintValidator.java(1 hunks)src/main/java/project/flipnote/infra/config/ResendConfig.java(1 hunks)src/main/java/project/flipnote/infra/config/ResendProperties.java(1 hunks)src/main/java/project/flipnote/infra/email/EmailService.java(1 hunks)src/main/java/project/flipnote/infra/email/ResendEmailService.java(1 hunks)src/main/java/project/flipnote/user/model/UserRegisterDto.java(1 hunks)src/main/java/project/flipnote/user/service/UserService.java(2 hunks)src/main/resources/application-local.yml(1 hunks)src/main/resources/application-test.yml(1 hunks)src/main/resources/application.yml(2 hunks)src/main/resources/templates/email/email-verification.html(1 hunks)src/test/java/project/flipnote/common/validator/PasswordConstraintValidatorTest.java(1 hunks)src/test/java/project/flipnote/common/validator/PhoneConstraintValidatorTest.java(1 hunks)src/test/java/project/flipnote/user/service/UserServiceTest.java(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (10)
src/main/java/project/flipnote/common/config/AsyncProperties.java (1)
src/main/java/project/flipnote/infra/config/ResendProperties.java (1)
Getter(9-16)
src/main/java/project/flipnote/common/annotation/ValidPhone.java (1)
src/main/java/project/flipnote/common/validator/PhoneConstraintValidator.java (1)
PhoneConstraintValidator(9-21)
src/main/java/project/flipnote/common/annotation/ValidPassword.java (1)
src/main/java/project/flipnote/common/validator/PasswordConstraintValidator.java (1)
PasswordConstraintValidator(10-24)
src/main/java/project/flipnote/infra/email/ResendEmailService.java (4)
src/main/java/project/flipnote/auth/service/AuthService.java (1)
RequiredArgsConstructor(24-94)src/main/java/project/flipnote/infra/config/ResendConfig.java (1)
RequiredArgsConstructor(10-20)src/main/java/project/flipnote/auth/listener/EmailVerificationEventListener.java (1)
Slf4j(17-38)src/main/java/project/flipnote/common/exception/EmailSendException.java (1)
EmailSendException(3-7)
src/main/java/project/flipnote/infra/config/ResendProperties.java (1)
src/main/java/project/flipnote/common/config/AsyncProperties.java (1)
Getter(9-17)
src/main/java/project/flipnote/auth/controller/AuthController.java (2)
src/main/java/project/flipnote/auth/model/EmailVerificationConfirmDto.java (1)
EmailVerificationConfirmDto(8-21)src/main/java/project/flipnote/auth/model/EmailVerificationDto.java (1)
EmailVerificationDto(6-14)
src/main/java/project/flipnote/common/config/AsyncConfig.java (5)
src/main/java/project/flipnote/common/config/RedisConfig.java (1)
Configuration(9-20)src/main/java/project/flipnote/auth/service/AuthService.java (1)
RequiredArgsConstructor(24-94)src/main/java/project/flipnote/auth/repository/EmailVerificationRedisRepository.java (1)
RequiredArgsConstructor(12-63)src/main/java/project/flipnote/common/security/config/SecurityConfig.java (1)
RequiredArgsConstructor(33-106)src/main/java/project/flipnote/infra/config/ResendConfig.java (1)
RequiredArgsConstructor(10-20)
src/main/java/project/flipnote/auth/listener/EmailVerificationEventListener.java (4)
src/main/java/project/flipnote/auth/service/AuthService.java (1)
RequiredArgsConstructor(24-94)src/main/java/project/flipnote/common/config/AsyncConfig.java (1)
RequiredArgsConstructor(12-29)src/main/java/project/flipnote/infra/email/ResendEmailService.java (1)
Slf4j(16-47)src/main/java/project/flipnote/common/exception/EmailSendException.java (1)
EmailSendException(3-7)
src/main/java/project/flipnote/user/service/UserService.java (1)
src/main/java/project/flipnote/user/model/UserRegisterDto.java (1)
UserRegisterDto(9-47)
src/test/java/project/flipnote/user/service/UserServiceTest.java (1)
src/main/java/project/flipnote/user/model/UserRegisterDto.java (1)
UserRegisterDto(9-47)
🪛 Gitleaks (8.26.0)
src/main/resources/application-local.yml
23-23: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🔇 Additional comments (43)
src/main/java/project/flipnote/common/security/filter/ExceptionHandlerFilter.java (1)
44-44: 파일 끝 개행 문자 추가 승인파일 끝에 개행 문자를 추가하는 것은 좋은 코딩 관례입니다.
src/main/java/project/flipnote/common/response/ApiResponse.java (1)
11-11: Import 문 순서 정리 승인프로젝트 내부 import를 외부 라이브러리 import 아래에 배치하는 것은 좋은 코딩 관례입니다.
src/main/java/project/flipnote/auth/event/EmailVerificationSendEvent.java (1)
3-7: 깔끔한 이벤트 레코드 구현입니다.이메일 인증 이벤트를 위한 간단하고 명확한 레코드 구현입니다. 불변성과 간결성을 잘 유지했습니다.
src/main/resources/application-test.yml (2)
18-21: Redis 설정이 적절하게 추가되었습니다.테스트 환경에서 사용할 Redis 설정이 올바르게 구성되어 있습니다. localhost와 기본 포트 6379 사용이 적절합니다.
26-27: 테스트용 더미 API 키 설정이 적절합니다.Resend API 연동을 위한 더미 키 설정이 테스트 환경에 적합합니다.
src/main/java/project/flipnote/common/util/CookieUtil.java (1)
11-18: 가독성 개선을 위한 좋은 포맷팅 변경입니다.메소드 파라미터를 여러 줄로 분할하여 가독성이 향상되었습니다. 많은 파라미터가 있을 때 적절한 포맷팅입니다.
src/main/java/project/flipnote/auth/model/UserLoginDto.java (1)
10-14: 검증 강화를 위한 우수한 개선사항입니다.
@NotEmpty에서@NotBlank로 변경하여 공백 문자만 있는 입력도 방지할 수 있게 되었습니다. 로그인 보안 강화에 도움이 됩니다.build.gradle (2)
31-38: 새로운 기능 지원을 위한 적절한 의존성 추가입니다.이메일 인증 기능 구현에 필요한 의존성들이 올바르게 추가되었습니다:
- Redis: 인증 코드 저장
- Thymeleaf: 이메일 템플릿
- AOP/Spring Retry: 재시도 로직
- Resend Java: 이메일 서비스 연동
38-38: com.resend:resend-java 의존성 버전 및 보안 검증 필요
- build.gradle 38행:
→ 해당 버전이 최신 릴리즈인지, 공개된 보안 취약점(CVE 등)은 없는지 확인해주세요.implementation 'com.resend:resend-java:4.1.1'src/main/java/project/flipnote/common/config/RedisConfig.java (1)
9-20: Redis 설정이 적절하게 구현되었습니다.이메일 인증을 위한 Redis 설정이 올바르게 구현되었습니다. StringRedisSerializer를 사용하여 키와 값을 모두 문자열로 처리하는 것이 이메일 인증 코드 저장 용도에 적합합니다.
src/main/java/project/flipnote/common/exception/GlobalExceptionHandler.java (1)
42-44: 코드 가독성 개선.메서드 시그니처를 여러 줄로 포맷팅하여 가독성이 향상되었습니다.
src/main/java/project/flipnote/auth/model/EmailVerificationDto.java (1)
6-14: 이메일 검증 DTO가 적절하게 구현되었습니다.Record를 사용한 구조와
@NotBlank검증 어노테이션이 적절하게 적용되어 있습니다. 이메일 인증 요청을 위한 간단하고 명확한 구조입니다.src/main/java/project/flipnote/auth/constants/AuthRedisKey.java (1)
9-15: Redis 키 상수 정의가 적절합니다.이메일 인증 코드와 인증 상태를 위한 Redis 키 패턴이 잘 정의되어 있습니다. TTL 값도 적절하게 설정되었습니다 (인증 코드는 더 짧은 시간, 인증 상태는 더 긴 시간).
src/main/resources/application-local.yml (2)
25-26: 더미 API 키는 적절합니다.로컬 개발환경용 더미 API 키로 적절하게 설정되어 있습니다. 실제 운영 환경에서는 올바른 API 키로 교체되어야 합니다.
1-21: 로컬 개발 환경 설정이 적절합니다.데이터베이스, Redis, JPA 설정이 로컬 개발 환경에 적합하게 구성되어 있습니다. Hibernate의 SQL 로깅 설정도 개발 시 유용할 것입니다.
src/main/java/project/flipnote/common/security/config/SecurityConfig.java (1)
60-63: 이메일 인증 엔드포인트 보안 설정이 올바르게 구성되었습니다.회원가입 플로우에서 이메일 인증 기능을 위해 인증 없이 접근할 수 있도록 설정된 것이 적절합니다.
src/main/java/project/flipnote/common/annotation/ValidPassword.java (1)
1-19: 비밀번호 유효성 검증 어노테이션이 올바르게 구현되었습니다.표준 Jakarta Bean Validation 패턴을 따르고 있으며, 필드, 메소드, 어노테이션 타입에 적용할 수 있도록 적절히 설정되었습니다.
src/main/java/project/flipnote/common/annotation/ValidPhone.java (1)
1-19: 휴대전화 번호 유효성 검증 어노테이션이 올바르게 구현되었습니다.ValidPassword와 일관성 있는 구조로 작성되었으며, 표준 validation 어노테이션 패턴을 따르고 있습니다.
src/main/java/project/flipnote/common/validator/PasswordConstraintValidator.java (1)
10-24: 비밀번호 유효성 검증 로직이 잘 구현되었습니다.정규표현식을 사용하여 영문자, 숫자, 특수문자를 포함한 8-16자 길이의 비밀번호를 요구하고 있으며, Pattern을 미리 컴파일하여 성능을 최적화한 점이 좋습니다.
src/main/java/project/flipnote/common/validator/PhoneConstraintValidator.java (1)
14-17: 전화번호 null 처리 동작을 명확히 검토해주세요.현재 null 값에 대해
true를 반환하여 전화번호를 선택적 필드로 처리하고 있습니다. 이것이 의도된 동작인지 확인하고, 필요시@NotNull어노테이션과 함께 사용하는 것을 고려해보세요.src/main/java/project/flipnote/auth/constants/VerificationConstants.java (1)
6-10: 상수 정의가 적절합니다유틸리티 클래스 패턴을 올바르게 구현했습니다. 인증 코드 길이 6자리와 TTL 5분은 보안성과 사용성의 균형을 잘 맞춘 설정입니다.
src/main/resources/templates/email/email-verification.html (1)
1-72: 이메일 템플릿이 잘 구성되어 있습니다.HTML 구조가 깔끔하고 이메일 클라이언트 호환성을 고려한 임베디드 CSS 스타일링이 적절합니다. Thymeleaf 표현식도 올바르게 사용되었습니다.
src/test/java/project/flipnote/common/validator/PhoneConstraintValidatorTest.java (1)
13-70: 휴대폰 번호 유효성 검증 테스트가 포괄적입니다.다양한 유효/무효 케이스를 모두 다루고 있으며, 한국어 디스플레이 이름으로 테스트 의도가 명확합니다. 테스트 커버리지도 우수합니다.
src/main/java/project/flipnote/user/model/UserRegisterDto.java (1)
13-36: 유효성 검증 어노테이션 업데이트가 적절합니다.
@NotEmpty에서@NotBlank로 변경하여 공백 문자열도 검증하도록 개선되었고, 커스텀 유효성 검증 어노테이션 사용으로 더 엄격한 검증이 가능합니다.getCleanedPhone()메서드 추가도 좋은 개선사항입니다.src/main/java/project/flipnote/common/config/AsyncConfig.java (2)
1-11: import 구문이 잘 정리되어 있습니다.필요한 Spring 비동기 설정 관련 클래스들이 적절히 import되어 있습니다.
12-17: 비동기 설정 클래스가 적절히 구현되었습니다.
@Configuration,@EnableAsync,@RequiredArgsConstructor어노테이션이 올바르게 사용되었고,AsyncConfigurer인터페이스를 구현하여 커스텀 비동기 실행기를 제공합니다.src/main/java/project/flipnote/auth/model/EmailVerificationConfirmDto.java (2)
1-6: 필요한 validation 관련 import가 적절히 구성되어 있습니다.Jakarta validation과 VerificationConstants 클래스가 올바르게 import되어 있습니다.
8-21: 검증 어노테이션이 적절히 적용되었습니다.
@NotBlank,@Size어노테이션을 사용하여 이메일과 인증 코드의 유효성을 검증하고 있습니다. 특히 인증 코드의 길이를VerificationConstants.CODE_LENGTH로 제한하는 것이 적절합니다.src/main/java/project/flipnote/auth/listener/EmailVerificationEventListener.java (3)
1-15: 필요한 Spring 관련 import가 적절히 구성되어 있습니다.이벤트 리스너, 재시도, 비동기 처리에 필요한 모든 클래스들이 올바르게 import되어 있습니다.
17-22: 이벤트 리스너 클래스가 적절히 구성되었습니다.
@Component,@RequiredArgsConstructor,@Slf4j어노테이션이 올바르게 사용되었고,EmailService의존성이 주입되었습니다.
34-37: 복구 메서드가 적절히 구현되었습니다.재시도 실패 시 로그를 남기는 복구 메서드가 올바르게 구현되어 있습니다.
src/main/resources/application.yml (7)
7-9: 데이터소스 설정이 환경 변수로 외부화되었습니다.설정 값들이 비워져 있어 환경 변수나 외부 설정 파일에서 주입받는 것으로 보입니다. 이는 보안 측면에서 적절한 접근입니다.
14-14: JPA ddl-auto 설정이 validate로 변경되었습니다.프로덕션 환경에 적합한
validate모드로 변경되어 안전성이 향상되었습니다.
20-25: Thymeleaf 설정이 적절히 구성되었습니다.이메일 템플릿 처리를 위한 Thymeleaf 설정이 올바르게 추가되었습니다.
27-31: Redis 설정이 환경 변수로 외부화되었습니다.Redis 연결 정보가 외부 설정으로 관리되어 보안성이 확보되었습니다.
41-41: JWT 시크릿이 환경 변수로 외부화되었습니다.보안에 중요한 JWT 시크릿이 외부 설정으로 관리되어 적절합니다.
45-48: 비동기 설정이 적절히 구성되었습니다.스레드 풀 설정이 합리적인 값으로 구성되어 있습니다.
50-52: Resend 설정이 환경 변수로 외부화되었습니다.API 키가 외부 설정으로 관리되어 보안성이 확보되었습니다.
src/main/java/project/flipnote/auth/controller/AuthController.java (3)
12-13: 새로운 DTO 클래스가 적절히 import되었습니다.이메일 인증 기능을 위한 DTO 클래스들이 올바르게 import되었습니다.
46-51: 이메일 인증 코드 발송 엔드포인트가 적절히 구현되었습니다.
@Valid어노테이션을 사용하여 요청 데이터의 유효성 검증을 수행하고, 적절한 HTTP 상태 코드를 반환합니다.
53-60: 이메일 인증 코드 확인 엔드포인트가 적절히 구현되었습니다.요청 데이터 검증과 적절한 HTTP 응답이 구현되어 있습니다.
src/main/java/project/flipnote/auth/exception/AuthErrorCode.java (1)
13-17: 에러 코드 추가가 적절합니다!이메일 인증 기능에 필요한 에러 코드들이 명확한 HTTP 상태 코드와 설명적인 메시지와 함께 잘 정의되었습니다.
src/main/java/project/flipnote/auth/service/AuthService.java (1)
34-34: SecureRandom 인스턴스는 final로 선언해야 합니다스레드 안전성과 불변성을 보장하기 위해 static 필드는 final로 선언하는 것이 좋습니다.
-private static final SecureRandom random = new SecureRandom(); +private static final SecureRandom RANDOM = new SecureRandom();메서드에서도 해당 상수 사용:
-return String.valueOf(random.nextInt(origin, bound)); +return String.valueOf(RANDOM.nextInt(origin, bound));Likely an incorrect or invalid review comment.
| @Test | ||
| @DisplayName("허용되지 않은 특수문자 포함 시 false") | ||
| void invalidPassword_wrongSpecialChar() { | ||
| assertThat(validator.isValid("Abc12345%", context)).isFalse(); | ||
| assertThat(validator.isValid("Abc12345?", context)).isFalse(); | ||
| } |
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.
💡 Verification agent
🧩 Analysis chain
허용되지 않은 특수문자 테스트 완성도 확인
허용되지 않은 특수문자에 대한 테스트가 있지만, 실제 검증기에서 허용하는 특수문자 목록을 확인해야 합니다.
다음 스크립트로 실제 검증기의 허용 특수문자 목록을 확인해주세요:
🏁 Script executed:
#!/bin/bash
# PasswordConstraintValidator에서 허용되는 특수문자 패턴 확인
rg -A 10 "class PasswordConstraintValidator" --type javaLength of output: 2630
PasswordConstraintValidator 허용 특수문자 패턴 확인 및 테스트 보완
PasswordConstraintValidator.java에서 허용하는 특수문자 목록이 아래와 같음을 확인했습니다.
^(?=.[A-Za-z])(?=.\d)(?=.[!@#$^()-])[A-Za-z\d!@#$^*()-]{8,16}$
→ 허용 특수문자: ! @ # $ ^ * ( ) _ -
현재 테스트에서 %와 ?가 올바르게 차단됨을 확인했으나, 완성도를 위해 허용 특수문자 각각에 대한 성공 케이스 테스트를 추가할 것을 권장드립니다.
• src/main/java/project/flipnote/common/validator/PasswordConstraintValidator.java
– 허용 특수문자 패턴: !@#$^*()_-
• src/test/java/project/flipnote/common/validator/PasswordConstraintValidatorTest.java
– 각 허용 특수문자(!, @, #, $, ^, *, (, ), _, -)를 포함하는 비밀번호에 대해 validator.isValid(..., context).isTrue() 테스트 추가
🤖 Prompt for AI Agents
In
src/test/java/project/flipnote/common/validator/PasswordConstraintValidatorTest.java
around lines 38 to 43, the test currently verifies that disallowed special
characters '%' and '?' cause validation to fail. To improve test coverage, add
separate test cases that assert validator.isValid returns true for passwords
containing each allowed special character (!, @, #, $, ^, *, (, ), _, -). This
ensures that all permitted special characters are explicitly tested for
successful validation.
src/main/java/project/flipnote/auth/listener/EmailVerificationEventListener.java
Show resolved
Hide resolved
src/main/java/project/flipnote/auth/repository/EmailVerificationRedisRepository.java
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: 0
🧹 Nitpick comments (1)
src/main/java/project/flipnote/infra/config/ResendProperties.java (1)
14-21: 클래스 문서화를 추가하는 것을 권장합니다.코드의 가독성과 유지보수성을 향상시키기 위해 클래스와 필드에 대한 JavaDoc 문서를 추가하는 것이 좋겠습니다.
다음과 같이 문서화를 추가할 수 있습니다:
+/** + * Resend 이메일 서비스 구성 속성 + * + * @author FlipNote Team + */ @Getter @Setter @ConfigurationProperties("resend") @Component public class ResendProperties { + /** + * 이메일 발신자 주소 + */ @NotEmpty private String fromEmail; + /** + * Resend API 키 + */ @NotEmpty private String apiKey; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/main/java/project/flipnote/infra/config/ResendProperties.java(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: build
🔇 Additional comments (1)
src/main/java/project/flipnote/infra/config/ResendProperties.java (1)
10-21: 클래스 구조와 구성이 적절합니다.Spring Boot 구성 속성 클래스의 베스트 프랙티스를 잘 따르고 있습니다.
@ConfigurationProperties와@Component어노테이션의 사용이 적절하고,@NotEmpty검증 어노테이션으로 필수 속성들을 올바르게 검증하고 있습니다.
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
♻️ Duplicate comments (1)
src/main/java/project/flipnote/common/config/AsyncProperties.java (1)
18-25: 기본값 설정 및 추가 검증 로직이 필요합니다.@positive 제약 조건이 추가되어 이전 리뷰 의견의 일부가 해결되었지만, 여전히 기본값이 없어 설정이 누락된 경우 시작 시 검증 오류가 발생할 수 있습니다. 또한 스레드 풀 설정의 논리적 일관성 검증도 필요합니다.
다음과 같이 개선하는 것을 권장합니다:
@Getter @Setter @Validated @ConfigurationProperties(prefix = "async") @Component public class AsyncProperties { @Positive - private int corePoolSize; + private int corePoolSize = 2; @Positive - private int maxPoolSize; + private int maxPoolSize = 10; @Positive - private int queueCapacity; + private int queueCapacity = 100; + + @AssertTrue(message = "Max pool size must be greater than or equal to core pool size") + private boolean isMaxPoolSizeValid() { + return maxPoolSize >= corePoolSize; + } }추가로
jakarta.validation.constraints.AssertTrueimport가 필요합니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/main/java/project/flipnote/common/config/AsyncProperties.java(1 hunks)src/main/java/project/flipnote/infra/config/ResendProperties.java(1 hunks)src/main/resources/application-test.yml(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main/java/project/flipnote/infra/config/ResendProperties.java
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/main/java/project/flipnote/common/config/AsyncProperties.java (1)
src/main/java/project/flipnote/infra/config/ResendProperties.java (1)
Getter(11-23)
🪛 Gitleaks (8.26.0)
src/main/resources/application-test.yml
24-24: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🔇 Additional comments (3)
src/main/java/project/flipnote/common/config/AsyncProperties.java (2)
11-16: 클래스 선언 및 어노테이션이 올바릅니다.Spring Boot 설정 프로퍼티 클래스의 모범 사례를 잘 따르고 있습니다. @validated 어노테이션이 추가되어 필드 유효성 검증이 활성화됩니다.
1-27: 전체적인 클래스 구조가 프로젝트 패턴과 일치합니다.ResendProperties 클래스와 동일한 구조와 어노테이션 패턴을 사용하여 프로젝트 내 일관성을 유지하고 있습니다. Lombok 사용으로 보일러플레이트 코드도 적절히 줄였습니다.
src/main/resources/application-test.yml (1)
26-27: Resend API 키도 하드코딩되어 있습니다 – 환경 분리 필요
dummy_api_key라고 명시돼 있지만, 실환경 배포 시 ENV 로 주입되지 않으면 메일 발송 실패가 발생합니다.resend.api-key=${RESEND_API_KEY:dummy}형태로 바꿔 두는 것이 안전합니다. 필요 시 CI 환경 변수를 확인해 주세요.
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: 0
🧹 Nitpick comments (1)
src/main/java/project/flipnote/auth/service/AuthService.java (1)
102-116: 유틸리티 메서드들 및 코드 생성 로직 검토코드 생성 로직에서 개선이 필요합니다:
-private String generateVerificationCode(int length) { - int origin = (int)Math.pow(10, length - 1); - int bound = (int)Math.pow(10, length); - return String.valueOf(random.nextInt(origin, bound)); -} +private String generateVerificationCode(int length) { + int origin = (int)Math.pow(10, length - 1); + int bound = (int)Math.pow(10, length); + return String.format("%0" + length + "d", random.nextInt(origin, bound)); +}현재 구현에서는 앞자리가 0인 코드가 생성될 수 있지만, 실제로는 Math.pow 계산으로 인해 항상 지정된 길이가 보장됩니다. 하지만 String.format을 사용하면 더 명확하고 안전합니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/main/java/project/flipnote/auth/service/AuthService.java(3 hunks)
🔇 Additional comments (6)
src/main/java/project/flipnote/auth/service/AuthService.java (6)
3-4: 새로운 import 추가 승인SecureRandom과 Objects 클래스 import가 적절하게 추가되었습니다. 보안을 위한 SecureRandom 사용은 좋은 선택입니다.
6-18: 이메일 인증 관련 의존성 추가 승인이메일 인증 기능을 위한 새로운 의존성들이 적절하게 추가되었습니다:
- ApplicationEventPublisher: 이벤트 기반 이메일 발송을 위한 좋은 패턴
- 각종 DTO와 Repository: 기능 구현에 필요한 컴포넌트들
31-34: 의존성 주입 및 SecureRandom 인스턴스 승인새로운 의존성들이 적절하게 주입되었고, static final SecureRandom 인스턴스 사용은 보안 측면에서 적절한 선택입니다.
39-53: 비밀번호 검증 로직 추출 승인비밀번호 검증 로직을 별도 메서드로 추출한 것은 코드의 가독성과 재사용성을 향상시키는 좋은 리팩토링입니다.
55-78: 이메일 인증 코드 발송 로직 검토이메일 인증 코드 발송 로직이 잘 구현되어 있습니다:
- 이메일 사용 가능성 검증
- 기존 인증 코드 존재 여부 확인
- 보안 코드 생성 및 Redis 저장
- 이벤트 발행을 통한 비동기 이메일 발송
전체 플로우가 논리적으로 올바르게 구성되어 있습니다.
80-100: 이메일 인증 코드 확인 로직 승인인증 코드 확인 로직이 안전하게 구현되어 있습니다:
- 적절한 예외 처리
- Objects.equals()를 사용한 안전한 문자열 비교
- 인증 성공 시 코드 삭제 및 검증 상태 저장
보안과 기능성 측면에서 잘 구현되었습니다.
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: 0
🧹 Nitpick comments (2)
src/test/java/project/flipnote/auth/service/AuthServiceTest.java (2)
104-105: 매직 넘버 사용을 개선해보세요.하드코딩된 이메일 주소와 인증번호 대신 상수나 테스트 데이터 클래스를 사용하는 것을 고려해보세요.
+ private static final String TEST_EMAIL = "test@test.com"; + private static final String VALID_CODE = "123456"; + private static final String INVALID_CODE = "654321"; EmailVerificationConfirmDto.Request req - = new EmailVerificationConfirmDto.Request("test@test.com", "123456"); + = new EmailVerificationConfirmDto.Request(TEST_EMAIL, VALID_CODE);
119-120: 테스트 데이터 중복을 줄여보세요.동일한 이메일 주소와 인증번호가 여러 테스트에서 반복 사용됩니다. 테스트 데이터를 상수로 추출하거나 @beforeeach에서 초기화하는 것을 고려해보세요.
+ private static final String TEST_EMAIL = "test@test.com"; + private static final String VALID_CODE = "123456"; + private static final String INVALID_CODE = "654321"; EmailVerificationConfirmDto.Request req - = new EmailVerificationConfirmDto.Request("test@test.com", "123456"); + = new EmailVerificationConfirmDto.Request(TEST_EMAIL, VALID_CODE);Also applies to: 137-138
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/test/java/project/flipnote/auth/service/AuthServiceTest.java(1 hunks)src/test/java/project/flipnote/user/service/UserServiceTest.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/test/java/project/flipnote/user/service/UserServiceTest.java
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/test/java/project/flipnote/auth/service/AuthServiceTest.java (2)
src/main/java/project/flipnote/auth/model/EmailVerificationConfirmDto.java (1)
EmailVerificationConfirmDto(8-21)src/main/java/project/flipnote/auth/model/EmailVerificationDto.java (1)
EmailVerificationDto(6-14)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: build
🔇 Additional comments (9)
src/test/java/project/flipnote/auth/service/AuthServiceTest.java (9)
1-26: 임포트 구조가 잘 정리되어 있습니다.정적 임포트와 일반 임포트가 적절히 사용되었고, 필요한 모든 의존성이 올바르게 임포트되어 있습니다.
27-42: 테스트 클래스 구조가 적절합니다.@DisplayName 어노테이션으로 한국어 설명을 제공하고, 필요한 모든 의존성을 @mock으로 모킹하여 단위 테스트 환경을 올바르게 구성했습니다.
49-65: 인증번호 생성 검증 로직이 우수합니다.정규표현식을 사용한 인증번호 형식 검증(
argThat(code -> code.length() == codeLength && code.matches("\\d{%s}".formatted(codeLength))))이 특히 인상적입니다. 길이와 숫자 형식을 모두 확인하여 생성된 인증번호의 품질을 보장합니다.
68-79: 예외 처리 테스트가 완벽합니다.예외 타입과 에러 코드를 모두 검증하고,
never()검증으로 부작용이 없음을 확인하는 것이 좋습니다.
82-94: 중복 인증번호 발급 방지 테스트가 적절합니다.Redis에 이미 존재하는 인증번호에 대한 중복 발급 방지 로직을 올바르게 테스트하고 있습니다.
107-108: 모킹 설정이 명확합니다.
given-willReturn패턴을 사용하여 Redis 리포지토리의 동작을 명확하게 모킹했습니다.
112-113: 메서드 호출 검증이 정확합니다.
deleteCode와markAsVerified메서드가 정확히 한 번씩 호출되는지 검증하여 서비스 로직의 정확성을 보장합니다.
140-141: 잘못된 인증번호 시나리오가 명확합니다.저장된 인증번호("654321")와 요청된 인증번호("123456")가 다른 경우를 명확하게 테스트하고 있습니다.
143-147: 예외 처리와 에러 코드 검증이 일관성 있습니다.모든 실패 케이스에서
BizException타입과 해당AuthErrorCode를 일관성 있게 검증하고 있습니다.
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: 0
🧹 Nitpick comments (1)
src/main/java/project/flipnote/user/model/UserRegisterRequest.java (1)
31-33: 전화번호 정리 메서드의 방어적 프로그래밍을 개선할 수 있습니다.현재 구현은 기본적인 null 체크와 하이픈 제거 기능을 제공하지만, 더 안전한 구현을 위해 개선할 수 있습니다.
다음과 같이 개선하는 것을 고려해보세요:
public String getCleanedPhone() { - return phone == null ? null : phone.replaceAll("-", ""); + return phone == null ? null : phone.replaceAll("[^0-9]", ""); }이렇게 하면 하이픈뿐만 아니라 다른 특수문자도 제거할 수 있어 더 안전한 전화번호 처리가 가능합니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
src/main/java/project/flipnote/user/controller/UserController.java(2 hunks)src/main/java/project/flipnote/user/model/UserRegisterDto.java(0 hunks)src/main/java/project/flipnote/user/model/UserRegisterRequest.java(1 hunks)src/main/java/project/flipnote/user/model/UserRegisterResponse.java(1 hunks)src/main/java/project/flipnote/user/service/UserService.java(2 hunks)src/test/java/project/flipnote/user/service/UserServiceTest.java(1 hunks)
💤 Files with no reviewable changes (1)
- src/main/java/project/flipnote/user/model/UserRegisterDto.java
✅ Files skipped from review due to trivial changes (1)
- src/main/java/project/flipnote/user/model/UserRegisterResponse.java
🚧 Files skipped from review as they are similar to previous changes (2)
- src/test/java/project/flipnote/user/service/UserServiceTest.java
- src/main/java/project/flipnote/user/service/UserService.java
🔇 Additional comments (3)
src/main/java/project/flipnote/user/model/UserRegisterRequest.java (1)
9-29: 레코드 구조와 유효성 검증이 적절하게 구현되었습니다.사용자 등록에 필요한 필드들이 적절한 유효성 검증 어노테이션과 함께 잘 정의되어 있습니다. 커스텀 유효성 검증 어노테이션(
@ValidPassword,@ValidPhone)을 사용하여 비즈니스 로직에 맞는 검증을 수행하고 있습니다.src/main/java/project/flipnote/user/controller/UserController.java (2)
12-13: 새로운 요청/응답 타입으로의 전환이 적절하게 이루어졌습니다.
UserRegisterRequest와UserRegisterResponse타입으로의 임포트 변경이 깔끔하게 처리되었습니다.
24-26: 서비스 계층 register 메서드 시그니처 일치 확인
src/main/java/project/flipnote/user/service/UserService.java28행:public UserRegisterResponse register(UserRegisterRequest req) {컨트롤러와 요청/응답 타입이 일치하므로 추가 작업이 필요 없습니다.
| .nickname(req.nickname()) | ||
| .smsAgree(req.smsAgree()) | ||
| .phone(req.phone()) | ||
| .phone(phone) |
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.
P3: 번호 같은 경우 개인정보라 암호화를 하는게 좋지 않을까요?
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.
깜빡했네요 반영하겠습니다!
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 (4)
src/main/java/project/flipnote/common/validation/validator/PasswordConstraintValidator.java (1)
12-14: 비밀번호 정규식 패턴의 특수문자 범위를 검토해주세요.현재 허용되는 특수문자가
!@#$^*()_-로 제한되어 있습니다. 일반적으로 사용되는 다른 특수문자들(예:%,&,+,=,?등)도 고려해보시기 바랍니다.현재 패턴을 더 포괄적으로 변경하는 것을 고려해보세요:
private static final String PASSWORD_PATTERN = - "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$^*()_\\-])[A-Za-z\\d!@#$^*()_\\-]{8,16}$"; + "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^&*()_\\-+=?])[A-Za-z\\d!@#$%^&*()_\\-+=?]{8,16}$";src/main/java/project/flipnote/common/validation/validator/PhoneConstraintValidator.java (1)
11-20: 성능 최적화를 위해 Pattern을 컴파일하는 것을 고려해보세요.PasswordConstraintValidator와 달리 매번 String.matches()를 호출하고 있어 성능 일관성이 떨어집니다.
다음과 같이 개선할 수 있습니다:
+import java.util.regex.Pattern; + public class PhoneConstraintValidator implements ConstraintValidator<ValidPhone, String> { - private static final String PHONE_PATTERN = "^010-\\d{4}-\\d{4}$"; + private static final String PHONE_PATTERN = "^010-\\d{4}-\\d{4}$"; + private static final Pattern pattern = Pattern.compile(PHONE_PATTERN); @Override public boolean isValid(String phone, ConstraintValidatorContext context) { if (Objects.isNull(phone)) { return true; } - return phone.matches(PHONE_PATTERN); + return pattern.matcher(phone).matches(); }src/main/java/project/flipnote/common/crypto/AesCryptoProperties.java (1)
12-25: 설정 파일에서 암호화 키를 안전하게 관리하는 방법을 고려해주세요.현재 구현은 application.yml에서 평문으로 키를 관리하게 됩니다. 프로덕션 환경에서는 다음과 같은 방법을 고려해보세요:
- 환경변수를 통한 키 주입
- Spring Cloud Config Server 사용
- AWS Systems Manager Parameter Store 등 외부 키 관리 시스템 활용
src/main/java/project/flipnote/common/crypto/AesCryptoConverter.java (1)
28-52: 암호화 로직이 올바르게 구현되었습니다.랜덤 IV 생성, 데이터 결합, Base64 인코딩이 모두 적절하게 처리되었습니다. 다만 성능 최적화를 위해 SecureRandom 인스턴스를 재사용하는 것을 고려해보세요.
성능 개선을 위한 SecureRandom 최적화 제안:
+ private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + @Override public String convertToDatabaseColumn(String attribute) { if (!StringUtils.hasText(attribute)) { return attribute; } try { Cipher cipher = Cipher.getInstance(ALGORITHM); byte[] iv = new byte[IV_SIZE_BYTES]; - new SecureRandom().nextBytes(iv); + SECURE_RANDOM.nextBytes(iv); IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
src/main/java/project/flipnote/auth/service/AuthService.java(2 hunks)src/main/java/project/flipnote/common/config/AsyncProperties.java(1 hunks)src/main/java/project/flipnote/common/crypto/AesCryptoConfig.java(1 hunks)src/main/java/project/flipnote/common/crypto/AesCryptoConverter.java(1 hunks)src/main/java/project/flipnote/common/crypto/AesCryptoProperties.java(1 hunks)src/main/java/project/flipnote/common/security/jwt/JwtProperties.java(1 hunks)src/main/java/project/flipnote/common/validation/annotation/ValidPassword.java(1 hunks)src/main/java/project/flipnote/common/validation/annotation/ValidPhone.java(1 hunks)src/main/java/project/flipnote/common/validation/validator/PasswordConstraintValidator.java(1 hunks)src/main/java/project/flipnote/common/validation/validator/PhoneConstraintValidator.java(1 hunks)src/main/java/project/flipnote/infra/config/ResendProperties.java(1 hunks)src/main/java/project/flipnote/user/entity/User.java(2 hunks)src/main/java/project/flipnote/user/model/UserRegisterRequest.java(1 hunks)src/main/resources/application.yml(1 hunks)src/test/java/project/flipnote/common/validator/PasswordConstraintValidatorTest.java(1 hunks)src/test/java/project/flipnote/common/validator/PhoneConstraintValidatorTest.java(1 hunks)src/test/resources/application-test.yml(1 hunks)
✅ Files skipped from review due to trivial changes (4)
- src/main/java/project/flipnote/common/security/jwt/JwtProperties.java
- src/test/resources/application-test.yml
- src/main/java/project/flipnote/user/entity/User.java
- src/main/java/project/flipnote/common/crypto/AesCryptoConfig.java
🚧 Files skipped from review as they are similar to previous changes (7)
- src/test/java/project/flipnote/common/validator/PasswordConstraintValidatorTest.java
- src/test/java/project/flipnote/common/validator/PhoneConstraintValidatorTest.java
- src/main/java/project/flipnote/user/model/UserRegisterRequest.java
- src/main/resources/application.yml
- src/main/java/project/flipnote/common/config/AsyncProperties.java
- src/main/java/project/flipnote/infra/config/ResendProperties.java
- src/main/java/project/flipnote/auth/service/AuthService.java
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/main/java/project/flipnote/common/validation/annotation/ValidPhone.java (1)
src/main/java/project/flipnote/common/validation/validator/PhoneConstraintValidator.java (1)
PhoneConstraintValidator(9-21)
src/main/java/project/flipnote/common/validation/annotation/ValidPassword.java (1)
src/main/java/project/flipnote/common/validation/validator/PasswordConstraintValidator.java (1)
PasswordConstraintValidator(10-24)
src/main/java/project/flipnote/common/crypto/AesCryptoProperties.java (3)
src/main/java/project/flipnote/common/config/AsyncProperties.java (1)
Getter(11-26)src/main/java/project/flipnote/common/security/jwt/JwtProperties.java (1)
Getter(13-31)src/main/java/project/flipnote/infra/config/ResendProperties.java (1)
Getter(11-23)
src/main/java/project/flipnote/common/crypto/AesCryptoConverter.java (1)
src/main/java/project/flipnote/common/crypto/AesCryptoConfig.java (1)
RequiredArgsConstructor(12-22)
🪛 ast-grep (0.38.1)
src/main/java/project/flipnote/common/crypto/AesCryptoConverter.java
[warning] 34-34: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: Cipher.getInstance(ALGORITHM)
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures
- https://googleprojectzero.blogspot.com/2022/10/rc4-is-still-considered-harmful.html
(use-of-aes-ecb-java)
[warning] 60-60: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: Cipher.getInstance(ALGORITHM)
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures
- https://googleprojectzero.blogspot.com/2022/10/rc4-is-still-considered-harmful.html
(use-of-aes-ecb-java)
[warning] 34-34: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: Cipher.getInstance(ALGORITHM)
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://find-sec-bugs.github.io/bugs.htm#TDES_USAGE
- https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA
(desede-is-deprecated-java)
[warning] 60-60: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: Cipher.getInstance(ALGORITHM)
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://find-sec-bugs.github.io/bugs.htm#TDES_USAGE
- https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA
(desede-is-deprecated-java)
🔇 Additional comments (6)
src/main/java/project/flipnote/common/validation/annotation/ValidPassword.java (1)
1-19: 비밀번호 검증 어노테이션이 올바르게 구현되었습니다.Bean Validation 표준을 잘 따르고 있으며, 필요한 모든 메타데이터가 포함되어 있습니다.
src/main/java/project/flipnote/common/validation/validator/PasswordConstraintValidator.java (1)
17-23: null 값 처리와 검증 로직이 올바르게 구현되었습니다.컴파일된 Pattern 사용으로 성능도 잘 최적화되어 있습니다.
src/main/java/project/flipnote/common/validation/annotation/ValidPhone.java (1)
1-19: 휴대전화 번호 검증 어노테이션이 올바르게 구현되었습니다.ValidPassword와 일관된 구조를 가지고 있어 코드 일관성이 좋습니다.
src/main/java/project/flipnote/common/validation/validator/PhoneConstraintValidator.java (1)
15-17: 휴대폰 번호 필드의 null 허용 여부 확인UserRegisterRequest의
phone필드에는@ValidPhone만 적용되어 있어 null도 유효 처리됩니다.
- 선택적 입력이라면 현재 로직(Objects.isNull → true 반환)을 그대로 유지해도 무방합니다.
- 필수 입력이라면
@NotNull을 추가로 적용해주세요.위치
- src/main/java/project/flipnote/user/model/UserRegisterRequest.java:25-26
src/main/java/project/flipnote/common/crypto/AesCryptoConverter.java (2)
18-26: 클래스 구조와 암호화 알고리즘 선택이 적절합니다.JPA 컨버터 애노테이션과 의존성 주입이 올바르게 구현되었으며, AES/CBC/PKCS5Padding 알고리즘 선택과 16바이트 IV 크기 설정이 암호화 보안 모범 사례에 부합합니다.
49-51: 예외 처리가 적절하게 구현되었습니다.JPA 컨버터에서 체크 예외를 런타임 예외로 변환하는 방식이 올바르며, 원본 예외를 보존하고 한국어 오류 메시지를 제공하는 것이 프로젝트와 일관성이 있습니다.
Also applies to: 76-78
| @Override | ||
| public String convertToEntityAttribute(String dbData) { | ||
| if (!StringUtils.hasText(dbData)) { | ||
| return dbData; | ||
| } | ||
|
|
||
| try { | ||
| Cipher cipher = Cipher.getInstance(ALGORITHM); | ||
|
|
||
| byte[] combined = Base64.getDecoder().decode(dbData); | ||
|
|
||
| byte[] iv = new byte[IV_SIZE_BYTES]; | ||
| System.arraycopy(combined, 0, iv, 0, iv.length); | ||
| IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); | ||
|
|
||
| byte[] encrypted = new byte[combined.length - iv.length]; | ||
| System.arraycopy(combined, iv.length, encrypted, 0, encrypted.length); | ||
|
|
||
| cipher.init(Cipher.DECRYPT_MODE, aesSecretKeySpec, ivParameterSpec); | ||
| byte[] decrypted = cipher.doFinal(encrypted); | ||
|
|
||
| return new String(decrypted, StandardCharsets.UTF_8); | ||
| } catch (Exception e) { | ||
| throw new IllegalStateException("데이터 복호화에 실패했습니다.", e); | ||
| } | ||
| } |
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.
🛠️ Refactor suggestion
복호화 로직이 적절하게 구현되었으나 입력 검증을 강화해야 합니다.
IV 추출과 복호화 과정이 올바르게 구현되었지만, Base64 디코딩 후 데이터 길이 검증이 누락되어 있습니다. 잘못된 데이터가 입력될 경우 예외가 발생할 수 있습니다.
입력 데이터 검증 강화를 위한 개선 사항:
@Override
public String convertToEntityAttribute(String dbData) {
if (!StringUtils.hasText(dbData)) {
return dbData;
}
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
byte[] combined = Base64.getDecoder().decode(dbData);
+
+ if (combined.length < IV_SIZE_BYTES) {
+ throw new IllegalArgumentException("암호화된 데이터의 길이가 올바르지 않습니다.");
+ }
byte[] iv = new byte[IV_SIZE_BYTES];📝 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.
| @Override | |
| public String convertToEntityAttribute(String dbData) { | |
| if (!StringUtils.hasText(dbData)) { | |
| return dbData; | |
| } | |
| try { | |
| Cipher cipher = Cipher.getInstance(ALGORITHM); | |
| byte[] combined = Base64.getDecoder().decode(dbData); | |
| byte[] iv = new byte[IV_SIZE_BYTES]; | |
| System.arraycopy(combined, 0, iv, 0, iv.length); | |
| IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); | |
| byte[] encrypted = new byte[combined.length - iv.length]; | |
| System.arraycopy(combined, iv.length, encrypted, 0, encrypted.length); | |
| cipher.init(Cipher.DECRYPT_MODE, aesSecretKeySpec, ivParameterSpec); | |
| byte[] decrypted = cipher.doFinal(encrypted); | |
| return new String(decrypted, StandardCharsets.UTF_8); | |
| } catch (Exception e) { | |
| throw new IllegalStateException("데이터 복호화에 실패했습니다.", e); | |
| } | |
| } | |
| @Override | |
| public String convertToEntityAttribute(String dbData) { | |
| if (!StringUtils.hasText(dbData)) { | |
| return dbData; | |
| } | |
| try { | |
| Cipher cipher = Cipher.getInstance(ALGORITHM); | |
| byte[] combined = Base64.getDecoder().decode(dbData); | |
| if (combined.length < IV_SIZE_BYTES) { | |
| throw new IllegalArgumentException("암호화된 데이터의 길이가 올바르지 않습니다."); | |
| } | |
| byte[] iv = new byte[IV_SIZE_BYTES]; | |
| System.arraycopy(combined, 0, iv, 0, iv.length); | |
| IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); | |
| byte[] encrypted = new byte[combined.length - iv.length]; | |
| System.arraycopy(combined, iv.length, encrypted, 0, encrypted.length); | |
| cipher.init(Cipher.DECRYPT_MODE, aesSecretKeySpec, ivParameterSpec); | |
| byte[] decrypted = cipher.doFinal(encrypted); | |
| return new String(decrypted, StandardCharsets.UTF_8); | |
| } catch (Exception e) { | |
| throw new IllegalStateException("데이터 복호화에 실패했습니다.", e); | |
| } | |
| } |
🧰 Tools
🪛 ast-grep (0.38.1)
[warning] 60-60: Use of AES with ECB mode detected. ECB doesn't provide message confidentiality and is not semantically secure so should not be used. Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions for more information.
Context: Cipher.getInstance(ALGORITHM)
Note: [CWE-327]: Use of a Broken or Risky Cryptographic Algorithm [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures
- https://googleprojectzero.blogspot.com/2022/10/rc4-is-still-considered-harmful.html
(use-of-aes-ecb-java)
[warning] 60-60: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES.
Context: Cipher.getInstance(ALGORITHM)
Note: [CWE-326]: Inadequate Encryption Strength [OWASP A03:2017]: Sensitive Data Exposure [OWASP A02:2021]: Cryptographic Failures [REFERENCES]
- https://find-sec-bugs.github.io/bugs.htm#TDES_USAGE
- https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA
(desede-is-deprecated-java)
🤖 Prompt for AI Agents
In src/main/java/project/flipnote/common/crypto/AesCryptoConverter.java around
lines 54 to 79, the method convertToEntityAttribute lacks validation of the
decoded Base64 data length before extracting the IV and encrypted bytes. To fix
this, add a check after Base64 decoding to ensure the combined byte array length
is at least the size of the IV plus some minimum expected length. If the length
is insufficient, throw an appropriate exception to prevent processing invalid
data and avoid runtime errors during array copying.
📝 변경 내용
✅ 체크리스트
💬 기타 참고 사항
Summary by CodeRabbit
신규 기능
버그 수정
구성 및 환경
테스트
기타