Skip to content

[USER] 회원가입 이메일 인증 시스템 구현, JWT 인증 로직 개선#15

Merged
s0ooo0k merged 3 commits into
PETTY-HUB:mainfrom
taehyun32:feat/auth
Apr 22, 2025
Merged

[USER] 회원가입 이메일 인증 시스템 구현, JWT 인증 로직 개선#15
s0ooo0k merged 3 commits into
PETTY-HUB:mainfrom
taehyun32:feat/auth

Conversation

@taehyun32
Copy link
Copy Markdown
Member

📜 PR 내용 요약

  • JWT 인증 실패(401) 처리 로직 개선 및 토큰 만료 시간을 수정했습니다.
  • 이메일 인증 코드 생성 및 Gmail SMTP를 통한 발송 기능을 구현했습니다.
  • 이메일 인증 코드 검증 및 회원가입 프로세스 완료 기능을 구현했습니다.
sequenceDiagram
  participant User as 사용자
  participant Web as PETTY
  participant Gmail as Gmail SMTP
  User->>Web: 회원가입 요청(이메일, 비밀번호, 이름, 전화번호)
  Web->>Web: 랜덤 인증 코드 생성
  Web->>Web: 인증 코드 DB 저장
  Web->>Gmail: 인증 코드 이메일 발송
  Gmail->>User: 인증 코드 수신
  User->>Web: 인증 코드 제출
  Web->>Web: DB의 인증 코드와 비교 검증
  Web->>User: 회원가입 완료
Loading

⚒️ 작업 및 변경 내용

JWT 인증 개선

  • 토큰 만료 시간 조정으로 사용자 경험 개선
  • 401 오류 발생 시 명확한 에러 메시지 및 처리 로직 구현
  • 클라이언트 측 JWT 만료 감지 및 재로그인 로직 추가

이메일 인증 시스템

  • 이메일 인증 코드 생성 기능
    • 4자리 랜덤 숫자 인증 코드 생성 (RandomInt 활용)
    • 생성된 인증 코드 DB에 저장
  • Gmail SMTP를 활용한 이메일 발송 서비스
    • Spring Mail을 활용한 인증 이메일 발송 기능
    • HTML 템플릿 기반 이메일 본문 작성
  • 인증 코드 검증 프로세스
    • 사용자 제출 코드와 서버 저장 코드 비교 검증
    • 인증 성공 시 회원가입 완료 처리
    • 인증 코드 유효시간 제한으로 보안 강화

📚 기타 참고 사항

리뷰 포인트

  • JWT 만료 시간 설정의 적절성 검토
  • 이메일 인증 코드의 보안성(길이, 복잡도) 검토
  • 인증 코드 만료 시간 및 재발송 기능 검토

환경 설정 관련

  • Gmail SMTP 서버 연결을 위한 앱 비밀번호 설정이 필요합니다
  • application-secret.yml에 Gmail 계정 및 앱 비밀번호 설정을 추가해야 합니다

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2025

Summary by CodeRabbit

  • 신규 기능

    • 이메일 인증 기능이 추가되어 회원가입 시 인증 코드 발송 및 확인이 가능합니다.
    • 회원 정보에 휴대폰 번호 입력란이 추가되었습니다.
    • 이메일 인증을 위한 UI와 타이머, 클라이언트 유효성 검사 등이 회원가입 페이지에 적용되었습니다.
  • 버그 수정

    • JWT 토큰 만료 시 401 오류와 함께 적절한 에러 메시지가 반환됩니다.
  • 기능 개선

    • 회원가입 실패 시 보다 구체적인 오류 메시지가 제공됩니다.
    • 회원가입 및 로그인 관련 UI가 개선되었습니다.
    • 데이터베이스 스키마가 자동으로 업데이트되도록 설정되었습니다.
  • 기타

    • AWS 및 Google Vision 관련 설정이 제거되었습니다.
    • 불필요한 코드와 설정이 정리되었습니다.

Walkthrough

