지난 포스팅에서 Gmail SMTP에 관련된 설정을 완료했다.
이제 우리 서버에서 인증코드를 담은 메일을 전송하고, 그것으로 어떻게 사용자를 인증할 것인지에 대해 생각해 봐야한다.
사용자 : 메일 입력 -> 서버 : 입력된 메일로 코드 전송
사용자 : 전송된 코드를 서버에 입력 -> 서버 : 입력 받은 코드와 발송한 코드가 일치하는 지 검증
@Getter
@NoArgsConstructor
@Entity
@Table(name = "mail")
public class Mail extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column
private Long id;
// 인증 여부
@Column
private boolean status;
// 메일 주소
@Column
private String email;
// 인증 코드
@Column
private String code;
// 사용자와 1대1 연결
@OneToOne
@JoinColumn(name = "user_id", nullable = false)
private User user;
@Builder
public Mail(User user) {
this.user = user;
this.email = user.getEmail();
this.code = "";
this.status = false;
}
// 인증 코드 업데이트
public void mailAddCode(String code) {
this.code = code;
}
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/users/email")
public class MailController {
private final MailService mailService;
// 메일 전송
@PostMapping("/send")
public ResponseEntity<String> sendMail(@RequestBody MailRequestDto requestDto) throws MessagingException {
mailService.sendMail(requestDto);
return ResponseEntity.ok(requestDto.getEmail() + "로 인증코드가 발송되었습니다.");
}
// 메일 검증
@PostMapping("/verify")
public ResponseEntity<String> verifyMail(@RequestBody VerifyRequestDto requestDto) {
mailService.verifyMail(requestDto);
return ResponseEntity.ok("입력하신 메일 " + requestDto.getEmail() + "이 정상적으로 인증되었습니다.");
}
}
public void sendMail(MailRequestDto requestDto) {
Mail mail = checkAndSaveMail(requestDto.getEmail());
MimeMessage emailForm = createMailForm(mail);
javaMailSender.send(emailForm);
}
MailRequestDto 객체를 매개변수로 받는 sendMail 메서드
MailRequestDto.java// MailRequestDto.java
@Getter
@NoArgsConstructor
public class MailRequestDto {
private String email;
@Builder
public MailRequestDto(String email) {
this.email = email;
}
}
requestDto 객체에서 메일 주소를 받아서 checkAndSaveMail 메서드를 수행
checkAndSaveMailprivate Mail checkAndSaveMail(String email) {
User user = getUserByEmail(email);
Optional<Mail> checkMail = mailRepository.findByEmail(user.getEmail());
checkMail.ifPresent(mailRepository::delete);
Mail mail = Mail.builder()
.user(user)
.build();
return mailRepository.save(mail);
}
저장된 사용자 정보 조회 (회원가입 도중이지만 임시 상태로 저장되어 있음)
메일 주소를 받아서 MailRepository에서 같은 메일 주소를 조회 -> 존재하면 메일 엔티티 삭제
새로운 메일 엔티티 생성 후 저장
사용자에게 보낼 메일 형태 생성 후 전송
createMailFormprivate MimeMessage createMailForm(Mail mail) {
String code = createCode();
mail.mailAddCode(code);
MimeMessage message = javaMailSender.createMimeMessage();
try {
MimeMessageHelper messageHelper = new MimeMessageHelper(message, true);
String senderEmail = "noreply_88@gmail.com";
messageHelper.setFrom(senderEmail);
messageHelper.setTo(mail.getEmail());
messageHelper.setSubject("[88하게] 이메일 인증 번호 발송");
String body = "<html><body style='background-color: #000000 !important; margin: 0 auto; max-width: 600px; word-break: break-all; padding-top: 50px; color: #ffffff;'>";
body += "<h1 style='padding-top: 50px; font-size: 30px;'>이메일 주소 인증</h1>";
body += "<p style='padding-top: 20px; font-size: 18px; opacity: 0.6; line-height: 30px; font-weight: 400;'>서비스 사용을 위해 회원가입 시 고객님께서 입력하신 이메일 주소의 인증이 필요합니다.<br />";
body += "하단의 인증 번호로 이메일 인증을 완료하시면, 정상적으로 서비스를 이용하실 수 있습니다.<br />";
body += "<div class='code-box' style='margin-top: 50px; padding-top: 20px; color: #000000; padding-bottom: 20px; font-size: 25px; text-align: center; background-color: #f4f4f4; border-radius: 10px;'>" + code + "</div>";
body += "</body></html>";
messageHelper.setText(body, true);
} catch (MessagingException e) {
e.printStackTrace();
}
return message;
}
createCode 메서드로 임의의 문자로 이루어진 인증 코드 생성
createCode// 이메일 인증코드 생성
private String createCode() {
int numberzero = 48; // 0 아스키 코드
int alphbetz = 122; // z 아스키 코드
int codeLength = 8; // 인증코드의 길이
Random rand = new Random(); // 임의 생성
return rand.ints(numberzero, alphbetz + 1)
.filter(i -> (i <= 57 || i >= 65) && (i <= 90 || i >= 97)) // 숫자와 알파벳만 허용
.limit(codeLength)
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
.toString();
}
생성된 인증 코드를 메일 엔티티에 추가
Spring boot에 포함된 MimeMessage class를 사용하여 이메일 구조 생성 후 반환
public void verifyMail(VerifyRequestDto requestDto) {
LocalDateTime now = LocalDateTime.now(); // 유효시간 체크를 위한 현재시간 체크
User user = getUserByEmail(requestDto.getEmail());
// 해당하는 메일주소가 없는 경우.
Mail mail = mailRepository.findByEmail(user.getEmail()).orElseThrow(
() -> new MailServiceException("잘못된 이메일입니다.")
);
// 인증 유효시간 180초가 지난 경우
LocalDateTime timeLimit = mail.getCreatedAt().plusSeconds(EXPIRED_TIME);
String targetCode = mail.getCode();
String code = requestDto.getCode();
if (now.isAfter(timeLimit)) {
throw new MailServiceException("만료된 인증코드입니다.");
}
// 발송된 코드와 입력받은 코드가 일치하는 경우
if (targetCode.equals(code)) {
user.updateStatusVeryfied(); // 사용자의 상태를 임시 -> 정상으로 변경
}
}