3층 1구역 - 개발의 장/Spring

spring - mail API(2022-10-25)

상이태상 2022. 10. 25. 17:08

1.서론

이번엔 메일과 관련해서 이러저런 작업을 한번 해보도록 하자.

2. 본론

먼저 3가지의 의존성부터 주입하도록 하자.

 

	<!-- https://mvnrepository.com/artifact/javax.mail/javax.mail-api -->
		<dependency>
			<groupId>javax.mail</groupId>
			<artifactId>javax.mail-api</artifactId>
			<version>1.6.2</version>
		</dependency>
		
		<!-- https://mvnrepository.com/artifact/com.sun.mail/javax.mail -->
		<dependency>
    		<groupId>com.sun.mail</groupId>            
    		<artifactId>javax.mail</artifactId>
    		<version>1.6.2</version>
		</dependency>
		
		<!-- https://mvnrepository.com/artifact/org.springframework/spring-context-support -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context-support</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>

 

다음은 Config와 Service 셋팅을 해주도록 하자.

MailConfig.java

package com.care.db.basic.config;

import java.util.Properties;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;

@Configuration
public class MailConfig {
	@Bean
	public JavaMailSender mailSender() {
		JavaMailSenderImpl jms = new JavaMailSenderImpl();
		jms.setHost("smtp.gmail.com"); //gmail내에서 제공하는 서버주소
		jms.setPort(587); //gmail내에서 제공하는 서버포트
		jms.setUsername("자신의 gmail"); // 인증번호를 클라이언트에게 보낼 이메일 주소.
		jms.setPassword("발급받은 비밀번호"); // 비밀번호(프로그램에서 사용하는 비밀번호 xggivnhweosptvdu)
		
		Properties pro = new Properties();
		pro.setProperty("mail.transport.protocol", "smtp");
		pro.setProperty("mail.smtp.auth", "true");
		pro.setProperty("mail.smtp.starttls.enable", "true");
		jms.setJavaMailProperties(pro);
		return jms; 
	}
}

 

위 코드에서 패스워드의 경우 구글 로그인 후 계정 관리에 들어가 '보안'에 진입하면

앱 비밀번호라고 있다. 이건 2단계 인증을 해야 사용할 수 있다 라 알고 있다.

생성하고 발급받은 비밀번호를 setPassword에 넣도록 하자. 한번 밖에 안 보여주니 확인 잘해야 한다.

MailService.java

package com.care.db.basic.service;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;

@Service
public class MailService {
	@Autowired
	private JavaMailSender mailSender;
//	여기서 오류가 났다면 config에서 확인해봐야 한다.

	public void sendMail(String to, String subject, String content) {
		MimeMessage message = mailSender.createMimeMessage();
//		1. MimeMessage객체를 만들어 
		try {
			MimeMessageHelper messageHelper = new MimeMessageHelper(message, true, "UTF-8");
//			2. Helper객체를 만들어 아래에 있는 내용을 포장하여
			
			messageHelper.setSubject(subject); // 이메일의 제목
			messageHelper.setText(content);// 이메일의 본문
			messageHelper.setTo(to); // 수신자
//			위 정보를 gmail서버에 보내줘야 한다. 그러면 gmail서버가 수신자에게 전달해준다.
//			왜 그러면 messageHelper를 만들어서 쓰느냐? MIME는 전자우편을 위한 인터넷 표준포맷
//			MIME포맷으로 변환되어 SMTP로 전송된다.
//			비유하자면? HTTP로 보낼라면 HTML로 포맷하여 전송한다.

			mailSender.send(message);
//			3. 보내준다

		} catch (MessagingException e) {
			e.printStackTrace();
		}
	}
}

 

 

이로써 클라이언트에게 메일을 보낼 세팅이 끝났다.

register.jsp

 

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>member</title>

<!-- 
	클라이언트 : doubleCheck 자바스크립트 함수에서 서버로 사용자가 입력한 아이디를 갖고 요청
	서버 : doubleCheck 요청을 받아 doubleCheck 자바 메서드호출
	서버 : server객체 내 doubleCheck 자바 메서드 호출
	서버 : dao 객체 내 doubleCheck 자바 메서드 호출
	서버 : testMapper.xml 파일에서 <select>태그로 중복 확인 쿼리문 동작
	서버 : 클라이언트 반환 데이터 "중복된 아이디" or "사용가능 아이디"
	클라이언트 : id="doubleCheckMsg" 태그에 출력
 -->
 
 <!-- 
		클라이언트 : sendAuth 자바스크립트 함수에서 서버로 사용자가 입력한 이메일을 갖고 요청 
		서버 : sendAuth 요청을 받아 sendAuth 자바 메서드 호출
		서버 : sendAuth 메서드 안에서 6자리의 랜덤 값 생성 후 클라이언트에게 응답
		클라이언트 : id="emailMsg" 태그에 출력.
		
		클라이언트 : checkAuth 자바스크립트 함수에서 서버로 사용자가 입력한 인증번호를 갖고 요청 
		서버 : checkAuth 요청을 받아 checkAuth 자바 메서드 호출
		서버 : checkAuth 메서드 안에서 인증번호 검증 후 클라이언트에게 응답
		서버 : 응답 메시지 : "인증번호를 입력하세요" or "인증 실패" or "인증 성공"
		클라이언트 : id="emailMsg" 태그에 출력.
	-->

<script>
	var req
	function doubleCheck() {
		req = new XMLHttpRequest();
		req.onreadystatechange = doubleCheckMsg
		req.open('post', "doubleCheck")
		var reqData = document.getElementById('id')
		req.send(reqData.value)
	}
	
	function doubleCheckMsg() {
		if(req.readyState == 4 && req.status == 200){
			var doubleCheckMsg = document.getElementById('doubleCheckMsg')
			doubleCheckMsg.innerHTML = req.responseText
		}
	}
	
	function sendAuth(){
		req = new XMLHttpRequest();
		req.onreadystatechange = emailMsg
		var email = document.getElementById('email').value
		req.open('post', 'sendAuth')
		req.send(email)
	}
	function checkAuth(){
		req = new XMLHttpRequest();
		req.onreadystatechange = emailMsg
		var authNumber = document.getElementById('authNumber').value
		req.open('post', 'checkAuth')
		req.send(authNumber)
	}
	function emailMsg(){
		if(req.readyState == 4 && req.status == 200){
			var emailMsg = document.getElementById('emailMsg')
			emailMsg.innerHTML = req.responseText
		}
	}
</script>
</head>
<body>
<c:if test="${not empty msg }" >
	<script>alert("${msg}");</script>
</c:if>

	<font id="doubleCheckMsg" color="red"></font><br>
	<font id="emailMsg" color="blue"></font>
	<form action="register" method="post">
		<input type="text" name="id" id="id" placeholder="아이디">
		<input type="button" value="중복 확인" onclick="doubleCheck()">
		<br>
		<input type="password" name="pw" placeholder="비밀번호"><br>
		<input type="password" name="confirmPw" placeholder="비밀번호 확인"><br>
		<input type="text" name="name" placeholder="이름"><br>
		
		<input type="text" name="email" id="email" placeholder="이메일">
		<input type="button" value="인증 번호 전송" onclick="sendAuth()"><br>
		
		<input type="text" name="authNumber" id="authNumber" placeholder="인증 번호">
		<input type="button" value="인증 번호 확인" onclick="checkAuth()"><br>
		
		
		<input type="submit" value="회원 가입" >
		<input type="button" value="취소" onclick="location.href='index'">
	</form>
</body>
</html>



아이디 중복확인 에서 쓰던 .jsp를 사용했다.

function sendAuth(){
		req = new XMLHttpRequest();
		req.onreadystatechange = emailMsg
		var email = document.getElementById('email').value
		req.open('post', 'sendAuth')
		req.send(email)
	}
	function checkAuth(){
		req = new XMLHttpRequest();
		req.onreadystatechange = emailMsg
		var authNumber = document.getElementById('authNumber').value
		req.open('post', 'checkAuth')
		req.send(authNumber)
	}
	function emailMsg(){
		if(req.readyState == 4 && req.status == 200){
			var emailMsg = document.getElementById('emailMsg')
			emailMsg.innerHTML = req.responseText
		}
	}

위 코드만 추가해주었다.

MemberController.java

private String randomNumber;
	@Autowired private MailService mailService;
	@ResponseBody
	@PostMapping(value = "sendAuth", produces = "text/html; charset=UTF-8")
	public String sendAuth(@RequestBody(required = false)String email) {
		Random r = new Random();
		int begin = 0;
		int end = 999999;
		int ran = r.nextInt(end-begin)+1;
		randomNumber = String.format("%06d", ran);
		System.out.println("인증번호 : " + randomNumber);
		mailService.sendMail(email, "[인증번호]", randomNumber);
		if(email == null) {
			return "이메일을 입력하세요.";
		}
		return randomNumber;
	}
	
	@ResponseBody
	@PostMapping(value = "checkAuth", produces = "text/html; charset=UTF-8")
	public String checkAuth(@RequestBody(required = false)String authNumber) {
		if(authNumber == null)
		return "인증번호를 입력하세요.";
		
		else if(randomNumber == null)
			return "이메일 입력 후 인증번호를 생성하세요.";
		
		else if(authNumber.equals(randomNumber)) 
			return "인증 성공";
		
		else return "인증 실패";
	}

위 코드를 추가시켜 주었다.

 

 

 


결과를 보면?

필자의 이메일을 넣고 전송 버튼 누르게 되면 위와 같이 랜덤으로 번호가 만들어지며

콘솔창에도 띄어달라 했으니 띄어진다.

그리고 메일창을 가보면?

실제로 메일이 오는 것을 볼 수 있다.

근데 여기서 한가지 문제가 생긴다.
이용자 수가 많아져서 동시에 다른 브라우저에서 다른 사람들이 회원가입을 진행할 때, 인증코드가 많이 뿌려질텐데
실질적으로 인증을 받을 수 있는건 제일 나중에 인증을 요청한 사람이다.

이럴땐 어떻게 해야 할까?
이럴땐 session값을 주면 된다.

 

//	private String randomNumber;
//  위 코드만 있을 경우, 서버가 가동될 때 마다 계속 랜덤값은 갱신이 되기 때문에 마지막에 랜덤값을 받은 사람만
//	인증이 가능하다.
	
//	그렇기에 session을 주어 웹 브라우저에 경계를 만들어 거기서만 session이 돌게끔 만들고 
//	더불어 각각의 사용자에게 session이라는 경계를 주어
//	각각에게 랜덤값을 뿌려줄 수 있다?
	@Autowired private MailService mailService;
	@Autowired private HttpSession session;
	@ResponseBody
	@PostMapping(value = "sendAuth", produces = "text/html; charset=UTF-8")
	public String sendAuth(@RequestBody(required = false)String email) {
		if(email == null) {
		return "이메일을 입력하세요.";
		}
		
		Random r = new Random();
		int begin = 0;
		int end = 999999;
		int ran = r.nextInt(end-begin)+1;
		String randomNumber = String.format("%06d", ran);
		
		// 인증번호는 사용자별 정보이기에 session에 꼭 저장해야함.
		session.setAttribute("randomNumber", randomNumber);
		System.out.println("인증번호 : " + randomNumber);
		mailService.sendMail(email, "[인증번호]", randomNumber);
		return "인증번호를 이메일로 전송했습니다.";
	}
	
	@ResponseBody
	@PostMapping(value = "checkAuth", produces = "text/html; charset=UTF-8")
	public String checkAuth(@RequestBody(required = false)String authNumber) {
		String randomNumber = (String)session.getAttribute("randomNumber");
		
		if(authNumber == null)
		return "인증번호를 입력하세요.";
		
		else if(randomNumber == null)
			return "이메일 입력 후 인증번호를 생성하세요.";
		
		else if(authNumber.equals(randomNumber)) 
			return "인증 성공";
		
		else return "인증 실패";
	}

 

이렇게 되면 session이라는 하나의 경계가 만들어지기 때문에 동시에 인증번호를 요청해도 각각의 사용자가 인증을 받을 수 있다.

3. 결론

뭔가 적은거 같으면서도?? 많은거 같은 양이다.
오늘 뭔가 알아가는게 있다면 555 5.5.2 syntax error에 대한 오류이다.

이 오류는 사용자의 데스크탑 및 노트북에 사용자 이름이 한글로 되어 있을 경우 발생하는 오류이다.
이 오류가 있다면 랜덤값을 출력하는 데엔 문제가 없지만 인증번호를 이메일로 발신 할 경우 문제가 생긴다.
(세심한 녀석....)