[Spring] 인증 메일 보내기

2024. 10. 11. 18:07Spring

웹 사이트에 새로 가입을 할 때 이메일로 인증 코드를 받는 경우가 되게 많다.

오늘은 그 인증 메일을 보내는 방법을 알아보자.


사전 작업

 

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