본문 바로가기
Java/Spring

[Spring] 이메일 인증번호를 통한 회원가입

by 전재경 2023. 1. 11.

이메일 인증을 통해 구현하는 프로젝트를 만들다가 이 부분에 막혀서 몇일을 고민을 하고 고생을 했다.

 

우선 링크를 통한 회원가입, 인증번호를 통한 회원가입 2가지의 방법이 있는데

이번에는 인증번호를 통한 회원가입을 진행하겠다.

 

1. Spring 라이브러리 다운받기

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

라이브러리를 추가해주자 !!

 

Spring에서 메일 서버를 사용하기 위해서는 서버와 연결을 해야한다.

Spring 자체적으로 메일을 주고받는것이 아닌 stmp 메일 서버를 사용해서 메일을 보내는 만큼 spring 안에 어떤 메일 서버와 연결하는지 관련 정보들을 넣어줘야 한다.

 

가장 많이 사용하는 법 2가지는 네이버, 구글 Gmail 이다.

우선 가장먼저 네이버 설정 메일 -> 환경설정에서 설정을 해주면 된다.

 

구글 Gmail의 경우는 조금 더 복잡해 진다.

가장 먼저 구글은 구글의 보안 정책의 변경에 따라서 

2022년 5월 30부터는 보안 수준이 낮은 앱의 액세스가 활성된 계정으로는 SMTP 서버를 이용할 수 없게 되었습니다.

 

그렇다면 어떻게 이용할 수 있는 것인가 ?

2단계 인증 활성화를 통해 앱 패스워드를 받고 그 패스워드로 설정하면 해결된다.

2단계 인증 활성화

보안 수준이 높은 Gmail 계정을 만들기 위해서는 먼저 2단계 인증을 활성화하여야 합니다. 구글 계정 관리 > 보안 메뉴에 들어가면 아래와 같이 2단계 인증을 사용중인지를 확인할 수 있습니다.

 

구글 계정 -> 보안

 

Google에 로그인 -> 2단계 인증에서 전화 또는 문자메시지를 통해 인증을 받고

 

 

앱 비밀번호 추가

2단계를 활성화 시킨 뒤 앱 비밀번호를 추가해준다. 여기서 사용되는 앱 비밀번호가 Spring에 들어가는 패스워드가 된다.

여기에 있는 windows 컴퓨터용 앱 비밀번호를 기억해두자.

 

이제 Gmail로 가서 설정을 해줘야한다.

 

 

설정 -> 모든설정보기 -> 전달 및 POP/IMAP을 설정해주자

 

Gmail 이메일 발송 한도

 

보통 수신자가 수백명을 넘어가는 대량 발송 이메일은 대량 이메일 발송 서비스나 마케팅 자동화 솔루션을 활용해 발송 및 관리하는 것이 가장 효율적이다. 하지만 이런 전문 솔루션을 활용하기 위해서는 추가 비용이 들기 때문에 비용 절감을 위해 대부분의 소규모 기업들은 웹메일을 통한 대량 이메일 발송을 고려할 수 밖에 없다.

Gmail의 개인 계정의 경우 일일 전송한도는 하루(24시간)에 500개로 제한되어 있다.
Google에서는 각 이메일 주소를 각기 다른 이메일로 계산하고 있기 때문에 5명의 수신자에게 보낸 하나의 이메일을 5개의 이메일로 계산한다.

  • 1회 전송 한도: 100개 (메일 당 최대 100명에게 이메일을 전송할 수 있다.)
  • 일일 (24시간) 전송 한도: 500개
  • 메일 당 수신자: 외부 500개

Google Workspace (전 G Suite)를 활용하는 경우의 일일 전송한도는 하루(24시간)에 2,000개이다. 더불어 Google의 SMTP 서비스를 사용하는 경우 메일 당 최대 100명의 수신자에게 이메일을 전송할 수 있다.

타 메일 발송 한도

