Skip to content
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

[#6] 이메일/SMS 인증 API 개발 #15

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.danahub.zipitda.auth.controller;

import com.danahub.zipitda.auth.dto.VerificationResponseDto;
import com.danahub.zipitda.auth.dto.VerificationSendCodeRequestDto;
import com.danahub.zipitda.auth.dto.VerificationVerifyCodeRequestDto;
import com.danahub.zipitda.auth.service.VerificationService;
import com.danahub.zipitda.common.dto.CommonResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/verification")
@RequiredArgsConstructor
public class VerificationController {

private final VerificationService verificationService;

// 인증 코드 발송 API
@PostMapping("/send-code")
public ResponseEntity<CommonResponse<VerificationResponseDto>> sendVerificationCode(
@RequestBody VerificationSendCodeRequestDto request) {

VerificationResponseDto response = verificationService.sendCode(request);
return ResponseEntity.ok(CommonResponse.success(response));
}

// 인증 코드 검증 API
@PostMapping("/verify-code")
public ResponseEntity<CommonResponse<VerificationResponseDto>> verifyCode(
@RequestBody VerificationVerifyCodeRequestDto request) {

VerificationResponseDto response = verificationService.verifyCode(request);
return ResponseEntity.ok(CommonResponse.success(response));
}
}
43 changes: 43 additions & 0 deletions src/main/java/com/danahub/zipitda/auth/domain/Verification.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.danahub.zipitda.auth.domain;

import com.danahub.zipitda.common.domain.BaseEntity;
import jakarta.persistence.*;
import lombok.*;
import org.apache.ibatis.annotations.Case;

import java.time.LocalDateTime;

@Entity
@Table(name = "VERIFICATIONS")
@Getter
@Setter
@Builder
@AllArgsConstructor
public class Verification extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id; // PK

@Column(nullable = false)
private String type; // 인증 유형 (email/sms)

private String email; // 인증 email

private String mobile; // 인증 폰 번호

@Column(nullable = false)
private String code; // 인증 코드

@Column(nullable = false)
private Boolean isVerified = false; // 인증 완료 여부

@Column(nullable = false)
private Integer retryCount = 0; // 재시도 횟수

@Column(nullable = false)
private LocalDateTime expiredAt; // 만료시간

public Verification() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.danahub.zipitda.auth.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class VerificationResponseDto {
private boolean success; // 요청 성공 여부
private String message; // 응답 메시지
private int retryCount; // 남은 재시도 횟수
private String expiredAt; // 인증코드 만료시간
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.danahub.zipitda.auth.dto;

import lombok.Data;

@Data
public class VerificationSendCodeRequestDto {
private String type; // 인증 유형 (email/sms)
private String recipient; // 수신자 (이메일/전화번호)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.danahub.zipitda.auth.dto;

import lombok.Data;

@Data
public class VerificationVerifyCodeRequestDto {
private String type; // 인증 유형 (email/sms)
private String recipient; // 수신자 (이메일/전화번호)
private String code; // 인증 코드
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.danahub.zipitda.auth.repository;

import com.danahub.zipitda.auth.domain.Verification;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface VerificationRepository extends JpaRepository<Verification, Integer> {
Optional<Verification> findFirstByTypeAndEmail(String type, String email); // 이메일로 검색
Optional<Verification> findFirstByTypeAndMobile(String type, String mobile); // 휴대폰 번호로 검색
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.danahub.zipitda.auth.service;

import com.danahub.zipitda.auth.domain.Verification;
import com.danahub.zipitda.auth.dto.VerificationResponseDto;
import com.danahub.zipitda.auth.dto.VerificationSendCodeRequestDto;
import com.danahub.zipitda.auth.dto.VerificationVerifyCodeRequestDto;
import com.danahub.zipitda.auth.repository.VerificationRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.Optional;
import java.util.Random;

@Service
@RequiredArgsConstructor
public class VerificationService {

private final VerificationRepository verificationRepository;

// 인증 코드 발송
public VerificationResponseDto sendCode(VerificationSendCodeRequestDto request) {
validateRequest(request);

// 인증 코드 생성 및 저장
String code = generateCode();
Verification verification = Verification.builder()
.type(request.getType())
.email("email".equals(request.getType()) ? request.getRecipient() : null)
.mobile("sms".equals(request.getType()) ? request.getRecipient() : null)
.code(code)
.retryCount(0) // 명시적으로 설정
.expiredAt(LocalDateTime.now().plusMinutes(5))
.isVerified(false)
.build();

verificationRepository.save(verification);

// DTO 변환 및 반환
return VerificationResponseDto.builder()
.success(true)
.message("인증 코드가 성공적으로 발송되었습니다.")
.retryCount(verification.getRetryCount())
.expiredAt(verification.getExpiredAt().toString())
.build();
}

// 인증 코드 검증

public VerificationResponseDto verifyCode(VerificationVerifyCodeRequestDto request) {
Optional<Verification> optionalVerification;

if ("email".equals(request.getType())) {
optionalVerification = verificationRepository.findFirstByTypeAndEmail(request.getType(), request.getRecipient());
} else if ("sms".equals(request.getType())) {
optionalVerification = verificationRepository.findFirstByTypeAndMobile(request.getType(), request.getRecipient());
} else {
throw new IllegalArgumentException("잘못된 인증 타입입니다.");
}

Verification verification = optionalVerification.orElseThrow(() ->
new IllegalArgumentException("인증 요청이 존재하지 않습니다.")
);

if (verification.getExpiredAt().isBefore(LocalDateTime.now())) {
throw new IllegalStateException("인증 코드가 만료되었습니다.");
}

if (!verification.getCode().equals(request.getCode())) {
throw new IllegalArgumentException("인증 코드가 일치하지 않습니다.");
}

verification.setIsVerified(true);
verificationRepository.save(verification);

return VerificationResponseDto.builder()
.success(true)
.message("인증이 성공적으로 완료되었습니다.")
.retryCount(verification.getRetryCount())
.expiredAt(verification.getExpiredAt().toString())
.build();
}

// 요청 유효성 검증
private void validateRequest(VerificationSendCodeRequestDto request) {
if ("email".equals(request.getType()) && !isValidEmail(request.getRecipient())) {
throw new IllegalArgumentException("유효하지 않은 이메일 형식입니다.");
} else if ("sms".equals(request.getType()) && !isValidPhoneNumber(request.getRecipient())) {
throw new IllegalArgumentException("유효하지 않은 전화번호 형식입니다.");
}
}

// 인증 코드 생성
private String generateCode() {
return String.format("%06d", new Random().nextInt(1000000));
}

// 이메일 유효성 검사
private boolean isValidEmail(String email) {
return email != null && email.contains("@");
}

// 전화번호 유효성 검사
private boolean isValidPhoneNumber(String phoneNumber) {
return phoneNumber != null && phoneNumber.matches("\\d{10,11}");
}
}
27 changes: 27 additions & 0 deletions src/main/java/com/danahub/zipitda/common/dto/CommonResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.danahub.zipitda.common.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.ibatis.annotations.ConstructorArgs;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class CommonResponse<T> {
private int code; // 에러 코드
private String message; // 메시지
private T data; // 응답 데이터

// 성공 응답용 메서드
public static <T> CommonResponse<T> success(T data) {
return new CommonResponse<>(0, "Success", data);
}

// 에러 응답용 메서드
public static <T> CommonResponse<T> error(int code, String message) {
return new CommonResponse<>(code, message, null);
}
}
4 changes: 3 additions & 1 deletion src/main/resources/logback-spring.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
</root>

<!-- 패키지별 로그 레벨 설정 -->
<logger name="com.danahub" level="debug" /> <!-- 내 코드 디버그용 -->
<logger name="com.danahub" level="info" /> <!-- 내 코드 디버그용 -->
<logger name="org.springframework" level="warn" /> <!-- 스프링 경고만 출력 -->

<logger name="org.springframework.boot.autoconfigure" level="warn" />
</configuration>