본문 바로가기
320x100
320x100

Google SMTP 사용

추가로 공부하세요~! 안타깝지만 제 블로그 글이 아님..ㅎ

- SMTP, POP3, IMAP 차이

https://m.blog.naver.com/ijoos/221742035684

 

SMTP, POP3, IMAP 차이

이번에는 SMTP, POP3, IMAP의 차이에 대해서 배워보고자 한다. SMTP SMTP는 Simple M...

blog.naver.com

- SSL, TSL 차이

https://ko.gadget-info.com/difference-between-ssl

 

SSL과 TLS의 차이점

SSL과 TLS 간에는 약간의 차이점이 있습니다. SSL은 목적을 달성하기위한 가장 중요한 접근 방법이며 모든 브라우저에서 지원됩니다. TLS는 보안 및 개인 정보 보호 기능이 강화 된 후속 인터넷 표

ko.gadget-info.com

# application.yaml에 작성

spring:
  mail:
    default-encoding: UTF-8
    host: smtp.gmail.com
    port: 587
    username: [------@gmail.com] <자기 구글 E-mail주소>
    password: [-------] <구글 비밀번호=>앱 비밀번호로 변경>
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
    protocol: smtp

 

 

----------------------------------------------------------------------------------------------------------------------

 

 

org.springframework.mail.MailAuthenticationException: Authentication failed; 
nested exception is javax.mail.AuthenticationFailedException

위 에러가 발생하는 경우 보안 수준이 낮은 앱의 액세스를 허용해 주어야 합니다.

라고 하는데 어차피 2022년 5월 기준 끝나기도 하고, 나의 경우 해결이 안됐었다

해결방법

구글(Gmail)을 기준으로 작성하였다

1) 구글에 로그인 한 후 오른쪽 위 동그란 프로필을 눌러서 Google 계정 관리에 들어가자

2) 왼쪽의 보안 탭으로 들어가자

3) 2단계 인증을 활성화한다(앱 비밀번호를 받기 위함)

4) 앱 비밀번호를 클릭해서 - 메일 / - Windows 컴퓨터를 선택한다

5) 생성을 누르고 받아온 기기용 앱 비밀번호를 yaml 파일의 pass로 바꿔준다

 

사용 방법들

 

1.  Springboot-starter-mail

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

 

참고로 template엔진은 thymeleaf, freemarker, velocity, mustache 등등 다양하다.

텍스트 혹은 객체를 html 또는 각각의 파일들에 매핑시켜주는 역할을 한다

 

# Controller

import com.example.testproject.dto.MailDto;
import com.example.testproject.service.MailService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

@RequiredArgsConstructor
@Controller
public class MailController {
    private final MailService mailService;

    @GetMapping("/mail")
    public String dispMail() {
        return "mail";
    }

    @PostMapping("/mail")
    public void execMail(MailDto mailDto) {
        mailService.mailSimpleSend(mailDto);
//        mailService.mailSend(mailDto);
    }

}

# Service

import com.example.testproject.dto.MailDto;
import com.example.testproject.handler.MailHandler;
import lombok.AllArgsConstructor;
import org.apache.commons.lang.RandomStringUtils;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
@AllArgsConstructor
public class MailService {
    private JavaMailSender mailSender;
    private static final String FROM_ADDRESS = "no_repy@boki.com";

    @Async
    public void mailSimpleSend(MailDto mailDto) {
        SimpleMailMessage message = new SimpleMailMessage();
        String msg = mailDto.getMessage() + "\n인증번호는 " + RandomStringUtils.randomAlphanumeric(6) + "입니다";
        message.setTo(mailDto.getAddress());
//        message.setFrom(MailService.FROM_ADDRESS); // 구글 정책 변경으로 설정한 gmail로 가게됨
        message.setSubject(mailDto.getTitle());
        message.setText(msg);
        mailSender.send(message);
    }

    @Async
    public void justSend(MailDto mailDto) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setSubject(mailDto.getTitle());
        message.setText(mailDto.getMessage());
        mailSender.send(message);
    }
}

 

비동기를 달아줬다. Configration을 따로 하는 객체 혹은 SpringBootApplication위에 @EnableAsync를 달아줘야한다

비동기는 Config해주지 않으면 스레드가 Integer.MAX_VALUE개만큼 생성되므로 컨피그가 필요하다

import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;

public class AsyncConfig extends AsyncConfigurerSupport {

    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(500);
        //executor.setThreadNamePrefix("hanumoka-async-");
        executor.initialize();
        return executor;
    }
}

나중에 제대로 포스팅하겠다!

 

# DTO

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public class MailDto {
    private String address;
    private String title;
    private String message;
}

 

# resources / templates / mail.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>메일 발송</title>
</head>
<body>
<h1>메일 발송</h1>

<form th:action="@{/mail}" method="post">
    <input name="address" placeholder="이메일 주소"> <br>
    <input name="title" placeholder="제목"> <br>
    <textarea name="message" placeholder="메일 내용을 입력해주세요." cols="60" rows="20"></textarea>
    <button>발송</button>