이번 변경에서는 이메일 인증 기능이 새롭게 도입되어, 사용자 가입 시 이메일로 인증 코드를 발송하고 검증할 수 있도록 구현되었습니다. 이를 위해 메일 발송 설정(Spring Boot Mail Starter 및 MailConfig), 이메일 인증 엔티티 및 레포지토리, 서비스, DTO, 컨트롤러 엔드포인트가 추가되었습니다. 회원가입 폼과 로직이 전화번호 입력 및 이메일 인증 절차를 반영하도록 확장되었으며, 기존 AWS/Google Vision 관련 설정 파일은 삭제되었습니다. JWT 만료 처리 방식도 개선되었습니다.

Changes

파일/경로 요약 변경 요약
build.gradle spring-boot-starter-mail 의존성 추가로 SMTP 메일 발송 지원
src/main/java/io/github/petty/config/AwsGoogleCloudConfig.java AWS Rekognition 및 Google Vision 클라이언트 빈 설정 클래스 삭제
src/main/java/io/github/petty/config/MailConfig.java SMTP 정보 기반 JavaMailSender 빈을 생성하는 메일 설정 클래스 추가
src/main/java/io/github/petty/config/SupabaseDataSourceConfig.java Hibernate 자동 스키마 업데이트(hbm2ddl.auto=update) 설정 추가
src/main/java/io/github/petty/users/controller/UsersApiController.java 이메일 인증 코드 전송/검증 엔드포인트 추가, 기존 엔드포인트 경로 변경, EmailService 주입
src/main/java/io/github/petty/users/controller/UsersController.java 회원가입 폼에 Model 파라미터 추가, 실패시 메시지 및 리다이렉트 경로 변경, PostMapping 경로 수정
src/main/java/io/github/petty/users/dto/EmailVerificationRequest.java 이메일 인증 요청 DTO 신설
src/main/java/io/github/petty/users/dto/JoinDTO.java phone(전화번호) 필드 추가
src/main/java/io/github/petty/users/dto/VerifyCodeRequest.java 이메일 및 인증 코드 필드를 가진 인증 코드 검증용 DTO 신설
src/main/java/io/github/petty/users/entity/EmailVerification.java 이메일 인증 코드 및 생성 시각을 저장하는 JPA 엔티티 추가
src/main/java/io/github/petty/users/entity/Users.java phone(전화번호) 필드 추가
src/main/java/io/github/petty/users/jwt/JWTFilter.java JWT 만료시 401 응답 및 JSON 에러 메시지 반환으로 변경
src/main/java/io/github/petty/users/jwt/JWTUtil.java JWT 만료 시간 계산 방식(초 단위로 변경) 수정
src/main/java/io/github/petty/users/repository/EmailVerificationRepository.java 이메일 인증 엔티티용 JPA 레포지토리 및 쿼리 메소드 추가
src/main/java/io/github/petty/users/service/EmailService.java 인증 코드 생성, 저장, 이메일 발송, 인증 코드 검증 및 삭제 기능의 서비스 클래스 추가
src/main/java/io/github/petty/users/service/JoinService.java 회원가입 시 전화번호 저장 로직 추가
src/main/resources/templates/index.html 에러 메시지 표시, JWT 만료시 페이지 리로드, 마크업 및 자바스크립트 일부 개선
src/main/resources/templates/join.html 약관 동의, 이메일 인증, 전화번호 등 확장된 회원가입 폼 및 UI/UX 전면 개편, 클라이언트 검증 및 인증 타이머 포함

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 }
Loading

Possibly related PRs

  • PETTY-HUB/PETTY-BACK#6: AwsGoogleCloudConfig 클래스를 추가했던 PR로, 이번 PR에서 해당 클래스가 삭제되어 직접적으로 관련됨.
  • PETTY-HUB/PETTY-BACK#12: AwsGoogleCloudConfig.java를 주석 처리하여 비활성화한 PR로, 이번 PR에서 해당 파일이 완전히 삭제되어 유사한 설정 변경과 관련됨.

Poem

🐇
이메일이 날아간다, 인증 코드와 함께
가입 폼도 새 옷 입고, 전화번호도 챙겼네!
토끼는 깡총깡총, 코드를 확인해
약관도 꼼꼼히 읽고, 타이머도 똑딱똑딱
이제는 더 안전하게, 모두 함께 가입해요!
🥕📧✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@taehyun32
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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: 이메일 전송 메소드 구현