네이버

  • 시간 한도: 시간당 최대 30회
  • 1회 전송 한도: 100개

다음 / 다음스마트워크

  • 1회 전송 한도: 150개
  • 일일 (24시간) 전송 한도: 10,000개

 

이까지 기본 세팅이 끝났다.

 

2. MailConfig

@Configuration
public class MailConfig {
    @Bean
    public JavaMailSender javaMailService() {
        JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();

        javaMailSender.setHost("smtp.gmail.com");	//네이버의 경우 smtp.naver.com
        javaMailSender.setUsername("ID@메일.com");	//메일
        javaMailSender.setPassword("password");		//패스워드

        javaMailSender.setPort(587);	//ssl의 경우 465? 네이버의 경우 465?(*확실하지는 않아 추후 다루고 수정하겠습니다)

        javaMailSender.setJavaMailProperties(getMailProperties());

        return javaMailSender;
    }

    private Properties getMailProperties() {
        Properties properties = new Properties();
        properties.setProperty("mail.transport.protocol", "smtp");
        properties.setProperty("mail.smtp.auth", "true");
        properties.setProperty("mail.smtp.starttls.enable", "true");
        properties.setProperty("mail.debug", "true");
        properties.setProperty("mail.smtp.starttls.trust","smtp.gmail.com");	//네이버의 경우 stmp.naver.com 변경
        properties.setProperty("mail.smtp.starttls.enable","true");	//starttls <-> ssl로 변경도 확인(*starttls 와 ssl은 추후 다루도록 하겠습니다.)
        return properties;
    }
}

 

 

*수정 - 587포트를 사용하면 됩니다.

 

587포트와 465포트의 차이점

PORT 465 465번 포트는 위의 RFC 821 규약을 보장하는 IETF(Internet Engineering Task Force) 라는 기관 말고, IANA(Internet Assigned Numbers Authority)라는 기관에서 제안한 포트이다. IANA는 인터넷 인프라의 많은 핵심을

jkadv.tistory.com

 

 

*수정 - starttls를 사용하면 됩니다.

 

STARTTLS, SSL및 TLS 차이점

STARTTLS는 두 컴퓨터 간의 통신 채널을 암호화하는 방법을 제공하는 SSL/TLS(Secure Socket Layer / Transport Layer Security)를 화용하여 비보안 연결을 보안 연결로 변환 SSL과 TLS는 특정 버전의 프로토콜을 명

jkadv.tistory.com

 

 

 

3. MailService

@Service
@RequiredArgsConstructor
public class MailService {

    private final JavaMailSender javaMailSender;

    private final MemberRepository memberRepository;
    private String authNum;

    private void checkDuplicatedEmail(String email){
        memberRepository.findByEmail(email).ifPresent(
                m -> {throw new IllegalArgumentException(DUPLICATE_EMAIL_MSG.getMsg());
                });
    }
    public MimeMessage createMessage(String to)throws MessagingException, UnsupportedEncodingException{

        MimeMessage message = javaMailSender.createMimeMessage();

        message.addRecipients(Message.RecipientType.TO, to);	//보내는 대상
        message.setSubject("회원가입 이메일 인증");		//제목

        String msgg = "";
        msgg += "<div style='margin:100px;'>";
        msgg += "<h1> 안녕하세요</h1>";
        msgg += "<br>";
        msgg += "<p>아래 코드를 회원가입 창으로 돌아가 입력해주세요<p>";
        msgg += "<br>";
        msgg += "<div align='center' style='border:1px solid black; font-family:verdana';>";
        msgg += "<h3 style='color:blue;'>회원가입 인증 코드입니다.</h3>";
        msgg += "<div style='font-size:130%'>";
        msgg += "CODE : <strong>";
        msgg += authNum + "</strong>";	//메일 인증번호
        msgg += "</div>";
        message.setText(msgg, "utf-8", "html");
        message.setFrom(new InternetAddress("jkdev12345@gmail.com", "BulletBox"));
		
        //해당 메시지는 아래에 예시
        
        return message;
    }

