간단하게 이메일, 인증 번호 입력 창을 만든다.
<body>
<!-- 이메일 입력 창-->
<input type="text" id="email" name="email" placeholder="EMAIL">
<!-- 인증번호 전송 버튼 -->
<button type="button" id="sendEmail">인증번호 전송</button>
<br />
<!-- 인증번호 입력 창 -->
<input type="text" id="authCode" name="authCode" placeholder="CODE">
<!-- 인증번호 확인 버튼 -->
<button type="button" id="confirmEmail">인증번호 확인</button>
</body>
인증번호 전송
인증번호 확인
package com.example.emailAuthenticationvelog.model.member.entity;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import java.time.LocalDateTime;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(of = "idx", callSuper = false)
@Entity
@Table(name = "`MEMBER_AUTHENTICATION_CODE`")
@DynamicInsert
@DynamicUpdate
public class MemberAuthenticationCodeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "idx", updatable = false)
private Long idx;
// 이메일
@Column(name = "email", nullable = false)
private String email;
// 인증 코드
@Column(name = "code", unique = true, nullable = false)
private String code;
// 인증 여부
@Column(name = "is_verified", nullable = false)
private boolean isVerified;
// 인증 유효 기간 설정
@Column(name = "end_date", nullable = false)
private LocalDateTime endDate;
@Column(name = "create_date", updatable = false, nullable = false)
private LocalDateTime createDate;
@Column(name = "update_date")
private LocalDateTime updateDate;
@Column(name = "delete_date")
private LocalDateTime deleteDate;
}
package com.example.emailAuthenticationvelog.model.member.repository;
import com.example.emailAuthenticationvelog.model.member.entity.MemberAuthenticationCodeEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.time.LocalDateTime;
import java.util.Optional;
public interface MemberAuthenticationCodeRepository extends JpaRepository<MemberAuthenticationCodeEntity, Long> {
// 이메일로 end_date가 지금 이후고, delete_date가 null인 데이터 찾아오기
Optional<MemberAuthenticationCodeEntity> findByEmailAndEndDateAfterAndDeleteDateIsNull(String email, LocalDateTime currentDateTime);
}
굳이 DTO로 만들어야 하나 싶긴 하지만 더 입력받을 데이터가 있을 수도 있으니 추가하기 편하게 일단 DTO로 만들어 놓았다.
package com.example.emailAuthenticationvelog.domain.auth.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ReqSendEmailAuthenticationApiV1DTO {
private String email;
}
package com.example.emailAuthenticationvelog.domain.auth.controller;
import com.example.emailAuthenticationvelog.domain.auth.dto.ReqSendEmailAuthenticationApiV1DTO;
import com.example.emailAuthenticationvelog.domain.auth.service.AuthServiceApiV1;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/auth")
public class AuthControllerApiV1 {
private final AuthServiceApiV1 authServiceApiV1;
// 이메일 인증 번호 요청
@PostMapping("/email-authentication")
public HttpEntity<?> sendEmailAuthentication(
@RequestBody ReqSendEmailAuthenticationApiV1DTO reqSendEmailAuthenticationApiV1DTO) {
return authServiceApiV1.sendEmailAuthentication(reqSendEmailAuthenticationApiV1DTO);
}
}
package com.example.emailAuthenticationvelog.common.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ResDTO<T> {
private Integer code;
private String message;
private T data;
}
package com.example.emailAuthenticationvelog.domain.auth.service;
import com.example.emailAuthenticationvelog.common.dto.ResDTO;
import com.example.emailAuthenticationvelog.domain.auth.dto.ReqSendEmailAuthenticationApiV1DTO;
import com.example.emailAuthenticationvelog.domain.email.service.EmailService;
import com.example.emailAuthenticationvelog.model.member.entity.MemberAuthenticationCodeEntity;
import com.example.emailAuthenticationvelog.model.member.repository.MemberAuthenticationCodeRepository;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Optional;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class AuthServiceApiV1 {
private final MemberAuthenticationCodeRepository memberAuthenticationCodeRepository;
private final EmailService emailService;
@Transactional
public HttpEntity<?> sendEmailAuthentication(
ReqSendEmailAuthenticationApiV1DTO reqSendEmailAuthenticationApiV1DTO) {
// 랜덤 인증 코드 생성해서
String authenticationCode = createAuthenticationCode();
// emailService의 sendEmailAuthenticationCode함수로 메일을 발송하고, 성공 여부에 따라 true / false 반환
if (!emailService.sendEmailAuthentication(reqSendEmailAuthenticationApiV1DTO, authenticationCode)) {
// 메일 발송 실패 시 BAD_REQUEST 반환
return new ResponseEntity<>(
ResDTO.builder()
.code(-1)
.message("인증 번호 발송 실패")
.build(),
HttpStatus.BAD_REQUEST);
}
// 메일 발송 성공 시
// 아직 유효한 인증 코드 데이터를 찾아서
Optional<MemberAuthenticationCodeEntity> beforeMemberAuthenticationCodeEntityOptional = memberAuthenticationCodeRepository
.findByEmailAndEndDateAfterAndDeleteDateIsNull(
reqSendEmailAuthenticationApiV1DTO.getEmail(),
LocalDateTime.now());
// 있으면 무효화 (delete_date 설정)
if (beforeMemberAuthenticationCodeEntityOptional.isPresent()) {
MemberAuthenticationCodeEntity beforeMemberAuthenticationCodeEntity = beforeMemberAuthenticationCodeEntityOptional
.get();
beforeMemberAuthenticationCodeEntity.setDeleteDate(LocalDateTime.now());
memberAuthenticationCodeRepository.save(beforeMemberAuthenticationCodeEntity);
}
// 인증 코드 데이터를 저장하기 위해 새 엔티티를 작성하여
MemberAuthenticationCodeEntity memberAuthenticationCodeEntity = MemberAuthenticationCodeEntity
.builder()
.email(reqSendEmailAuthenticationApiV1DTO.getEmail())
.code(authenticationCode)
.isVerified(false)
.endDate(LocalDateTime.now().plus(5, ChronoUnit.MINUTES))
.createDate(LocalDateTime.now())
.build();
// 저장
memberAuthenticationCodeRepository.save(memberAuthenticationCodeEntity);
return new ResponseEntity<>(
ResDTO.builder()
.code(0)
.message("인증 번호 발송 성공")
.build(),
HttpStatus.OK);
}
// 랜덤 인증번호 생성 함수
public String createAuthenticationCode() {
// 8자리, 문자, 숫자 포함 문자열 생성
return RandomStringUtils.random(8, true, true);
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>인증 페이지</title>
</head>
<body style="font-family: 'Arial', sans-serif; background-color: #2C3E50; color: white; text-align: center; margin: 0; padding: 0; height: 100%; width: 100%;">
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin: auto; max-width: 600px; background-color: #AED6F1; border-radius: 5px;">
<tr>
<td style="padding: 40px; text-align: center;">
<div style="margin-bottom: 30px;">
<!-- 여기에 로고 이미지를 삽입하거나 로고 텍스트를 사용하세요 -->
YOUR LOGO
</div>
<h1 style="color: white; font-size: 24px; margin: 0 0 10px 0;">안녕하세요!</h1>
<p style="margin: 0 0 20px 0; color: white; line-height: 1.5; font-size: 16px; ">회원가입 이메일 인증 코드 보내드립니다.</p>
<!-- Thymeleaf로 동적 값 바인딩 -->
<div style="background-color: #ffffff; color: black; padding: 10px 20px; display: inline-block; border-radius: 4px;" th:text="${authenticationCode}">
[인증 코드]
</div>
</td>
</tr>
</table>
</body>
</html>
package com.example.emailAuthenticationvelog.domain.email.service;
import com.example.emailAuthenticationvelog.domain.auth.dto.ReqSendEmailAuthenticationApiV1DTO;
import jakarta.mail.Message;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import java.io.UnsupportedEncodingException;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class EmailService {
private final JavaMailSender javaMailSender;
private final TemplateEngine templateEngine;
public Boolean sendEmailAuthentication(ReqSendEmailAuthenticationApiV1DTO reqEmailAuthenticationApiV1DTO, String authenticationCode) {
// 메시지 객체를 생성하고
MimeMessage message = javaMailSender.createMimeMessage();
try {
// 이메일 제목 설정
message.setSubject("사이트 회원가입 인증번호 입니다.");
// 이메일 수신자 설정
message.addRecipient(Message.RecipientType.TO, new InternetAddress(reqEmailAuthenticationApiV1DTO.getEmail(), "", "UTF-8"));
// 이메일 내용 설정
message.setText(setContext(authenticationCode), "UTF-8", "html");
// 송신
javaMailSender.send(message);
} catch (MessagingException e) {
e.printStackTrace();
return false;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return false;
}
// 다 성공했다면
return true;
}
// 생성해놓은 html에 인증 코드를 넣어서 반환
private String setContext(String authenticationCode) {
Context context = new Context();
context.setVariable("authenticationCode", authenticationCode);
return templateEngine.process("email-authentication", context);
}
}
<script>
document.querySelector("#sendEmail").addEventListener("click", function () {
// 이메일 입력 창에서 이메일 주소를 가져온다.
var email = document.querySelector("#email").value;
// 이메일 주소가 비어있는지 확인한다.
if (email === "") {
alert("이메일을 입력해주세요.");
return;
}
// 이메일 주소를 JSON 형태로 만든다.
var data = {"email": email};
// POST /email-authentication 요청을 보낸다.
fetch("/api/v1/auth/email-authentication", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(data)
}).then((response) => response.json())
.then((result) => {
if(result.code === 0){
console.log("err" ,result);
// 응답 결과를 alert로 보여준다.
alert(result.message);
}
if(result.code === 1){
// 응답 결과를 alert로 보여준다.
console.log("err" ,result);
alert(result.message);
}
});
});
</script>
실제로 전송해보자.
html 이름을 index.html로 설정해놨기 때문에 localhost:8080으로 진입시 포트 변경 설정을 해놓지 않은 이상 만들어 놓은 index 페이지로 들어갈 것이다.
이메일을 작성하고 전송 버튼을 클릭한다.
2초 정도 기다리면, 팝업창이 뜬다.
이제 작성한 이메일로 들어가 확인해보면 이메일이 수신된 것을 확인할 수 있다.
DB에도 잘 들어가있다.