개인프로젝트/피그스페이스

[Spring Boot] Ggmail로 인증 링크를 포함한 메일 보내기

DEV_HEO 2022. 10. 12. 21:01
320x100

 

https://sdy-study.tistory.com/269

 

Spring Boot 로 이메일 회원가입 하기

토이 프로젝트를 하다가 회원가입과 로그인, 로그아웃 기능을 만들게 되었는데 그때 사용했던 개념들을 정리하고자 한다 먼저 이메일을 통해서 회원가입을 했던 과정을 정리한다 - 개발환경 spr

sdy-study.tistory.com

 위 글을 참고하여 재사용 가능하도록 커스텀해서 작성하였다.

 


1. Gmail SMTP 설정

 

예전에는 보안 수준 낮은 앱의 액세스 허용만 체크하면 됐지만

최근에는 정책이 바뀌어 2차 인증을 해주고 앱 비밀번호를 기억해둬야 한다.

 

https://hyunmin1906.tistory.com/276

 

[Go] Google Gmail SMTP 설정 방법 및 메일 전송

■ SMTP 간이 우편 전송 프로토콜(Simple Mail Transfer Protocol)의 약자. 이메일 전송에 사용되는 네트워크 프로토콜이다. 인터넷에서 메일 전송에 사용되는 표준이다. 1982년 RFC821에서 ..

hyunmin1906.tistory.com

 

 


2. gradle, yml 설정

build.gradle 

//SMTP
implementation 'org.springframework.boot:spring-boot-starter-mail'
//thymeleaf
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

 

application.yml

spring:
  mail:
    host: smtp.gmail.com
    port: 587
    username: 이메일주소
    password: 앱비밀번호
    properties:
      mail:
        smtp:
          starttls:
            enable: true
            requred: true
          auth: true
          connectiontimeout: 5000
          timeout: 5000
          writetimeout: 5000

 

 


3. 클래스 작성(메일 전송)

위에 작성한 출처 블로그에서 작성한 코드를 기본으로 작성했고,

난 이메일 인증을 다시 사용할 예정이기 때문에

재사용 가능한 소스로 약간 커스텀했다.

 

회원가입 소스는 작성돼있다고 가정

 

- EmailToken

토큰id, user id, 만료여부 저장

import java.time.LocalDateTime;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

import org.hibernate.annotations.GenericGenerator;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@ToString
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class EmailToken {

	private static final long EMAIL_TOKEN_EXPIRATION_TIME_VALUE = 5L;    
    
	@Id
	@GeneratedValue(generator = "uuid2")
	@GenericGenerator(name = "uuid2", strategy = "uuid2")
	@Column(length = 36)
	private String id;

	private LocalDateTime expirationDate;

	private boolean expired;

	private String mbrNo;


	public static EmailToken createEmailToken(String mbrNo) {
		EmailToken emailToken = new EmailToken();
		emailToken.expirationDate = LocalDateTime.now().plusMinutes(EMAIL_TOKEN_EXPIRATION_TIME_VALUE);
		emailToken.expired = false;
		emailToken.mbrNo = mbrNo;

		return emailToken;
	}


	public void setTokenToUsed() {
		this.expired = true;
	}


}

 

 

- EmailTokenService

토큰 생성, 토큰 확인

import java.time.LocalDateTime;
import java.util.Optional;

import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import com.pigplace.comn.entity.EmailToken;
import com.pigplace.comn.repository.EmailTokenRepository;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RequiredArgsConstructor
@Service
public class EmailTokenService {

	private final EmailTokenRepository emailTokenRepository;


	// 이메일 인증 토큰 생성
	public EmailToken createEmailToken(String mbrNo, String receiverEmail) {

		Assert.notNull(mbrNo, "mbrNo는 필수입니다");
		Assert.hasText(receiverEmail, "receiverEmail은 필수입니다.");

		// 이메일 토큰 저장
		EmailToken emailToken = EmailToken.createEmailToken(mbrNo);
		System.out.println("EmailToken >> " + emailToken);
		emailTokenRepository.save(emailToken);


		return emailToken;

	}