    public String createCode(){
        Random random = new Random();
        StringBuffer key = new StringBuffer();

        for(int i = 0; i< 8; i++){	//인증 코드 8자리
            int index = random.nextInt(3);	//0~2까지 랜덤, 랜덤값으로 switch문 실행

            switch (index) {
                case 0 -> key.append((char) ((int) random.nextInt(26) + 97));
                case 1 -> key.append((char) (int) random.nextInt(26) + 65);
                case 2 -> key.append(random.nextInt(9));
            }
        }
        return authNum = key.toString();
    }

	//메일 발송
    //등록해둔 javaMail 객체를 사용해서 이메일 send
    public String sendSimpleMessage(String sendEmail) throws Exception{

        authNum = createCode();	//랜덤 인증번호 생성

        MimeMessage message = createMessage(sendEmail);	//메일 발송
        try{
            javaMailSender.send(message);
        }catch (MailException es){
            es.printStackTrace();
            throw new IllegalArgumentException();
        }

        return authNum;
    }

}

아래와 같이 메일을 구성하여 보내게 됩니다.

 

아직은 메일에 대한 Service만 작성했기 때문에 메일이 보내질리 없겠죠 !?

Controller에서 인증번호를 보내는 동작을 하도록 합시다 !

 

4. MemberController

@RestController
@RequestMapping("/api/members")
@RequiredArgsConstructor
public class MemberController {

    private final MemberService memberService;

    private final MailService mailService;


    @PostMapping("/signup/email-validate")
    public Response<?> mailConfirm(HttpSession httpSession, @RequestParam(value = "email", required = false) String email, MemberDto memberDto) throws Exception{
        String code = mailService.sendSimpleMessage(email, memberDto);
        System.out.println("인증코드 : " + code);
        httpSession.setAttribute("code", code);
        return Response.success(200, "이메일 인증 메일이 전송되었습니다.", code);
    }

    @PostMapping("/signup/verifyCode")
    public Response<?> verifyCode(HttpSession httpSession, @RequestBody VerifyCodeDto verifyCodeDto){
        boolean result=false;
        if(httpSession.getAttribute("code").equals(verifyCodeDto.getVerifyCode())){
            result = true;
        }else{
            throw new NoSuchElementException(DIFFERENT_CODE_MSG.getMsg());
        }
        return Response.success(200,"이메일 인증이 완료되었습니다.", result);
    }

}

회원가입과 로그인 부분은 우선 지우고 저희가 사용하려고 하는 부분만 보도록 하겠습니다.

 

일단 public Response<?>로 받아오는 것은 저희 프로젝트의 컨벤션 설정때문에 작성된 것이고

세션을 넣어준 이유는 인증번호를 세션에 넣고 다음에 있을 인증번호 확인을 하기 위해서이다.

    @PostMapping("/signup/email-validate")
    public Response<?> mailConfirm(HttpSession httpSession, @RequestParam(value = "email", required = false) String email, MemberDto memberDto) throws Exception{
        String code = mailService.sendSimpleMessage(email, memberDto);
        System.out.println("인증코드 : " + code);
        httpSession.setAttribute("code", code);
        return Response.success(200, "이메일 인증 메일이 전송되었습니다.", code);
    }

세션도, 정해진 컨벤션도 따로 없다면 아래와 같이 작성을 하면 된다.

@PostMapping("/signup/email-validate")
    public String mailConfirm(@RequestParam(value = "email", required = false) String email) throws Exception{
        String code = mailService.sendSimpleMessage(email);
        System.out.println("인증코드 : " + code);
        return code;
    }
}

 

 

이렇게 Postman에서도 정상작동 되는것을 확인할 수 있다.(첫번째 코드 기준)

 

이제 인증번호도 메일로 전송을 했으니 이 인증번호를 통해 회원가입을 진행해야 한다.

 

이 부분에서 많이 헤맸다.

 