메일 전송 로직이 잘 구현되어 있습니다. 단, 몇 가지 개선 사항이 있습니다:

  1. 발신자 이메일 주소가 하드코딩되어 있습니다. 환경 설정으로 분리하는 것이 바람직합니다.
  2. 일반 텍스트 대신 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: 이메일 인증 코드 발송 엔드포인트

이메일 인증 코드 발송 엔드포인트가 잘 구현되어 있습니다. 다만 다음 개선 사항을 고려해보세요:

  1. 요청 본문의 유효성 검증이 누락되어 있습니다.
  2. 더 자세한 오류 메시지를 포함하면 클라이언트에서 디버깅이 용이해집니다.
@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

📥 Commits

Reviewing files that changed from the base of the PR and between 8f8419b and e109bce.

📒 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/templates

Length of output: 185


템플릿에서 ${message} 사용 위치를 정확히 확인하기 위해 아래 명령어를 실행해주세요.


🏁 Script executed:

#!/bin/bash
# src/main/resources/templates 내 ${message} 사용 위치 확인
rg -n '\$\{message\}' --type html src/main/resources/templates

Length 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))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

토큰 만료 시간 계산 방식 변경에 대한 주의가 필요합니다.

expiredMs 파라미터의 의미가 밀리초에서 초 단위로 변경되었습니다. 이는 토큰 유효 시간을 1000배 증가시키는 변화입니다. 이 메서드를 호출하는 모든 코드가 이 변경을 인지하고 적절히 수정되었는지 확인해야 합니다.

다음 스크립트를 실행하여 createJwt 메서드를 호출하는 모든 위치를 확인하세요:


🏁 Script executed:

#!/bin/bash
# createJwt 메서드 호출 위치 찾기
grep -r "createJwt" --include="*.java" src/main/java

Length 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.

Suggested change
.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))
}

Comment on lines +70 to +72
// ddl.auto
jpaProperties.put("hibernate.hbm2ddl.auto", "update");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Hibernate DDL 자동 업데이트 설정이 추가되었습니다.

hibernate.hbm2ddl.autoupdate로 설정한 것은 개발 환경에서는 편리하지만, 프로덕션 환경에서는 위험할 수 있습니다.

프로덕션 환경에서는 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 set spring.jpa.hibernate.ddl-auto (or directly, hibernate.hbm2ddl.auto) to validate or none in 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 SchemaManagementTool also offers a validate strategy 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:

    • create or create-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 update option for the hibernate.hbm2ddl.auto configuration 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 validate in 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:


Hibernate DDL 자동 업데이트 설정 변경 필요

프로덕션 환경에서 hibernate.hbm2ddl.auto=update는 데이터 무결성·성능 문제를 일으킬 수 있으므로, 아래와 같이 조치하세요.

  • 대상 파일 및 위치

    • src/main/java/io/github/petty/config/SupabaseDataSourceConfig.java
    • 70–72라인: jpaProperties.put("hibernate.hbm2ddl.auto", "update");
  • 권장 설정

    1. 프로덕션 기본값을 validate 또는 none으로 변경
    2. 개발(local/dev) 환경에서만 update 사용하도록 프로파일 분리
    3. 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.

Suggested change
// 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);

Comment on lines +13 to +14
private String email; // 사용자 이메일
private String verificationCode; // 인증 코드
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

입력값 검증 애노테이션 추가 제안
DTO에 스프링 Bean Validation 애노테이션(@Email, @NotBlank, @Size 등)을 활용하여 이메일 형식과 인증 코드 길이(예: 4자리 숫자)를 검증하도록 개선하는 것을 권장합니다.

Comment on lines +8 to +9
EmailVerification findTopByEmailOrderByIdDesc(String email);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optional 반환 고려
findTopByEmailOrderByIdDesc가 일치하는 레코드가 없을 경우 null이 반환됩니다. Optional<EmailVerification>을 반환하도록 변경하면 호출부에서 NPE 위험을 방지할 수 있습니다.