	// 유효한 토큰 가져오기
	public EmailToken findByIdAndExpirationDateAfterAndExpired(String emailTokenId) throws Exception {
		Optional<EmailToken> emailToken = emailTokenRepository
			.findByIdAndExpirationDateAfterAndExpired(emailTokenId, LocalDateTime.now(), false);

		// 토큰이 없다면 예외 발생
		return emailToken.orElseThrow(() -> new Exception("BaseResponseStatus.DATABASE_ERROR"));
	}

}

 

- MailConentBuilder

메일 content 생성

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class MailContentBuilder {

    private final TemplateEngine templateEngine;

    @Value("${server.url}")
    private String serverUrl;

    public String signupBuild(String tokenId) {
        Context context = new Context();
        context.setVariable("link", "http://"+ serverUrl +"/member/confirm-email/" + tokenId);
        return templateEngine.process("joinMail", context);
    }
}

 

 

- joinMail.html

MailContentBuilder 에서 생성한 link 를 a 태그 속성에 추가한다.

<!DOCTYPE html>
<html lang="ko" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
    <head>
    	<meta charset="UTF-8">
        <title>이메일 인증 링크</title>
    </head>
    <body>
        <span>계정 활성화를 위해서 아래의 인증 링크를 클릭해주세요.</span><br>
        <a th:href="${link}">인증 링크</a>
    </body>
</html>

 

 

 EmailSenderService

메일을 보내는 서비스

import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.mail.javamail.MimeMessagePreparator;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import com.pigplace.common.support.PigException;
import com.pigplace.member.vo.EmailVO;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class EmailSenderService {

	private final JavaMailSender javaMailSender;

	@Async
	public void sendEmail(EmailVO emailVO) throws Exception {


		MimeMessagePreparator messagePreparator = mimeMessage -> {
            MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
//            messageHelper.setFrom("보내는 사람 이메일 ");
            messageHelper.setTo(emailVO.getReceiverEmail());
            messageHelper.setSubject(emailVO.getSubject());
            messageHelper.setText(emailVO.getText(), true);

            System.out.println("##############"+emailVO.getText());
        };

        try {
        	javaMailSender.send(messagePreparator);
//            log.info("활성화 메일이 보내졌다");
        } catch (MailException e) {
//            log.error(String.valueOf(e));
        	e.printStackTrace();
            throw new PigException("메일을 여기로 보내는 중 에러 발생 :  " + emailVO.getReceiverEmail());
        }
	}
}

 

- JoinController

MailSenderService를 호출하는 컨트롤러.

회원가입 요청과 동시에 인증 요청 메일 보내기

회원가입하는 controller에 작성하였다.

//토큰 생성
EmailToken emailToken = emailTokenService.createEmailToken(user.getMbrNo(), user.getUserId());

String message = mailContentBuilder.signupBuild(emailToken.getId());

EmailVO emailVO = new EmailVO();
emailVO.setReceiverEmail(user.getUserId());
emailVO.setSubject("PigSpace가입을 환영합니다.");
emailVO.setText(message);

 

메일을 보내면

인증 링크를 포함한 메일이 전송된다.

 

css는 전혀 없어서 나중에 꾸밀 예정

 

 

- EmailController

위에서 전송한 메일에서 인증 링크를 클릭 했을 때

토큰을 확인하는 소스

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.pigplace.common.support.ControllerSupport;
import com.pigplace.common.support.ResponseEntity;
import com.pigplace.comn.service.EmailService;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/member")
public class EmailController extends ControllerSupport{

	private final EmailService emailService;

	@GetMapping("/confirm-email/{tokenId}")
	public ResponseEntity<?> viewConfirmEmail(@PathVariable("tokenId") String tokenId) {
		try {
			boolean result = emailService.verifyEmail(tokenId);
//			return new BaseResponse<>(result);
		} catch (Exception exception) {
//			return new BaseResponse<>(exception.getStatus());
		}
		return getOkResponse();
	}

}

 

320x100