기록/BACKEND

[SpringBoot] 자바 메일 보내기

5월._. 2022. 7. 21.
728x90

회원가입이나 비밀번호 찾기 메뉴를 이용할 때 인증코드를 보내려고 한다. 흐름은 다음과 같다.

 

1. pom.xml

java mail을 보내기 위해 pom.xml에 다음을 추가했다. 이 부분을 추가해야 JavaMailSender나 MimeMessage 등 메일 보낼 때 필요한 클래스들을 사용할 수 있다.

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-mail -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

 

2. application.properties

src/main/resources/application.properties에 다음 설정을 추가한다.

나는 gmail로 보냈기때문에 gmail에 맞는 설정이다.

spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=보내는 이메일 주소
spring.mail.password=보내는 이메일 비밀번호(앱 비밀번호 권장)
spring.mail.default-encoding=utf-8
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true

 

spring.mail.password에 저장할 비밀번호 만드는 법

진짜 비밀번호를 입력하면 AuthenticationFailedException가 뜨면서 코드를 제대로 입력해도 메일이 가지 않는 오류가 발생한다. 구글이 보안 관련해서 일부러 막아놨기 때문인데, 이 오류를 막으려면 spring.mail.password에 앱 비밀번호를 등록해야한다.

1.  구글 계정 설정 -> 보안 -> 2단계 인증 사용을 체크한다. 

 

Google 계정

보안 계정을 안전하게 보호하기 위해 보안 설정을 검토 및 조정하고 권장사항을 받아보려면 계정에 로그인하세요.

myaccount.google.com

2.  휴대폰 번호 인증으로 2단계 인증을 마치면 위의 구글 계정 -> 보안 메뉴 중 2단계 인증 밑에 앱 비밀번호 탭이 생긴다. 

3.  클릭한 뒤 메일 -> Windows 컴퓨터 로 체크하고 비밀번호를 받는다.

4.  그 비밀번호를 spring.mail.password에 작성하면 끝난다.

구글 고객센터의 설명은 이 페이지에 있다.

 

3. MailService

먼저 MailService 인터페이스를 만들었다. 필수는 아니지만 필요한 메서드를 적어놓고 나중에 한번에 구현하기 편해서 인터페이스->클래스 방식으로 개발했다.

public interface MailService {
	String makeCode(int size);
	String makeHtml(String type, String code);
	String sendMail(String type, String email);
}

 

4. MailServiceImpl

세 메서드로 구성된다. 인증코드를 만드는 부분, html string을 만드는 부분, 메일을 전송하는 부분이다.

그 전에 @Autowired된 JavaMailSender가 필요하다.

 

인증코드 만드는 부분

1.  만드려는 인증코드의 사이즈를 파라미터로 받는다.

2.  랜덤으로 48부터 122까지 숫자를 뽑는다. 뽑은 숫자가 소문자거나(48~57), 숫자거나(65~90), 대문자일 경우(97~122) StringBuilder에 char로 변환해서 더한다. 이 나머지 숫자를 뽑았다면 continue로 다시 뽑는다.

3.  StringBuilder의 길이가 size와 같아질 때까지 반복한다.

4.  sb를 반환한다.

@Override
public String makeCode(int size) {
	Random ran = new Random();
	StringBuffer sb = new StringBuffer();
	int num = 0;
		do {
		num = ran.nextInt(75) + 48;
		if ((num >= 48 && num <= 57) || (num >= 65 && num <= 90) || (num >= 97 && num <= 122)) {
			sb.append((char) num);
		} else {
			continue;
		}
		} while (sb.length() < size);
	return sb.toString();
}

 

html string 만드는 부분

1.  파라미터로 code와 type을 받는다. 회원가입에서만 메일을 전송할 게 아니기 때문에 type을 받았다.

2.  type에 따라서 맞는 html 코드를 저장하고 return한다.

@Override
public String makeHtml(String type, String code) {
	String html = null;
	switch(type) {
	case "register":
		html = "생략";
		break;
	case "findPw":
		html = "생략";
		break;
	}
	return html;
}

 

메일 전송 부분

1.  type과 email을 파라미터로 받는다. type은 어떤 종류의 메일인지, email은 받을 사람의 메일주소다.

2.  타입에 따라서 코드, html, 제목을 정한다. 나는 회원가입에 6자리 코드, 비밀번호찾기에 10자리 인증코드를 만들었다.

3.  MimeMessage 객체를 생성하고 제목, 내용, 보낼 이메일주소를 지정한 뒤 전송한다.