</form>
</body>
</html>

# 테스트


2.  javax.mail:mail or com.sun.mail:javax.mail

implementation 'javax.mail:mail:1.4.7'

둘 중에 하나

implementation 'com.sun.mail:javax.mail:1.6.2'

그냥 static main 메서드에서 작성을 했다

import org.apache.commons.lang.RandomStringUtils;

import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Properties;

public class VerifyEmail {
    public static void main(String[] args) {
//        String recipient = "받는사람이메일@co.kr";
        String recipient = "[이메일]@naver.com";
//        String code = "abc";
        String msg = "인증번호는 " + RandomStringUtils.randomAlphanumeric(6) + "입니다";

        // 1. 발신자의 메일 계정과 비밀번호 설정
//        final String user = "전송자이메일@co.kr";
        final String user = "[구글 이메일 주소]";
//        final String user = "no_repy@boki.com"; // 구글 정책 변경으로 이거로 전송 안됨
//        final String password = "********";
        final String password = "[발급받은 앱 기기용 비밀번호]";

        // 2. Property에 SMTP 서버 정보 설정
        Properties prop = new Properties();
        prop.put("mail.smtp.host", "smtp.gmail.com");
        prop.put("mail.smtp.port", 465);
        prop.put("mail.smtp.auth", "true");
        prop.put("mail.smtp.ssl.enable", "true");
        prop.put("mail.smtp.ssl.trust", "smtp.gmail.com");

        // 3. SMTP 서버정보와 사용자 정보를 기반으로 Session 클래스의 인스턴스 생성
        Session session = Session.getDefaultInstance(prop, new javax.mail.Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(user, password);
            }
        });

        // 4. Message 클래스의 객체를 사용하여 수신자와 내용, 제목의 메시지를 작성한다.
        // 5. Transport 클래스를 사용하여 작성한 메세지를 전달한다.

        MimeMessage message = new MimeMessage(session);
        try {
            message.setFrom(new InternetAddress(user));

            // 수신자 메일 주소
            message.addRecipient(Message.RecipientType.TO, new InternetAddress(recipient));

            // Subject
            message.setSubject("임시비밀번호 발송메일");

            // Text
            message.setText(msg);

            Transport.send(message);    // send message

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

    }
}


3.  WebClient

implementation 'org.springframework.boot:spring-boot-starter-webflux:2.6.4'

non-block 방식 이용

// 호출하는 쪽

    @Builder
    @Getter
    static class MailDto {
        private String address;
        private String title;
        private String message;
    }

    @PostMapping("/mail")
    public ResponseEntity<Object> mail() {
        final String MAIL_API = "localhost:8090";
        String msg = "인증번호: " + RandomStringUtils.randomAlphanumeric(6);
        MailDto mailDto = MailDto.builder().address("[받는 쪽 이메일 입력]").message(msg).title("인증번호 발송").build();
        ObjectMapper objectMapper = new ObjectMapper();
        MultiValueMap<String, String> multiValueMap = convert(objectMapper, mailDto);
        webClient.method(HttpMethod.POST)
                .uri(MAIL_API + "/mail")
                .bodyValue(multiValueMap)
                .retrieve()
                .bodyToMono(Void.class)
                .subscribe();
        return ResponseEntity.ok("메일발송 SUCCESS");
    }

//    @Slf4j
//    @NoArgsConstructor(access = AccessLevel.PRIVATE)
//    public abstract class MultiValueMapConverter {

//        public static MultiValueMap<String, String> convert(ObjectMapper objectMapper, Object dto) { // (2)
        public MultiValueMap<String, String> convert(ObjectMapper objectMapper, Object dto) { // (2)
            try {
                MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
                Map<String, String> map = objectMapper.convertValue(dto, new TypeReference<Map<String, String>>() {}); // (3)
                params.setAll(map); // (4)

                return params;
            } catch (Exception e) {
                log.error("Url Parameter 변환중 오류가 발생했습니다. requestDto={}", dto, e);
                throw new IllegalStateException("Url Parameter 변환중 오류가 발생했습니다.");
            }
//        }
    }

 

// 호출 당하는 쪽

import com.example.testproject.dto.MailDto;
import com.example.testproject.service.MailService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

@RequiredArgsConstructor
@Controller
public class MailController {
    private final MailService mailService;

    @GetMapping("/mail")
    public String dispMail() {
        return "mail";
    }

    @PostMapping("/mail")
    public void execMail(MailDto mailDto) {
//        mailService.mailSimpleSend(mailDto);
        mailService.justSend(mailDto);
//        mailService.mailSend(mailDto);
    }

}

 

추가로 이벤트방식으로 코드를 작성할 수도 있다

실질적으로는 실서비스를 할때는 AWS SES(Simple Email Service)를 이용하는 등 다른 방법을 찾아봐야하겠다!

 

 

참고:

https://jojoldu.tistory.com/478

https://victorydntmd.tistory.com/342

 

320x100

댓글