아까 말했듯 우리가 직접 구현하는 것보다는 외부 서버가 나을 것이다!
3)우리는 앱비밀번호라는게 필요한데 앱 비밀번호를 만드려면 우리의 구글 계정에 2단계 인증이 필요하다. 보안탭에서 2단계 인증을 사용하는 로그인을 설정해 주어야 한다.
4)저 위에 검색창이 앱 비밀번호라고 치고 나오는 화면을 확인한다.
5)그러면 왼쪽에 메일 오른쪽에 Windows 컴퓨터를 누른후 생성을 해주면 된다.
6)생성을 하면 아래 화면처럼 나온다.
7)windows 컴퓨터용 앱 비밀번호를 따로 저장해둔다.
4)메일에 들어간다면
이런 탭을 볼 수 있다.
환경설정으로 가자!
5)환경 설정으로 가서 POP3,IMAP 설정으로 가보자
그렇다면 POP3/SMTP 사용을 사용함으로 바꾸어주고 아래 화면처럼 세팅을 해둔다.
https://guide.worksmobile.com/kr/mail/mail-guide/settings/pop3-imap-smtp/
implementation 'javax.mail:mail:1.4.7'
// Spring Context Support
implementation 'org.springframework:spring-context-support:5.3.9'
를 추가한다. (Spring Boot에서 제공하는 편리한 이메일 전송 기능을 위한 의존성으로, 스프링 생태계 내에서 통합되고 설정이 간단하게 처리)
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-mail', version: '2.6.3'
앞으로 할 코드는 2가지 방식 모두에서 작동되는 것을 확인 하였다
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import java.util.Properties;
@Configuration
public class EmailConfig {
@Bean
public JavaMailSender mailSender() {//JAVA MAILSENDER 인터페이스를 구현한 객체를 빈으로 등록하기 위함.
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();//JavaMailSender 의 구현체를 생성하고
mailSender.setHost("smtp.gmail.com");// 속성을 넣기 시작합니다. 이메일 전송에 사용할 SMTP 서버 호스트를 설정
mailSender.setPort(587);// 587로 포트를 지정
mailSender.setUsername("구글@gmail.com");//구글계정을 넣습니다.
mailSender.setPassword("아까 저장한 앱비밀번호");//구글 앱 비밀번호를 넣습니다.
Properties javaMailProperties = new Properties();//JavaMail의 속성을 설정하기 위해 Properties 객체를 생성
javaMailProperties.put("mail.transport.protocol", "smtp");//프로토콜로 smtp 사용
javaMailProperties.put("mail.smtp.auth", "true");//smtp 서버에 인증이 필요
javaMailProperties.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");//SSL 소켓 팩토리 클래스 사용
javaMailProperties.put("mail.smtp.starttls.enable", "true");//STARTTLS(TLS를 시작하는 명령)를 사용하여 암호화된 통신을 활성화
javaMailProperties.put("mail.debug", "true");//디버깅 정보 출력
javaMailProperties.put("mail.smtp.ssl.trust", "smtp.naver.com");//smtp 서버의 ssl 인증서를 신뢰
javaMailProperties.put("mail.smtp.ssl.protocols", "TLSv1.2");//사용할 ssl 프로토콜 버젼
mailSender.setJavaMailProperties(javaMailProperties);//mailSender에 우리가 만든 properties 넣고
return mailSender;//빈으로 등록한다.
}
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import java.util.Properties;
@Configuration
public class EmailConfig {
@Bean
public JavaMailSender mailSender() {//JAVA MAILSENDER 인터페이스를 구현한 객체를 빈으로 등록하기 위함.
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();//JavaMailSender 의 구현체를 생성하고
mailSender.setHost("smtp.naver.com");// 속성을 넣기 시작합니다. 이메일 전송에 사용할 SMTP 서버 호스트를 설정
mailSender.setPort(465);// 465로 포트를 지정
mailSender.setUsername("네이버 ID");//네이버 ID를 넣습니다.
mailSender.setPassword("네이버 비밀번호");//네이버 비밀번호를 넣습니다.
Properties javaMailProperties = new Properties();//JavaMail의 속성을 설정하기 위해 Properties 객체를 생성
javaMailProperties.put("mail.transport.protocol", "smtp");//프로토콜로 smtp 사용
javaMailProperties.put("mail.smtp.auth", "true");//smtp 서버에 인증이 필요
javaMailProperties.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");//SSL 소켓 팩토리 클래스 사용
javaMailProperties.put("mail.smtp.starttls.enable", "true");//STARTTLS(TLS를 시작하는 명령)를 사용하여 암호화된 통신을 활성화
javaMailProperties.put("mail.debug", "true");//디버깅 정보 출력
javaMailProperties.put("mail.smtp.ssl.trust", "smtp.naver.com");//smtp 서버의 ssl 인증서를 신뢰
javaMailProperties.put("mail.smtp.ssl.protocols", "TLSv1.2");//사용할 ssl 프로토콜 버젼
mailSender.setJavaMailProperties(javaMailProperties);//mailSender에 우리가 만든 properties 넣고
return mailSender;//빈으로 등록한다.
}
}
사용자의 이메일을 받아올때 DTO로 받아보겠습니다.
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
@Getter
@Setter
public class EmailRequestDto {
@Email//1)@기호를 포함해야 한다.2_@기호를 기준으로 이메일 주소를 이루는 로컬호스트와 도메인 파트가 존재해야 한다.3)도메인 파트는 최소하나의 점과
//그 뒤에 최소한 2개의 알파벳을 가진다를 검증
@NotEmpty(message = "이메일을 입력해 주세요")
private String email;
}
import com.testtt.email.Dto.EmailCheckDto;
import com.testtt.email.Dto.EmailRequestDto;
import com.testtt.email.Service.MailSendService;
import lombok.RequiredArgsConstructor;
import org.hibernate.annotations.Check;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
@RequiredArgsConstructor
public class MailController {
private final MailSendService mailService;
@PostMapping ("/mailSend")
public String mailSend(@RequestBody @Valid EmailRequestDto emailDto){
System.out.println("이메일 인증 이메일 :"+emailDto.getEmail());
return mailService.joinEmail(emailDto.getEmail());
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.util.Random;
@Service
public class MailSendService {
@Autowired
private JavaMailSender mailSender;
private int authNumber;
//임의의 6자리 양수를 반환합니다.
public void makeRandomNumber() {
Random r = new Random();
String randomNumber = "";
for(int i = 0; i < 6; i++) {
randomNumber += Integer.toString(r.nextInt(10));
}
authNumber = Integer.parseInt(randomNumber);
}
//mail을 어디서 보내는지, 어디로 보내는지 , 인증 번호를 html 형식으로 어떻게 보내는지 작성합니다.
public String joinEmail(String email) {
makeRandomNumber();
String setFrom = "dionisos198@naver.com"; // email-config에 설정한 자신의 이메일 주소를 입력
String toMail = email;
String title = "회원 가입 인증 이메일 입니다."; // 이메일 제목
String content =
"나의 APP을 방문해주셔서 감사합니다." + //html 형식으로 작성 !
"<br><br>" +
"인증 번호는 " + authNumber + "입니다." +
"<br>" +
"인증번호를 제대로 입력해주세요"; //이메일 내용 삽입
mailSend(setFrom, toMail, title, content);
return Integer.toString(authNumber);
}
//이메일을 전송합니다.
public void mailSend(String setFrom, String toMail, String title, String content) {
MimeMessage message = mailSender.createMimeMessage();//JavaMailSender 객체를 사용하여 MimeMessage 객체를 생성
try {
MimeMessageHelper helper = new MimeMessageHelper(message,true,"utf-8");//이메일 메시지와 관련된 설정을 수행합니다.
// true를 전달하여 multipart 형식의 메시지를 지원하고, "utf-8"을 전달하여 문자 인코딩을 설정
helper.setFrom(setFrom);//이메일의 발신자 주소 설정
helper.setTo(toMail);//이메일의 수신자 주소 설정
helper.setSubject(title);//이메일의 제목을 설정
helper.setText(content,true);//이메일의 내용 설정 두 번째 매개 변수에 true를 설정하여 html 설정으로한다.
mailSender.send(message);
} catch (MessagingException e) {//이메일 서버에 연결할 수 없거나, 잘못된 이메일 주소를 사용하거나, 인증 오류가 발생하는 등 오류
// 이러한 경우 MessagingException이 발생
e.printStackTrace();//e.printStackTrace()는 예외를 기본 오류 스트림에 출력하는 메서드
}
}
}
//redis 추가
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
를 build.gradle 에 추가한다.
spring.redis.host=localhost
spring.redis.port=6379
를 추가한다.
사용자가 인증번호를 확인하고 인증번호를 입력하였을 때 받아오는 DTO입니다.
import lombok.Data;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
@Data
public class EmailCheckDto {
@Email
@NotEmpty(message = "이메일을 입력해 주세요")
private String email;
@NotEmpty(message = "인증 번호를 입력해 주세요")
private String authNum;
}
@RestController
@RequiredArgsConstructor
public class MailController {
private final MailSendService mailService;
@PostMapping ("/mailSend")
public String mailSend(@RequestBody @Valid EmailRequestDto emailDto){
System.out.println("이메일 인증 요청이 들어옴");
System.out.println("이메일 인증 이메일 :"+emailDto.getEmail());
return mailService.joinEmail(emailDto.getEmail());
}
@PostMapping("/mailauthCheck")
public String AuthCheck(@RequestBody @Valid EmailCheckDto emailCheckDto){
Boolean Checked=mailService.CheckAuthNum(emailCheckDto.getEmail(),emailCheckDto.getAuthNum());
if(Checked){
return "ok";
}
else{
throw new NullPointerException("뭔가 잘못!");
}
}
}
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class ErrorResult {
private String code;
private String message;
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice
public class ExControllerAdvice {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(NullPointerException.class)
public ResponseEntity<ErrorResult> testing(NullPointerException e){
ErrorResult errorResult=new ErrorResult("EMAIL",e.getMessage());
return new ResponseEntity<>(errorResult,HttpStatus.BAD_REQUEST);
}
}
@Service
public class MailSendService {
@Autowired
private JavaMailSender mailSender;
@Autowired
private RedisUtil redisUtil;
private int authNumber;
//추가 되었다.
public boolean CheckAuthNum(String email,String authNum){
if(redisUtil.getData(authNum)==null){
return false;
}
else if(redisUtil.getData(authNum).equals(email)){
return true;
}
else{
return false;
}
}
//임의의 6자리 양수를 반환합니다.
public void makeRandomNumber() {
Random r = new Random();
String randomNumber = "";
for(int i = 0; i < 6; i++) {
randomNumber += Integer.toString(r.nextInt(10));
}
authNumber = Integer.parseInt(randomNumber);
}
//mail을 어디서 보내는지, 어디로 보내는지 , 인증 번호를 html 형식으로 어떻게 보내는지 작성합니다.
public String joinEmail(String email) {
makeRandomNumber();
String setFrom = "dionisos198@naver.com"; // email-config에 설정한 자신의 이메일 주소를 입력
String toMail = email;
String title = "회원 가입 인증 이메일 입니다."; // 이메일 제목
String content =
"나의 APP을 방문해주셔서 감사합니다." + //html 형식으로 작성 !
"<br><br>" +
"인증 번호는 " + authNumber + "입니다." +
"<br>" +
"인증번호를 제대로 입력해주세요"; //이메일 내용 삽입
mailSend(setFrom, toMail, title, content);
return Integer.toString(authNumber);
}
//이메일을 전송합니다.
public void mailSend(String setFrom, String toMail, String title, String content) {
MimeMessage message = mailSender.createMimeMessage();//JavaMailSender 객체를 사용하여 MimeMessage 객체를 생성
try {
MimeMessageHelper helper = new MimeMessageHelper(message,true,"utf-8");//이메일 메시지와 관련된 설정을 수행합니다.
// true를 전달하여 multipart 형식의 메시지를 지원하고, "utf-8"을 전달하여 문자 인코딩을 설정
helper.setFrom(setFrom);//이메일의 발신자 주소 설정
helper.setTo(toMail);//이메일의 수신자 주소 설정
helper.setSubject(title);//이메일의 제목을 설정
helper.setText(content,true);//이메일의 내용 설정 두 번째 매개 변수에 true를 설정하여 html 설정으로한다.
mailSender.send(message);
} catch (MessagingException e) {//이메일 서버에 연결할 수 없거나, 잘못된 이메일 주소를 사용하거나, 인증 오류가 발생하는 등 오류
// 이러한 경우 MessagingException이 발생
e.printStackTrace();//e.printStackTrace()는 예외를 기본 오류 스트림에 출력하는 메서드
}
redisUtil.setDataExpire(Integer.toString(authNumber),toMail,60*5L);
}
}
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import java.time.Duration;
@Service
@RequiredArgsConstructor
public class RedisUtil {
private final StringRedisTemplate redisTemplate;//Redis에 접근하기 위한 Spring의 Redis 템플릿 클래스
public String getData(String key){//지정된 키(key)에 해당하는 데이터를 Redis에서 가져오는 메서드
ValueOperations<String,String> valueOperations=redisTemplate.opsForValue();
return valueOperations.get(key);
}
public void setData(String key,String value){//지정된 키(key)에 값을 저장하는 메서드
ValueOperations<String,String> valueOperations=redisTemplate.opsForValue();
valueOperations.set(key,value);
}
public void setDataExpire(String key,String value,long duration){//지정된 키(key)에 값을 저장하고, 지정된 시간(duration) 후에 데이터가 만료되도록 설정하는 메서드
ValueOperations<String,String> valueOperations=redisTemplate.opsForValue();
Duration expireDuration=Duration.ofSeconds(duration);
valueOperations.set(key,value,expireDuration);
}
public void deleteData(String key){//지정된 키(key)에 해당하는 데이터를 Redis에서 삭제하는 메서드
redisTemplate.delete(key);
}
}
일단 인증번호가 잘 나온다는 것은 확인이 가능하다.
이렇게 보낸다면 인증이 성공한 것이 되겠다.
참고로 key값은
redis cli 에서
확인 할 수 있다.
만료기한으로 설정한 5분이 지나면 만료가 되어
아까 설정한 NullPointerException 예외가 발생해서 지정된 형식으로 JSON 형태로 데이터가 나왔다.
좋은 글 감사합니다.
글을 읽으며 따라하다 한가지 의문이 생겼습니다!
인증번호 메일을 보냈을 때 컨트롤러에서 프론트단으로 인증번호를 넘겨주는데 혹시 따로 넘겨주는 이유가 있는건가요?
사용자 입장에선 이메일을 통해 인증번호를 확인할 것이고
인증번호에 대한 검증은 백엔드단에서 이루어지는데 굳이 넘겨준 이유가 있으신건지 궁금합니다!!
단순히 테스트용도인가요?