4.  만약 3번 단계에서 오류가 났다면 인증코드 대신 error를 반환했다. 나중에 컨트롤러에서 error가 반환된 것을 알면 처리하기 위해서다.

5.  3번 단계까지 잘 됐다면 메일로 보낸 인증코드를 반환한다.

@Override
public String sendMail(String type, String email) {
	//타입에 따라
	//1. 인증코드 만들기
	//2. html string만들기		
	String code = null, html = null, subject = null;
	switch(type) {
	case "register":
		code = makeCode(6);
		html = makeHtml(type, code);
		subject = "제목입력";
		break;
	case "findPw":
		code = makeCode(10);
		html = makeHtml(type, code);
		subject = "제목입력";
		break;
	}
	
	//공통 - 메일보내기
	MimeMessage mail = mailSender.createMimeMessage();
	try {
		mail.setSubject(subject,"utf-8");
		mail.setText(html,"utf-8","html");
		mail.addRecipient(RecipientType.TO, new InternetAddress(email));
		mailSender.send(mail);
	} catch (MessagingException e) {
		e.printStackTrace();
		return "error";
	}
	
	return code;
}

 

전체 코드

import java.util.Random;

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

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

@Service
public class MailServiceImpl implements MailService {
	@Autowired
	private JavaMailSender mailSender;

	@Override
	public String makeCode(int size) {
		Random ran = new Random();
		StringBuffer sb = new StringBuffer();
		int num = 0;
			do {
			num = ran.nextInt(75) + 48;
			if ((num >= 48 && num <= 57) || (num >= 65 && num <= 90) || (num >= 97 && num <= 122)) {
				sb.append((char) num);
			} else {
				continue;
			}
			} while (sb.length() < size);
		return sb.toString();
	}

	@Override
	public String makeHtml(String type, String code) {
		String html = null;
		switch(type) {
		case "register":
			html = "생략";
			break;
		case "findPw":
			html = "생략";
			break;
		}
		return html;
	}

	@Override
	public String sendMail(String type, String email) {
		//타입에 따라
		//1. 인증코드 만들기
		//2. html string만들기		
		String code = null, html = null, subject = null;
		switch(type) {
		case "register":
			code = makeCode(6);
			html = makeHtml(type, code);
			subject = "제목입력";
			break;
		case "findPw":
			code = makeCode(10);
			html = makeHtml(type, code);
			subject = "제목입력";
			break;
		}
		
		//공통 - 메일보내기
		MimeMessage mail = mailSender.createMimeMessage();
		try {
			mail.setSubject(subject,"utf-8");
			mail.setText(html,"utf-8","html");
			mail.addRecipient(RecipientType.TO, new InternetAddress(email));
			mailSender.send(mail);
		} catch (MessagingException e) {
			e.printStackTrace();
			return "error";
		}
		
		return code;
	}

}

 

5. UserController

1.  만든 서비스를 이용하는 컨트롤러다. 회원관리 기능에서 이용하기 때문에 UserController에서 주입받아 사용했다.

2.  rest api이기 때문에 @RequestBody를 사용해서 map으로 정보를 받았다. type과 email 정보를 담아 mailService의 sendMail을 호출한다..

3.  만약 2번 반환값이 error라면 정상적으로 메일이 전송되지 않은 것이다. 따라서 message에 FAIL메세지를 담는다.

4.  정상적인 인증코드가 반환되었다면 그 코드를 resultMap에 넣고 message에도 SUCCESS를 담는다.

5.  ResponseEntity를 반환한다. 프론트엔드에서 이 정보를 받으면 message로 성공여부를 알 수 있고, 성공했다면 code에 담긴 인증코드로 사용자가 입력하는 인증코드와 우리가 보낸 인증코드가 일치하는지 확인할 수 있다.

@Autowired
private MailService mailService;

//생략

@ApiOperation(value = "이메일 인증코드 전송", notes = "전송한 인증코드를 반환한다.", response = Map.class)
@PostMapping("/sendmail")
public ResponseEntity<Map<String, Object>> sendMail(@RequestBody Map<String, String> map) { 
	Map<String, Object> resultMap = new HashMap<>();
	HttpStatus status = null;
	
	String code = mailService.sendMail(map.get("type"), map.get("email"));
	if(code.equals("error")) {
		resultMap.put("message",FAIL);
		status = HttpStatus.ACCEPTED;
	}else {
		resultMap.put("message", SUCCESS);
		resultMap.put("code", code);
		status = HttpStatus.ACCEPTED;
	}
	
	return new ResponseEntity<Map<String, Object>>(resultMap, status);
}

 

댓글