@NoArgsConstructor
@AllArgsConstructor
public class EmailVerificationRequest {
private String email;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

이메일 검증 애노테이션 추가 제안
@Email, @NotBlank 등의 Bean Validation 애노테이션을 추가하여 잘못된 이메일 입력을 사전에 방지하도록 개선할 수 있습니다.

Comment on lines +18 to +21
@Column(nullable = false)
private String email;
@Column(nullable = false)
private String code;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

인증 코드 길이 제한 추가 제안
code 필드는 4자리 숫자 코드만 허용되므로 @Column(length = 4) 또는 Bean Validation의 @Size(min=4, max=4)를 활용해 데이터 무결성을 강화하는 것을 권장합니다.

Comment on lines +26 to +42
// 인증 코드 생성 + 저장 + 이메일 전송
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;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

인증 코드 생성 및 이메일 전송 로직

인증 코드 생성, 저장, 전송을 하나의 메소드로 처리한 것은 좋은 설계입니다. 예외 처리와 로깅도 잘 구현되었습니다.

다만, 보안 측면에서 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.

Suggested change
// 인증 코드 생성 + 저장 + 이메일 전송
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
}

Copy link
Copy Markdown
Member

@s0ooo0k s0ooo0k left a comment

Choose a reason for hiding this comment

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

✅ 리뷰

  • 코드 확인했습니다! 전반적으로 로직이 고민을 많이 했다는 것이 느껴지고, 회원가입, 검증, 생성 흐름이 이해하기 쉽고, 사용자 관점에서 자연스러운 진행이 될 거 같습니다.
  • 특히 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");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

해당 파트가 Config에서 지정되어 있는데, 추가로도 지정을 해야하는지 궁금합니다!

Copy link
Copy Markdown
Member Author

@taehyun32 taehyun32 Apr 22, 2025

Choose a reason for hiding this comment

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

jpaProperties.put("hibernate.hbm2ddl.auto", "update");가 없으면 DB 테이블이 엔티티 구조에 맞게 변경되지 않습니다
추후에는 value로 가져다가 쓸 예정입니다.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

확인했습니다! 추후 리팩토링 기대하겠습니다. 머지하겠습니다

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

해당 파트의 인증 코드 생성 > 발송 > 검증 로직이 리뷰어들도 이해하기 쉽게 잘 구성되어 있어 인상깊었습니다.

@23MinL
Copy link
Copy Markdown
Contributor

23MinL commented Apr 22, 2025

✅ 코드 리뷰

1. 이메일 인증

  • 인증 코드 유효시간(5분)이 코드에 바로 써 있어서, 나중에 바꿀 때 힘들 수도 있을 것 같아요. 설정 파일로 빼면 더 좋을 것 같습니다.
  • 이메일 보내는 주소도 하드코딩 말고 설정에서 관리하면 환경 바꿀 때 편할 것 같습니다.

2. JWT 인증

  • 만료된 토큰 처리 잘 하신 것 같습니다!
  • 근데 모든 에러에서 토큰 삭제하는 건 조금 위험할 수도 있을 것 같아요. 401 같은 인증 에러에서만 삭제하는 게 더 안전할 것 같습니다.

3. 회원가입

  • 전화번호 필드 추가된 거 좋은 생각 같습니다!
  • 전화번호나 이메일 같은 입력값에 대한 검증(Validation)도 추가하면 더 안전할 것 같습니다.

4. 기타

  • 주석 처리된 큰 코드 블록은 TODO로 남기거나 정리하면 코드가 더 깔끔해질 것 같습니다.

전체적으로 기능 잘 구현하신 것 같습니다!

@taehyun32
Copy link
Copy Markdown
Member Author

@23MinL
아직 validation이나 value로 설정을 분리하는 작업은 구현되지 않았지만, 추후 작업에서 해당 부분을 반영할 예정입니다.
감사합니다.

@s0ooo0k s0ooo0k merged commit 03b9c29 into PETTY-HUB:main Apr 22, 2025
2 checks passed
23MinL pushed a commit to 23MinL/PETTY-BACK-VISION that referenced this pull request Apr 23, 2025
[USER] 회원가입 이메일 인증 시스템 구현, JWT 인증 로직 개선
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants