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