[Spring] 인증 메일 보내기
2024. 10. 11. 18:07ㆍSpring
웹 사이트에 새로 가입을 할 때 이메일로 인증 코드를 받는 경우가 되게 많다.
오늘은 그 인증 메일을 보내는 방법을 알아보자.
사전 작업
1. 프로젝트 내에서 바로 id, password 를 임의로 작성해줄 것이기 때문에
편의성을 위해서는 2차인증을 "해제" 해두자.
2. 네이버 메일 -> 환경설정 -> POP3/IMAP 설정에서,
POP3/SMTP 설정 , IMAP/SMTP 설정 둘다 사용함으로 체크해둔 후, "저장"해두어야 한다.
꼭, 네이버가 아니더라도
2번에서 언급한 환경설정만 해둔다면 메일 보내기 가능하다.
3. 프로젝트에 필요한 설정
pom.xml
의존성 추가
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- 스프링에서 STOMP 처리를 위한 라이브러리 -->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-stomp</artifactId>
<version>5.4.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.sun.mail/jakarta.mail -->
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${org.springframework-version}</version>
</dependency>
</dependencies>
serlvet-context.xml
web-socket message-broker prefix 설정
<websocket:message-broker application-destination-prefix="/app">
<websocket:stomp-endpoint path="/endpoint">
<websocket:sockjs websocket-enabled="true" />
</websocket:stomp-endpoint>
<websocket:simple-broker prefix="/broker" />
</websocket:message-broker>
root-context.xml
component를 사용할 거기 때문에 스프링 빈 등록
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- Root Context: defines shared resources visible to all other web components -->
<context:component-scan base-package="com.itbank.component" />
</beans>
ex03.jsp
인증번호가 담겨있는 메일을 보내고,
사용자에게 인증번호입력을 받아서
일치하는지 확인하기.
인증번호 보내기 버튼을 누르면
숨어있던 인증번호 입력 form이 나타난다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ include file="header.jsp" %>
<h3>AJAX로 메일보내기</h3>
<div class="mailSend">
<form>
<h3>인증번호 발송</h3>
<p>
<input type="email" name="address" placeholder="email">
<button>인증번호 보내기</button>
</p>
<p class="message"></p>
</form>
</div>
<div class="auth box hidden">
<form>
<h3>인증번호 확인</h3>
<p>
<input type="text" name="authNumber" placeholder="인증번호 입력">
<button>인증 확인</button>
</p>
<p class="message"></p>
</form>
</div>
<script>
const mailSendForm = document.forms[0] // form은 배열로 바로 불러올 수 있음
const authForm = document.forms[1]
mailSendForm.onsubmit = async function(event) {
event.preventDefault()
const url = '${cpath}/ajax/sendMail'
const opt = {
method: 'POST',
body : JSON.stringify({
address: event.target.querySelector('input').value
}),
headers: {
'Content-Type' : 'application/json;charset=utf-8'
}
}
const result = await fetch(url, opt).then(resp => resp.text())
const message = event.target.querySelector('p.message')
if(result == 1) {
message.innerText = '메일을 전송했습니다'
message.style.color = 'blue'
document.querySelector('.auth').classList.remove('hidden')
}
else {
message.innerText = '메일을 보낼 수 없습니다'
message.style.color = 'red'
}
}
authForm.onsubmit = async function(event) {
event.preventDefault()
const inputNumber = event.target.querySelector('input').value
const url = '${cpath}/ajax/authNumber/' + inputNumber // /를 꼭 붙여야 pathVariable 사용할 수 있음
const result = await fetch(url).then(resp => resp.text())
const message = event.target.querySelector('p.message')
if(result == 1) {
message.innerText = '인증 성공'
message.style.color = 'blue'
}
else {
message.innerText = '인증 실패'
message.style.color = 'red'
}
}
</script>
</body>
</html>
AjaxController
import java.util.HashMap;
import java.util.Random;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
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;
import com.itbank.component.MailComponent;
// AJAX 니까 그냥 controller 말고 Restcontroller
// 문자열 하나만 받는다면, 굳이 ajax 필요없음
// 객체로 묶어주기 위해서 ajax 로 처리하자
@RestController
@RequestMapping("/ajax")
public class AjaxController {
@Autowired private MailComponent component;
private Random ran = new Random();
@PostMapping("/sendMail")
public int sendMail(@RequestBody HashMap<String, String> param, HttpSession session) {
System.out.println("address : " + param.get("address"));
int num = ran.nextInt(999999);
String authNumber = String.format("%06d", num);
System.out.println("authNumber : " + authNumber);
session.setAttribute("authNumber", authNumber);
session.setMaxInactiveInterval(180);
param.put("subject", "인증번호");
param.put("content", authNumber);
int row = component.sendMimeMessage(param);
System.out.println(row != 0 ? "전송 성공" : "전송 실패");
return row;
}
@GetMapping("/authNumber/{inputNumber}")
public int authNumber(@PathVariable("inputNumber") String inputNumber, HttpSession session) {
// 만약, 세션이 만료되었다면 (== 180초)
// authNumber 의 값은 1이다
// 두개의 문자열의 일치를 비교할때 A.equals(B) 형태로 비교한다
// null 일 가능성이 있는 문자열을 뒤에 배치하여 Nullpointer 에러를 방지함
// 만약, 세션이 만료되었을 때 예외를 발생시켜서 다른 반환값을 전달하려면,
// ExceptionHandler 를 사용하거나,
// @RestControllerAdvice 클래스를 작성하여 처리할 수도 있다
String authNumber = (String) session.getAttribute("authNumber");
int row = 0;
if(inputNumber.equals(authNumber)) {
row = 1;
}
return row;
}
}
MailComponent
import java.io.IOException;
import java.util.HashMap;
import java.util.Properties;
import java.util.Scanner;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import jakarta.mail.Authenticator;
import jakarta.mail.Message;
import jakarta.mail.MessagingException;
import jakarta.mail.PasswordAuthentication;
import jakarta.mail.Session;
import jakarta.mail.Transport;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
@Component
public class MailComponent {
private final String host = "smtp.naver.com";
private final int port = 465;
private String serverId = "로그인할 아이디"; // 이 계정으로 로그인해서 메일을 보낼것임
private String serverPw = "비번";
private Properties props;
// @Autowired 가 자동으로 스프링 빈 연결하듯이
// @Value는 자동으로 자원(파일)을 연결한다
// org.springframework.core.io.Resource
// classpath : "src/main/java" or "src/main/resources
@Value("classpath:mailForm.html")
private Resource mailForm;
@PostConstruct
private void init() {
props = new Properties();
props.put("mail.smtp.host", host);
props.put("mail.smtp.prot", port);
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.ssl.enable", "true");
props.put("mail.smtp.true", host);
}
// 단순 텍스트 메일 보내기 (ex01)
public int sendSimpleMessage(String address, String content, String subject) {
// 1) 메일 서버 인증 (접속에 필요하다)
Session mailSession = Session.getDefaultInstance(props, new Authenticator() {
String un = serverId;
String pw = serverPw;
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(un, pw);
}
});
mailSession.setDebug(true); // 메일 전송 과정을 콘솔창에 출력한다
// 2) 보낼 메세지 작성
Message message = new MimeMessage(mailSession);
try {
message.setFrom(new InternetAddress(serverId + "@naver.com"));
message.setRecipient(Message.RecipientType.TO, new InternetAddress(address)); // 받는사람
message.setSubject(subject); // 제목
message.setText(content); // 내용
Transport.send(message); // 3) 준비가 끝난 메시지를 발송한다
return 1;
} catch (MessagingException e) {
e.printStackTrace();
return 0;
}
}
// html 포함한 메일(ex02)
public int sendMimeMessage(HashMap<String, String> param) {
// 1) 메일 서버 인증 (접속에 필요하다)
Session mailSession = Session.getDefaultInstance(props, new Authenticator() {
String un = serverId;
String pw = serverPw;
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(un, pw);
}
});
mailSession.setDebug(true); // 메일 전송 과정을 콘솔창에 출력한다
// 2) 보낼 메세지 작성
Message message = new MimeMessage(mailSession);
String address = param.get("address");
String subject = param.get("subject");
String content = param.get("content");
try {
message.setFrom(new InternetAddress(serverId + "@naver.com"));
message.setRecipient(Message.RecipientType.TO, new InternetAddress(address)); // 받는사람
message.setSubject(subject); // 제목
String tag = "";
Scanner sc = new Scanner(mailForm.getFile());
while(sc.hasNextLine()) {
tag += sc.nextLine();
}
sc.close();
content = String.format(tag, content);
message.setContent(content, "text/html; charset=utf-8"); // 태그 포함 내용
Transport.send(message);
return 1;
} catch (MessagingException | IOException e) {
e.printStackTrace();
return 0;
}
}
}
네이버가 아니라 구글이라도 메일을 보내려면 POP/IMAP을 활성화 해야하고,
사이트 별로 port가 다르기 때문에 유의해야 한다.
네이버 port : 465
구글 port : 587
'Spring' 카테고리의 다른 글
[Spring] QueryString (0) | 2024.10.11 |
---|---|
[Spring] ViewResolver (0) | 2024.10.11 |
[Project] 실시간 1:1 채팅 (0) | 2024.10.10 |
[Spring] WebSocket - 실시간 채팅 (0) | 2024.10.10 |
[Spring] WebSocket을 활용한 메모장 만들기 (2) | 2024.10.10 |