어떤값을 어떻게 넣고 비교를 해야 하는지

 

내가 찾은 답은 

    @PostMapping("/signup/verifyCode")
    public Response<?> verifyCode(HttpSession httpSession, @RequestBody VerifyCodeDto verifyCodeDto){
        boolean result=false;
        if(httpSession.getAttribute("code").equals(verifyCodeDto.getVerifyCode())){
            result = true;
        }else{
            throw new NoSuchElementException(DIFFERENT_CODE_MSG.getMsg());
        }
        return Response.success(200,"이메일 인증이 완료되었습니다.", result);
    }

 

세션을 통해 인증번호를 보낼때 사용한 setAttribute의 값을 getAttribute로 가져와서 작성한 코드와 비교한다.

 

예외처리는 Common에서 따로 관리해주기 때문에 그런 경우가 아니라면 본인이 생각하는 적절한 예외처리와 String을 넣어주면 된다. 

 

    @PostMapping("/signup/verifyCode")
    public String verifyCode(HttpSession httpSession, @RequestBody VerifyCodeDto verifyCodeDto){
        boolean result=false;
        if(httpSession.getAttribute("code").equals(verifyCodeDto.getVerifyCode())){
            result = true;
        }else{
            throw new NoSuchElementException("인증번호가 일치하지 않습니다.");
        }
        return result;
    }

우선 우리의 프로젝트는 프론트측에 true, false값만 주면 되어서 이렇게만 처리하였다.

그게 아닌 다른 경우는 boolean result같은 값이 아니라 본인의 입맛대로 수정하여 사용하면 된다.

 

 

처음 해보는 작업이다보니 여러 블로그를 보며 이렇게 고민하고 저렇게 고민했다.

 

확실히 몇날 몇일을 머리아프게 고민하며 해결하니 어떤흐름인지 알게된 것 같다.

그렇게 어려운 문제도 아니였는데 내 입맛에 맞는 정보를 찾기가 정말 어려운 것 같다.

 

내가 작성한 글도 분명 누군가에게는 가장 알맞는 글이기를 바라며 정리를 끝마치겠다.

 

세션에 대한 정보

 

Spring - 16. session 사용하기

로그인에서 정보를 유지해야할때 사용하는 기능입니다. 대부분 로그인 쪽에서 많이사용한다고 됩니다. 스프링에서는 2가지 방법이있습니다. 1. HttpSession 2. @SessionAttributes() 방법이있습니다. 1. Htt

memory-develo.tistory.com

 

이메일 정보 참고한 블로그

 

Springboot 이메일 인증 구현 - limjunho

Summry 본 문서에서는 인증번호를 생성해 이메일을 보내는 방법을 정리한다. 본 문서에서 다룬 방법은 Gmail을 기준으로 작성됨. send me email if you have any questions. 구글 계정 설정 2022년 5월 기준 보안

limjunho.github.io

 

 

(Spring /Java)이메일 인증 구현하기😎

요즘사이트에서 회원가입을 하다보면 꼭! 각종 인증들 이있다. 사용자 입장에서는 굳이?? 이걸 해야하나? 싶기도 하겠지만 사업자 입장에서보면 각종 Black Consumer 외 다른 부분에 대해서 보안을

devofroad.tistory.com

 

 

Spring Boot - 회원가입 시 인증 메일 발송(feat.네이버 이메일 연결)

오늘은 학원에서 파이널 프로젝트때 사용했던 사이트 회원 가입 시 인증 메일을 발송하는 부분을 정리해볼까 한다. 당연히 SpringBoot 를 사용하였고, Gradle 을 사용해서 라이브러를 import 하였다.

terianp.tistory.com

참고한 블로그가 수없이 더 많은데... 다 정리를 못했다.

그래도 이런 내 글이 누군가에게는 입맛에 딱맞는 원하는 정보여서

나같이 답답해서 머리아픈 일이 없으면 좋겠다 !

댓글