[USER] 회원가입 이메일 인증 시스템 구현, JWT 인증 로직 개선#15
Conversation
Summary by CodeRabbit
Walkthrough이번 변경에서는 이메일 인증 기능이 새롭게 도입되어, 사용자 가입 시 이메일로 인증 코드를 발송하고 검증할 수 있도록 구현되었습니다. 이를 위해 메일 발송 설정(Spring Boot Mail Starter 및 MailConfig), 이메일 인증 엔티티 및 레포지토리, 서비스, DTO, 컨트롤러 엔드포인트가 추가되었습니다. 회원가입 폼과 로직이 전화번호 입력 및 이메일 인증 절차를 반영하도록 확장되었으며, 기존 AWS/Google Vision 관련 설정 파일은 삭제되었습니다. JWT 만료 처리 방식도 개선되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UsersApiController
participant EmailService
participant EmailVerificationRepository
participant JavaMailSender
User->>UsersApiController: POST /api/auth/send-verification (email)
UsersApiController->>EmailService: sendVerificationCode(email)
EmailService->>EmailVerificationRepository: save(email, code, createdAt)
EmailService->>JavaMailSender: send(email, code)
EmailService-->>UsersApiController: 성공/실패 반환
UsersApiController-->>User: { success: true/false }
User->>UsersApiController: POST /api/auth/verify-code (email, code)
UsersApiController->>EmailService: verifyCode(email, code)
EmailService->>EmailVerificationRepository: findTopByEmailOrderByIdDesc(email)
EmailService->>EmailVerificationRepository: deleteByEmail(email) (성공시)
EmailService-->>UsersApiController: 검증 성공/실패 반환
UsersApiController-->>User: { success: true/false }
Possibly related PRs
Poem
✨ 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:
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 (
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (22)
src/main/java/io/github/petty/users/jwt/JWTFilter.java (1)
48-51: 만료된 토큰에 대한 처리 개선이 잘 이루어졌습니다.만료된 JWT 토큰에 대해 401 Unauthorized 응답을 반환하도록 변경한 것은 보안 측면에서 적절한 개선입니다. 다만, 응답의 Content-Type을 명시적으로 설정하면 더 좋을 것 같습니다.
// 토큰이 만료되었을 때 401 Unauthorized 응답을 반환 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401 상태 코드 +response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"error\": \"Token has expired\"}"); // 에러 메시지src/main/java/io/github/petty/users/dto/JoinDTO.java (1)
14-15: 전화번호 필드가 추가되었지만 유효성 검증이 없습니다.사용자 등록 과정에 전화번호 필드를 추가한 것은 좋은 변화입니다. 그러나 이 필드에 대한 유효성 검증이 없습니다. 전화번호 형식 검증을 추가하는 것이 좋겠습니다.
다음과 같이 Bean Validation 어노테이션을 추가하는 것을 고려해보세요:
private String displayName; -private String phone; +@Pattern(regexp = "^\\d{10,11}$", message = "전화번호는 10-11자리 숫자여야 합니다") +private String phone;또한 필드에 대한 설명 주석을 추가하면 더 좋을 것입니다:
private String displayName; +/** + * 사용자 전화번호 (예: 01012345678) + */ @Pattern(regexp = "^\\d{10,11}$", message = "전화번호는 10-11자리 숫자여야 합니다") private String phone;src/main/java/io/github/petty/users/entity/Users.java (1)
26-26: 사용자 전화번호 필드가 추가되었습니다.사용자 엔티티에 전화번호 필드가 추가된 것은 이메일 인증 시스템과 함께 사용자 정보를 강화하는 좋은 개선입니다.
다음 사항을 고려해 보세요:
- 전화번호 형식 검증을 위한 Bean Validation 어노테이션(예:
@Pattern) 추가- 전화번호가 필수인지 선택인지에 따라
@NotNull또는@Column(nullable = false)같은 제약조건 고려- 국제 전화번호 형식을 지원하기 위한 형식 지정 고려(E.164 등)
private String username; // email private String password; private String displayName; -private String phone; +@Pattern(regexp = "^\\d{10,11}$", message = "전화번호는 10-11자리 숫자여야 합니다") +private String phone;src/main/java/io/github/petty/users/service/JoinService.java (1)
34-34: 사용자 엔티티에 전화번호 설정이 추가되었습니다.전화번호 필드 설정 코드가 적절히 추가되었습니다.
전화번호 데이터가 설정되기 전에 기본적인 유효성 검사를 추가하는 것을 고려해보세요. 현재는 DTO나 엔티티에서 아무런 검증 없이 값을 그대로 저장하고 있습니다.
users.setUsername(username); users.setPassword(encodedPassword); users.setDisplayName(displayName); -users.setPhone(phone); +// 전화번호 기본 검증 후 저장 +if (phone != null && !phone.isBlank()) { + users.setPhone(phone.replaceAll("[^0-9]", "")); // 숫자만 추출하여 저장 +} else { + users.setPhone(null); +} users.setRole(Role.ROLE_USER.name());src/main/resources/templates/index.html (3)
49-50: 회원가입 오류 메시지 표시 요소가 추가되었습니다.회원가입 오류를 표시하기 위한 h3 요소가 추가되었습니다. 오류 메시지는 사용자 경험을 향상시키는 좋은 방법입니다.
다음과 같은 개선을 고려해보세요:
- 오류 메시지에 스타일 적용
- 오류가 있을 때만 표시되도록 조건부 렌더링 추가
-<h1 th:text="${message}">PETTY에 오신 것을 환영합니다</h1> -<h3 th:text="${error}">회원가입 에러 확인용</h3> +<h1 th:text="${message}">PETTY에 오신 것을 환영합니다</h1> +<h3 th:if="${error}" th:text="${error}" style="color: #e74c3c;">회원가입 에러 확인용</h3>
105-106: JWT 토큰 만료 시 페이지 새로고침 로직이 추가되었습니다.401 Unauthorized 응답을 받았을 때 JWT 토큰을 제거하고 페이지를 새로고침하는 로직이 추가되었습니다. 이는 PR 목표에 언급된 "클라이언트 측 JWT 만료 감지 및 재로그인 로직 구현"과 일치합니다.
메시지나 토스트 알림을 추가하여 사용자에게 세션 만료 정보를 제공하는 것을 고려해보세요.
if (response.status === 401) { localStorage.removeItem('jwt'); - location.reload(); // 로그인 상태를 유지할 수 있도록 새로 고침 + // 세션 만료 메시지 표시 후 새로고침 + alert('로그인 세션이 만료되었습니다. 다시 로그인해주세요.'); + location.reload(); }
112-113: API 호출 실패 시 페이지 새로고침 로직이 추가되었습니다.API 호출 실패 시 JWT 토큰을 제거하고 페이지를 새로고침하는 로직이 추가되었습니다. 이는 인증 상태 처리를 개선하는 좋은 변경입니다.
하지만 모든 종류의 오류에 대해 동일하게 토큰을 제거하고 새로고침하는 것이 적절한지 검토해 볼 필요가 있습니다. 네트워크 오류와 같은 일시적인 문제의 경우 토큰을 유지하는 것이 더 좋을 수 있습니다.
} catch (error) { console.error('사용자 정보 조회 실패:', error); showLoginMenu(); - localStorage.removeItem('jwt'); - location.reload(); // 오류가 발생한 경우 새로 고침 + // 401 오류인 경우에만 토큰 제거 및 새로고침 + if (error.message && error.message.includes('401')) { + localStorage.removeItem('jwt'); + location.reload(); + } }src/main/java/io/github/petty/users/dto/VerifyCodeRequest.java (1)
13-14: 주석 스타일 일관성 검토
필드에 한글 주석이 포함되어 있는데, 패키지 내 다른 DTO(EmailVerificationRequest)에는 주석이 없습니다. 주석 스타일을 통일하거나 간단한 Javadoc으로 교체하여 가독성을 높이면 좋겠습니다.src/main/java/io/github/petty/users/repository/EmailVerificationRepository.java (2)
7-11: 메서드 주석 통일성 제안
메서드 위에 한글 주석이 추가되어 있는데, 다른 리포지토리 인터페이스에는 주석이 없습니다. 코드베이스 전반의 주석 스타일을 통일하거나 제거하는 것을 권장합니다.
6-12: 이메일 컬럼 인덱스 추가 검토
findTopByEmailOrderByIdDesc조회 성능 향상을 위해 데이터베이스에 이메일 컬럼 인덱스를 추가하거나, 엔티티에@Table(indexes = @Index(...))를 정의하는 것을 검토해 보세요.src/main/java/io/github/petty/config/MailConfig.java (3)
12-22: 생성자 또는 @ConfigurationProperties 사용 고려
@Value필드 주입 대신 생성자 주입이나@ConfigurationProperties를 활용하면 테스트 용이성과 프로퍼티 관리를 개선할 수 있습니다.
26-34: 메일 인코딩 및 추가 프로퍼티 설정 제안
기본 인코딩 설정(mailSender.setDefaultEncoding("UTF-8")) 및 프로토콜, 디버그 모드 등의 추가 프로퍼티를 활용해 안정성과 디버깅 편의성을 강화해 보세요.+ mailSender.setDefaultEncoding("UTF-8"); + props.put("mail.transport.protocol", "smtp"); + props.put("mail.debug", "false");
9-37: Spring Boot 자동 설정 활용 검토
spring-boot-starter-mail이 제공하는 자동 설정으로 대부분의MailConfig를 대체할 수 있습니다. 커스텀 설정이 꼭 필요한지 재검토해 보시기 바랍니다.src/main/java/io/github/petty/users/entity/EmailVerification.java (1)
18-19: 이메일 컬럼 인덱스 검토 제안
빈번한 조회를 고려해@Table(indexes = @Index(name = "idx_email", columnList = "email"))등으로 이메일 컬럼에 인덱스를 추가하는 것을 검토해 보시기 바랍니다.src/main/java/io/github/petty/users/service/EmailService.java (2)
49-60: 이메일 전송 메소드 구현메일 전송 로직이 잘 구현되어 있습니다. 단, 몇 가지 개선 사항이 있습니다:
- 발신자 이메일 주소가 하드코딩되어 있습니다. 환경 설정으로 분리하는 것이 바람직합니다.
- 일반 텍스트 대신 HTML 템플릿을 사용하면 더 전문적인 이메일을 보낼 수 있습니다.
- helper.setFrom("krpetty54@gmail.com"); // 보내는 사람 이메일 (본인의 실제 이메일 주소로 변경) + // 설정 파일에서 발신자 이메일 주소 가져오기 + helper.setFrom(emailProperties.getSenderAddress()); - helper.setText(String.format("안녕하세요.\n\n요청하신 이메일 인증 코드는 다음과 같습니다.\n\n%s\n\n감사합니다.", code)); + // HTML 형식의 이메일 메시지 사용 + String htmlContent = String.format("<div style='font-family: Arial, sans-serif; max-width: 600px;'>" + + "<h2 style='color: #f39c12;'>PETTY</h2>" + + "<p>안녕하세요.</p>" + + "<p>요청하신 이메일 인증 코드는 다음과 같습니다.</p>" + + "<div style='padding: 10px; background-color: #f8f9fa; font-size: 24px; text-align: center; letter-spacing: 5px;'>" + + "<strong>%s</strong></div>" + + "<p>감사합니다.</p></div>", code); + helper.setText(htmlContent, true); // true 파라미터로 HTML 형식 지정
62-78: 인증 코드 확인 로직트랜잭션 처리와 코드 검증 로직이 잘 구현되어 있습니다. 검증 후 데이터 삭제를 통해 데이터베이스 관리도 적절합니다.
인증 코드 유효 시간이 5분으로 설정되어 있는데, 이 값을 상수나 설정으로 분리하면 유지보수가 더 용이해질 것입니다.
+ // 클래스 상단에 상수 추가 + private static final int VERIFICATION_CODE_EXPIRY_MINUTES = 5; - if (latestVerification != null - && latestVerification.getCode().equals(code) - && latestVerification.getCreatedAt().isAfter(LocalDateTime.now().minusMinutes(5))) { + if (latestVerification != null + && latestVerification.getCode().equals(code) + && latestVerification.getCreatedAt().isAfter(LocalDateTime.now().minusMinutes(VERIFICATION_CODE_EXPIRY_MINUTES))) {src/main/java/io/github/petty/users/controller/UsersApiController.java (2)
44-53: 이메일 인증 코드 발송 엔드포인트이메일 인증 코드 발송 엔드포인트가 잘 구현되어 있습니다. 다만 다음 개선 사항을 고려해보세요:
- 요청 본문의 유효성 검증이 누락되어 있습니다.
- 더 자세한 오류 메시지를 포함하면 클라이언트에서 디버깅이 용이해집니다.
@PostMapping("/auth/send-verification") public ResponseEntity<Map<String, Object>> sendVerification(@RequestBody EmailVerificationRequest request) { + // 이메일 유효성 검증 + if (request.getEmail() == null || request.getEmail().isEmpty()) { + Map<String, Object> errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("message", "이메일이 제공되지 않았습니다."); + return ResponseEntity.badRequest().body(errorResponse); + } // 인증 코드 생성 및 이메일 발송 로직 boolean success = emailService.sendVerificationCode(request.getEmail()); Map<String, Object> response = new HashMap<>(); response.put("success", success); + if (!success) { + response.put("message", "인증 코드 발송에 실패했습니다. 잠시 후 다시 시도해주세요."); + } return ResponseEntity.ok(response); }
55-64: 인증 코드 확인 엔드포인트인증 코드 확인 엔드포인트도 잘 구현되어 있습니다. 발송 엔드포인트와 마찬가지로 입력 유효성 검증과 상세한 오류 메시지를 추가하면 좋을 것 같습니다.
@PostMapping("/auth/verify-code") public ResponseEntity<Map<String, Object>> verifyCode(@RequestBody VerifyCodeRequest request) { + // 입력 유효성 검증 + if (request.getEmail() == null || request.getEmail().isEmpty() || + request.getVerificationCode() == null || request.getVerificationCode().isEmpty()) { + Map<String, Object> errorResponse = new HashMap<>(); + errorResponse.put("success", false); + errorResponse.put("message", "이메일과 인증 코드가 모두 필요합니다."); + return ResponseEntity.badRequest().body(errorResponse); + } // 인증 코드 검증 로직 boolean isValid = emailService.verifyCode(request.getEmail(), request.getVerificationCode()); Map<String, Object> response = new HashMap<>(); response.put("success", isValid); + if (!isValid) { + response.put("message", "인증 코드가 일치하지 않거나 만료되었습니다."); + } return ResponseEntity.ok(response); }src/main/resources/templates/join.html (4)
262-321: 반려동물 정보 섹션 (주석 처리됨)반려동물 정보 섹션이 주석 처리되어 있습니다. 향후 기능 구현을 위한 코드로 보이지만, 프로덕션 코드에 주석 처리된 큰 코드 블록은 혼란을 줄 수 있습니다. 구현 예정이면 TODO 주석을 추가하거나, 아니면 제거하는 것이 좋습니다.
360-398: 이메일 인증 발송 기능이메일 인증 발송 기능이 잘 구현되어 있습니다. 이메일 유효성 검사, 에러 처리, 그리고 UI 업데이트 로직이 모두 포함되어 있습니다.
다만, fetch 함수를 사용할 때 then/catch 체인 대신 try/catch 블록을 사용하는 방식이 일관성 없이 혼용되고 있습니다. 코드 스타일의 일관성을 유지하는 것이 바람직합니다.
- // 인증 코드 재발송 AJAX - await fetch('/api/auth/send-verification', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ email: email }) - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - resetTimer(); - alert('인증코드가 재발송되었습니다. 이메일을 확인해주세요.'); - } else { - document.getElementById('verification-error').textContent = data.message || '인증코드 재발송에 실패했습니다.'; - } - }) - .catch(error => { - console.error('Error:', error); - document.getElementById('verification-error').textContent = '서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요.'; - }); + // 인증 코드 재발송 AJAX - try/catch 스타일로 일관성 유지 + try { + const response = await fetch('/api/auth/send-verification', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ email: email }) + }); + const data = await response.json(); + + if (data.success) { + resetTimer(); + alert('인증코드가 재발송되었습니다. 이메일을 확인해주세요.'); + } else { + document.getElementById('verification-error').textContent = data.message || '인증코드 재발송에 실패했습니다.'; + } + } catch (error) { + console.error('Error:', error); + document.getElementById('verification-error').textContent = '서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요.'; + }
400-472: 인증 코드 확인 및 재발송 기능인증 코드 확인 및 재발송 기능이 잘 구현되어 있습니다. 앞서 언급한 것처럼 fetch 요청 스타일의 일관성을 유지하면 좋겠습니다.
또한, 인증 성공 후 UI 상태 변경(입력창 비활성화 등)이 잘 처리되어 있습니다.
- fetch('/api/auth/verify-code', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - email: email, - verificationCode: code - }) - }) - .then(response => response.json()) - .then(data => { - // 성공 로직... - }) - .catch(error => { - // 에러 처리... - }); + try { + const response = await fetch('/api/auth/verify-code', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + email: email, + verificationCode: code + }) + }); + const data = await response.json(); + + // 성공 로직... + } catch (error) { + // 에러 처리... + }
473-513: 타이머 기능 구현타이머 기능이 효과적으로 구현되어 있습니다. 시간 형식 포맷팅, 타이머 리셋 및 중지 기능이 모두 잘 동작합니다.
다만, 타이머 만료 시간(5분)이 하드코딩되어 있는데, 이를 상수로 분리하면 유지보수가 더 쉬워질 것입니다.
+ // 상수 정의 + const VERIFICATION_TIME_MINUTES = 5; function startTimer() { - let minutes = 5; + let minutes = VERIFICATION_TIME_MINUTES; let seconds = 0; // 나머지 코드... }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
build.gradle(1 hunks)src/main/java/io/github/petty/config/AwsGoogleCloudConfig.java(0 hunks)src/main/java/io/github/petty/config/MailConfig.java(1 hunks)src/main/java/io/github/petty/config/SupabaseDataSourceConfig.java(1 hunks)src/main/java/io/github/petty/users/controller/UsersApiController.java(2 hunks)src/main/java/io/github/petty/users/controller/UsersController.java(2 hunks)src/main/java/io/github/petty/users/dto/EmailVerificationRequest.java(1 hunks)src/main/java/io/github/petty/users/dto/JoinDTO.java(1 hunks)src/main/java/io/github/petty/users/dto/VerifyCodeRequest.java(1 hunks)src/main/java/io/github/petty/users/entity/EmailVerification.java(1 hunks)src/main/java/io/github/petty/users/entity/Users.java(1 hunks)src/main/java/io/github/petty/users/jwt/JWTFilter.java(1 hunks)src/main/java/io/github/petty/users/jwt/JWTUtil.java(1 hunks)src/main/java/io/github/petty/users/repository/EmailVerificationRepository.java(1 hunks)src/main/java/io/github/petty/users/service/EmailService.java(1 hunks)src/main/java/io/github/petty/users/service/JoinService.java(2 hunks)src/main/resources/templates/index.html(4 hunks)src/main/resources/templates/join.html(1 hunks)
💤 Files with no reviewable changes (1)
- src/main/java/io/github/petty/config/AwsGoogleCloudConfig.java
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/main/java/io/github/petty/users/dto/VerifyCodeRequest.java (2)
src/main/java/io/github/petty/users/dto/EmailVerificationRequest.java (1)
Getter(8-14)src/main/java/io/github/petty/users/dto/JoinDTO.java (1)
Getter(8-16)
src/main/java/io/github/petty/users/dto/EmailVerificationRequest.java (2)
src/main/java/io/github/petty/users/dto/JoinDTO.java (1)
Getter(8-16)src/main/java/io/github/petty/users/dto/VerifyCodeRequest.java (1)
Getter(8-15)
src/main/java/io/github/petty/users/service/EmailService.java (1)
src/main/java/io/github/petty/users/service/JoinService.java (1)
Service(11-40)
src/main/java/io/github/petty/users/entity/EmailVerification.java (3)
src/main/java/io/github/petty/users/dto/EmailVerificationRequest.java (1)
Getter(8-14)src/main/java/io/github/petty/users/dto/VerifyCodeRequest.java (1)
Getter(8-15)src/main/java/io/github/petty/users/entity/Users.java (1)
Entity(16-31)
🔇 Additional comments (25)
build.gradle (1)
57-58: 이메일 발송 기능 추가를 위한 적절한 의존성 추가입니다.Spring Boot Mail Starter를 추가하여 SMTP 기반 이메일 발송 기능을 구현할 수 있게 되었습니다. PR 목표에 기술된 이메일 인증 시스템 구현에 필요한 적절한 변경사항입니다.
src/main/java/io/github/petty/users/service/JoinService.java (1)
22-22: 전화번호 필드 추출이 추가되었습니다.DTO에서 전화번호를 추출하는 로직이 추가된 것을 확인했습니다.
src/main/resources/templates/index.html (2)
52-56: 이미지 업로드 폼 들여쓰기가 수정되었습니다.폼 요소의 들여쓰기가 개선되어 코드 가독성이 향상되었습니다.
71-71:Details
❓ Verification inconclusive
메시지 표시 태그가 h1에서 h2로 변경되었습니다.
동일한 메시지가, line 49의 h1 태그와 중복으로 표시되고 있습니다. 이는 의도된 것인지 확인이 필요합니다.
49행과 71행에서 동일한
${message}변수를 사용하고 있습니다. 이는 중복으로 보이며, 하나를 제거하거나 서로 다른 변수를 사용하는 것이 좋을 것 같습니다.
🏁 Script executed:
#!/bin/bash # 다른 템플릿에서 message 사용 방식 확인 rg -l "th:text=\"\${message}\"" --type html src/main/resources/templatesLength of output: 185
템플릿에서
${message}사용 위치를 정확히 확인하기 위해 아래 명령어를 실행해주세요.
🏁 Script executed:
#!/bin/bash # src/main/resources/templates 내 ${message} 사용 위치 확인 rg -n '\$\{message\}' --type html src/main/resources/templatesLength of output: 487
index.html 메시지 중복 렌더링 확인 필요
index.html에서 동일한${message}변수가 두 번 출력되고 있습니다. 다른 템플릿(visionUpload.html, recommend.html)에서도${message}를 사용 중이므로, 이 중복이 의도된 동작인지 검토해 주세요.
- src/main/resources/templates/index.html
• 49행:<h1 th:text="${message}">PETTY에 오신 것을 환영합니다</h1>
• 71행:<h2 th:text="${message}">index message</h2>의도되지 않은 중복이라면, 다음 중 하나를 고려해 주세요:
- 중복 출력되는 태그(h2 또는 h1) 제거
- 서로 다른 변수를 사용하여 역할 분리
검토 후 조치 부탁드립니다.
src/main/java/io/github/petty/users/dto/VerifyCodeRequest.java (1)
1-15: DTO 구조 적절함
Lombok을 활용한 단순 DTO 구현이 올바르며, 직렬화/역직렬화 과정에서 필수 필드를 적절히 전달합니다.src/main/java/io/github/petty/users/repository/EmailVerificationRepository.java (1)
1-12: 리포지토리 인터페이스 검토 완료
Spring Data JPA를 활용해EmailVerification엔티티에 대한 기본 CRUD 및 커스텀 조회/삭제 메서드가 올바르게 선언되었습니다.src/main/java/io/github/petty/config/MailConfig.java (1)
1-9: 기본 MailConfig 설정 확인
JavaMailSender빈 생성 및 SMTP 호스트/포트/인증 설정 로직이 적절하게 구현되었습니다.src/main/java/io/github/petty/users/dto/EmailVerificationRequest.java (1)
1-14: DTO 구조 적절함
단일 필드 DTO가 Lombok을 통해 올바르게 생성되었으며, 직렬화/역직렬화에 문제가 없어 보입니다.src/main/java/io/github/petty/users/entity/EmailVerification.java (2)
1-25: 엔티티 매핑 기본 검토
@Entity,@Id,@CreationTimestamp등 JPA 매핑이 올바르게 선언되었으며, 이메일 인증 코드 저장용으로 적절합니다.
14-16: ID 전략 일관성 확인 요청
다른 주요 엔티티(Users)는GenerationType.UUID를 사용하는 반면, 여기서는IDENTITY를 사용하고 있습니다. 전체 도메인 모델에서 ID 생성 전략이 일관된지 확인해 주세요.src/main/java/io/github/petty/users/controller/UsersController.java (4)
8-8: Model 추가를 통한 뷰 데이터 준비Model을 import 하여 JoinDTO 객체를 뷰에 전달할 수 있게 되었습니다.
24-25: JoinDTO 모델 추가로 폼 바인딩 개선JoinDTO 객체를 모델에 추가하여 폼 바인딩을 구현한 것은 좋은 방식입니다. 이를 통해 이메일 인증 및 기타 등록 필드의 데이터 바인딩이 가능해졌습니다.
29-29: @PostMapping 경로 수정URL 패턴 앞에 슬래시(/)를 추가하여 표준 URL 형식을 준수했습니다.
34-35: 오류 메시지 및 리다이렉트 경로 개선오류 메시지가 더 구체적으로 변경되어 사용자에게 명확한 정보를 제공합니다. 리다이렉트 경로를 루트('/')로 변경한 것도 사용자 흐름 개선에 도움이 됩니다.
src/main/java/io/github/petty/users/service/EmailService.java (2)
17-25: 이메일 서비스 구조 설정Lombok 어노테이션과 의존성 주입을 적절히 사용하여 서비스 클래스를 구성했습니다. Random 객체를 필드로 선언한 것은 효율적인 선택입니다.
44-47: 인증 코드 생성 방식4자리 랜덤 코드를 생성하는 방식이 단순하고 명확합니다. 그러나 앞서 언급했듯이 보안 강화를 위해 코드 길이와 복잡성을 높이는 것을 고려해보세요.
src/main/java/io/github/petty/users/controller/UsersApiController.java (4)
3-6: 필요한 DTO 및 서비스 클래스 임포트이메일 인증 관련 DTO 클래스와 서비스 클래스를 적절히 임포트했습니다.
18-18: API 경로 변경기본 경로를 '/api/users'에서 '/api'로 변경했습니다. 이는 API 구조 재설계를 의미하며, 기존 클라이언트에 영향을 미칠 수 있습니다. 변경 사유와 클라이언트 대응 방안을 고려해보세요.
22-27: EmailService 의존성 추가EmailService를 컨트롤러에 의존성으로 추가하고 생성자를 통해 주입하는 방식이 적절합니다.
29-29: 사용자 정보 엔드포인트 경로 변경사용자 정보 조회 엔드포인트 경로가 '/me'에서 '/users/me'로 변경되었습니다. API 리팩토링의 일환으로 보이며 논리적으로 타당한 변경입니다.
src/main/resources/templates/join.html (5)
1-133: 회원가입 페이지 기본 구조 및 스타일페이지 구조와 스타일이 잘 구성되어 있습니다. 반응형 디자인을 고려한 스타일링이 적용되어 있어 사용자 경험이 향상될 것으로 보입니다.
134-210: 이용약관 섹션 구현이용약관 섹션이 상세하게 구현되어 있어 법적 측면에서 완성도가 높습니다. 체크박스와 동의 버튼 비활성화 로직도 잘 구현되어 있습니다.
211-261: 회원 정보 입력 폼 구현회원 정보 입력 폼이 잘 구성되어 있습니다. 이메일 인증 기능, 비밀번호 확인, 그리고 연락처 입력 필드 등 필요한 요소들이 모두 포함되어 있습니다.
327-359: 약관 동의 및 비밀번호 확인 로직약관 동의 체크박스 이벤트와 비밀번호 확인 검증 로직이 잘 구현되어 있습니다. 코드가 명확하고 이해하기 쉽습니다.
514-556: 폼 제출 전 유효성 검사폼 제출 전 유효성 검사가 잘 구현되어 있습니다. 이메일 인증, 비밀번호 정규식, 비밀번호 확인, 전화번호 형식 검사 등 필요한 모든 검증이 포함되어 있습니다.
비밀번호 정규식이 적절한 보안 수준을 요구하고 있어 좋습니다.
| .claim("role", role) | ||
| .issuedAt(new Date(System.currentTimeMillis())) | ||
| .expiration(new Date(System.currentTimeMillis() + expiredMs)) | ||
| .expiration(new Date(System.currentTimeMillis() + expiredMs * 1000)) |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
토큰 만료 시간 계산 방식 변경에 대한 주의가 필요합니다.
expiredMs 파라미터의 의미가 밀리초에서 초 단위로 변경되었습니다. 이는 토큰 유효 시간을 1000배 증가시키는 변화입니다. 이 메서드를 호출하는 모든 코드가 이 변경을 인지하고 적절히 수정되었는지 확인해야 합니다.
다음 스크립트를 실행하여 createJwt 메서드를 호출하는 모든 위치를 확인하세요:
🏁 Script executed:
#!/bin/bash
# createJwt 메서드 호출 위치 찾기
grep -r "createJwt" --include="*.java" src/main/javaLength of output: 304
파라미터 단위(초) 명세화 필요
현재 createJwt(String username, String role, Long expiredMs)는 내부에서 expiredMs * 1000으로 처리하며, 파라미터가 초 단위임을 가정하고 있습니다. 호출 위치를 확인한 결과, 아래 한 곳에서만 사용 중이며 초 단위 값(60*60L)을 전달하므로 기능상 문제는 없습니다.
- src/main/java/io/github/petty/users/jwt/LoginFilter.java:
jwtUtil.createJwt(username, role, 60*60L);
그러나 파라미터명·문서화가 초 단위임을 명확히 하지 않으면 혼동을 초래할 수 있습니다.
다음 중 하나를 적용해 주세요:
- 메서드 시그니처 변경
- public String createJwt(String username, String role, Long expiredMs) { + public String createJwt(String username, String role, Long expiredInSeconds) { … .expiration(new Date(System.currentTimeMillis() + expiredInSeconds * 1000))
- 또는 Javadoc/코드 주석으로
expiredMs가 “초 단위(expiredInSeconds)”임을 명시
📝 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.
| .expiration(new Date(System.currentTimeMillis() + expiredMs * 1000)) | |
| public String createJwt(String username, String role, Long expiredInSeconds) { | |
| … | |
| - .expiration(new Date(System.currentTimeMillis() + expiredMs * 1000)) | |
| + .expiration(new Date(System.currentTimeMillis() + expiredInSeconds * 1000)) | |
| … | |
| } |
| // ddl.auto | ||
| jpaProperties.put("hibernate.hbm2ddl.auto", "update"); | ||
|
|
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Hibernate DDL 자동 업데이트 설정이 추가되었습니다.
hibernate.hbm2ddl.auto를 update로 설정한 것은 개발 환경에서는 편리하지만, 프로덕션 환경에서는 위험할 수 있습니다.
프로덕션 환경에서는 validate로 변경하거나 환경별 설정을 분리하는 것이 안전합니다. 또한 데이터베이스 스키마 변경을 위해 Flyway나 Liquibase와 같은 마이그레이션 도구 사용을 고려해보세요.
// ddl.auto
-jpaProperties.put("hibernate.hbm2ddl.auto", "update");
+// 환경별 설정 분리
+String ddlAuto = "validate"; // 기본값은 validate로 설정
+// 개발 환경에서만 update 사용
+if (environment.getActiveProfiles().length > 0 &&
+ (environment.getActiveProfiles()[0].equals("dev") ||
+ environment.getActiveProfiles()[0].equals("local"))) {
+ ddlAuto = "update";
+}
+jpaProperties.put("hibernate.hbm2ddl.auto", ddlAuto);🌐 Web query:
Spring Boot production environment hibernate.hbm2ddl.auto best practices
💡 Result:
Best Practices for hibernate.hbm2ddl.auto in Spring Boot Production Environments
In a Spring Boot application that uses Hibernate, the hibernate.hbm2ddl.auto property controls how Hibernate handles database schema management at application startup. This setting is critical for production environments, and misconfiguration can lead to data loss or serious schema integrity issues.
Recommended Setting in Production
-
Best Practice:
Always setspring.jpa.hibernate.ddl-auto(or directly,hibernate.hbm2ddl.auto) tovalidateornonein production environments.validate: Hibernate checks that the schema matches the entity mappings. If there is a mismatch, the application will fail to start, ensuring that accidental schema drift is detected before it results in runtime errors or data corruption.none: Disables all schema management; Hibernate will not check or modify the schema at all.
"While generating the DDL scripts with this Hibernate tool can be useful only to determine the DB schema that Hibernate expects for the current JPA entity mappings, it’s best to use an automatic schema management tool, like Flyway, to manage your database schema. However, the Hibernate
SchemaManagementToolalso offers avalidatestrategy that can verify whether the JPA entity mappings are compatible with the underlying database schema" [5].
What to Avoid in Production
-
Never use these settings in production:
createorcreate-drop: These drop and recreate your database schema on application start (and stop), which can result in irreversible data loss.update: This attempts to automatically change the schema to match your entities. It is not reliable for complex changes, may not handle all edge cases, and can introduce subtle bugs or performance issues such as unnecessary index drops and recreations[3][6].
"Although Hibernate provides the
updateoption for thehibernate.hbm2ddl.autoconfiguration property, this feature is not suitable for a production environment."
"You should always use an automatic schema migration tool and have all the migration scripts stored in the Version Control System." [6]
Production-Grade Schema Management Workflow
- Use database migration tools such as Flyway or Liquibase to manage schema changes.
- All schema migrations (CREATE/ALTER statements) should be scripted, version-controlled, and thoroughly tested in non-production environments before deployment.
- Rely on
validatein production as a safety net to catch schema drift between your entities and the actual database schema. - Do not allow Hibernate to perform automatic schema generation, updates, or drops in production; this is suitable for development and testing only[1][9].
Summary Table
| Setting | Production Suitability | Behavior |
|---|---|---|
none |
✅ Recommended | No changes or checks made to schema, safest in production if migrations are externally managed. |
validate |
✅ Recommended | Schema is checked for compatibility with entities. Application fails on mismatch. |
update |
❌ Not Recommended | Tries to update schema automatically; risky and can cause data corruption. |
create / create-drop |
❌ Dangerous | Drops and recreates schema; will result in data loss. |
Additional Notes
- Recent versions of Spring Boot may require explicit property configuration to ensure Hibernate does not manage the schema, especially when using external migration tools[4][8].
- Always test your migration scripts and entity-to-schema compatibility in a staging environment mirroring production.
Conclusion
In Spring Boot production environments, set spring.jpa.hibernate.ddl-auto to validate or none. Use a dedicated tool like Flyway or Liquibase for all migrations. Avoid update, create, and create-drop to prevent unintended schema changes or data loss.[5][6]
Citations:
- 1: http://vladmihalcea.com/hibernate-hbm2ddl-auto-schema/
- 2: https://docs.spring.io/spring-boot/docs/1.1.0.M1/reference/html/howto-database-initialization.html
- 3: https://discourse.hibernate.org/t/hbm2ddl-auto-update-recreating-existing-indexes-after-drop/3991
- 4: Hibernate ignores
spring.jpa.hibernate.ddl-auto:nonewith Liquibase spring-projects/spring-data-jpa#3023 - 5: https://vladmihalcea.com/validate-ddl-schema-spring-hibernate/
- 6: https://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/appendices/BestPractices.html
- 7: https://www.thomasvitale.com/spring-data-jpa-hibernate-java-configuration/
- 8: Simplify JPA DDL properties and auto-configuration spring-projects/spring-boot#40177
- 9: https://www.geeksforgeeks.org/automatic-table-creation-using-hibernate/
- 10: https://discourse.hibernate.org/t/hibernate-multitenancy-and-hbm2ddl-auto/6411
Hibernate DDL 자동 업데이트 설정 변경 필요
프로덕션 환경에서 hibernate.hbm2ddl.auto=update는 데이터 무결성·성능 문제를 일으킬 수 있으므로, 아래와 같이 조치하세요.
-
대상 파일 및 위치
src/main/java/io/github/petty/config/SupabaseDataSourceConfig.java- 70–72라인:
jpaProperties.put("hibernate.hbm2ddl.auto", "update");
-
권장 설정
- 프로덕션 기본값을
validate또는none으로 변경 - 개발(local/dev) 환경에서만
update사용하도록 프로파일 분리 - Flyway 또는 Liquibase로 스키마 마이그레이션 관리
- 프로덕션 기본값을
-
예시 리팩토링
// ddl.auto
- jpaProperties.put("hibernate.hbm2ddl.auto", "update");
+ // 프로덕션: validate, 개발: update
+ String ddlAuto = "validate"; // 프로덕션 기본
+ List<String> profiles = List.of(environment.getActiveProfiles());
+ if (profiles.contains("dev") || profiles.contains("local")) {
+ ddlAuto = "update";
+ }
+ jpaProperties.put("hibernate.hbm2ddl.auto", ddlAuto);📝 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.
| // ddl.auto | |
| jpaProperties.put("hibernate.hbm2ddl.auto", "update"); | |
| // ddl.auto | |
| // 프로덕션: validate, 개발: update | |
| String ddlAuto = "validate"; // 프로덕션 기본 | |
| List<String> profiles = List.of(environment.getActiveProfiles()); | |
| if (profiles.contains("dev") || profiles.contains("local")) { | |
| ddlAuto = "update"; | |
| } | |
| jpaProperties.put("hibernate.hbm2ddl.auto", ddlAuto); |
| private String email; // 사용자 이메일 | ||
| private String verificationCode; // 인증 코드 |
There was a problem hiding this comment.
🛠️ Refactor suggestion
입력값 검증 애노테이션 추가 제안
DTO에 스프링 Bean Validation 애노테이션(@Email, @NotBlank, @Size 등)을 활용하여 이메일 형식과 인증 코드 길이(예: 4자리 숫자)를 검증하도록 개선하는 것을 권장합니다.
| EmailVerification findTopByEmailOrderByIdDesc(String email); | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Optional 반환 고려
findTopByEmailOrderByIdDesc가 일치하는 레코드가 없을 경우 null이 반환됩니다. Optional<EmailVerification>을 반환하도록 변경하면 호출부에서 NPE 위험을 방지할 수 있습니다.
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| public class EmailVerificationRequest { | ||
| private String email; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
이메일 검증 애노테이션 추가 제안
@Email, @NotBlank 등의 Bean Validation 애노테이션을 추가하여 잘못된 이메일 입력을 사전에 방지하도록 개선할 수 있습니다.
| @Column(nullable = false) | ||
| private String email; | ||
| @Column(nullable = false) | ||
| private String code; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
인증 코드 길이 제한 추가 제안
code 필드는 4자리 숫자 코드만 허용되므로 @Column(length = 4) 또는 Bean Validation의 @Size(min=4, max=4)를 활용해 데이터 무결성을 강화하는 것을 권장합니다.
| // 인증 코드 생성 + 저장 + 이메일 전송 | ||
| public boolean sendVerificationCode(String email) { | ||
| String code = generateCode(); | ||
|
|
||
| EmailVerification verification = new EmailVerification(); | ||
| verification.setEmail(email); | ||
| verification.setCode(code); | ||
| emailVerificationRepository.save(verification); | ||
|
|
||
| try { | ||
| sendEmail(email, code); | ||
| return true; | ||
| } catch (MessagingException e) { | ||
| log.error("이메일 전송 실패: {}", e.getMessage()); | ||
| return false; | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
인증 코드 생성 및 이메일 전송 로직
인증 코드 생성, 저장, 전송을 하나의 메소드로 처리한 것은 좋은 설계입니다. 예외 처리와 로깅도 잘 구현되었습니다.
다만, 보안 측면에서 4자리 숫자만으로 구성된 인증 코드는 브루트 포스 공격에 취약할 수 있습니다. 더 긴 코드나 영숫자 조합을 고려해보세요.
- private String generateCode() {
- return String.format("%04d", random.nextInt(10000)); // 0000 ~ 9999
- }
+ private String generateCode() {
+ // 6자리 숫자 또는 영숫자 조합으로 보안 강화
+ return String.format("%06d", random.nextInt(1000000)); // 000000 ~ 999999
+ }📝 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.
| // 인증 코드 생성 + 저장 + 이메일 전송 | |
| public boolean sendVerificationCode(String email) { | |
| String code = generateCode(); | |
| EmailVerification verification = new EmailVerification(); | |
| verification.setEmail(email); | |
| verification.setCode(code); | |
| emailVerificationRepository.save(verification); | |
| try { | |
| sendEmail(email, code); | |
| return true; | |
| } catch (MessagingException e) { | |
| log.error("이메일 전송 실패: {}", e.getMessage()); | |
| return false; | |
| } | |
| } | |
| private String generateCode() { | |
| // 6자리 숫자 또는 영숫자 조합으로 보안 강화 | |
| return String.format("%06d", random.nextInt(1000000)); // 000000 ~ 999999 | |
| } |
s0ooo0k
left a comment
There was a problem hiding this comment.
✅ 리뷰
- 코드 확인했습니다! 전반적으로 로직이 고민을 많이 했다는 것이 느껴지고, 회원가입, 검증, 생성 흐름이 이해하기 쉽고, 사용자 관점에서 자연스러운 진행이 될 거 같습니다.
- 특히 401 예외 처리, 인증 관련 예외 처리 등 실제 서비스에서 일어날 수 있는 부분을 체크하여 예외 처리한 것이 인상깊었습니다.
- SMTP 및 JWT를 깊이 배우지 않은 상태에서 인증 로직을 직접 설계하고 구현했다는 점에서, 어려우셨을텐데 고생하셨다는 리뷰 남깁니다
🔧 제안 및 아이디어
- https://github.com/PETTY-HUB/PETTY-BACK/pull/15/files#r2053261584
로직상 update로 설정되어야 하는 것이 맞는 거 같은데, 배포 과정에선 이슈가 있을 수 있어 해당 파트만 주의 깊게 확인해주시면 될 거 같습니다! - 만료 시간과 같이 yml 환경 변수로 분리할 수 있는 부분은 분리하면 더 좋을 거 같습니다.
- 추후 테스트 진행 후 이슈 있으면 공유드리겠습니다. 고생하셨습니다!
| @@ -67,6 +67,9 @@ public LocalContainerEntityManagerFactoryBean supabaseEntityManagerFactory( | |||
| Map<String, Object> jpaProperties = new HashMap<>(); | |||
| jpaProperties.put("hibernate.physical_naming_strategy", "org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy"); | |||
There was a problem hiding this comment.
해당 파트가 Config에서 지정되어 있는데, 추가로도 지정을 해야하는지 궁금합니다!
There was a problem hiding this comment.
jpaProperties.put("hibernate.hbm2ddl.auto", "update");가 없으면 DB 테이블이 엔티티 구조에 맞게 변경되지 않습니다
추후에는 value로 가져다가 쓸 예정입니다.
There was a problem hiding this comment.
확인했습니다! 추후 리팩토링 기대하겠습니다. 머지하겠습니다
There was a problem hiding this comment.
해당 파트의 인증 코드 생성 > 발송 > 검증 로직이 리뷰어들도 이해하기 쉽게 잘 구성되어 있어 인상깊었습니다.
✅ 코드 리뷰1. 이메일 인증
2. JWT 인증
3. 회원가입
4. 기타
전체적으로 기능 잘 구현하신 것 같습니다! |
|
@23MinL |
[USER] 회원가입 이메일 인증 시스템 구현, JWT 인증 로직 개선
📜 PR 내용 요약
⚒️ 작업 및 변경 내용
JWT 인증 개선
이메일 인증 시스템
📚 기타 참고 사항
리뷰 포인트
환경 설정 관련
application-secret.yml에 Gmail 계정 및 앱 비밀번호 설정을 추가해야 합니다