[토이프로젝트]당근마켓 구현하기(8) - 이메일 인증 구현

gamja·2022년 11월 21일
0
post-thumbnail

사이트마다 회원가입 시 이메일 인증을 요구로 한다. 따라서 나도 이메일 인증을 구현 하기로 했다.

구현 전 설정

1. 구글 2단계 인증

  • 아마 핸드폰 인증을 통해 2단계 인증 설정이 가능한 것으로 안다. 쨋든 2단계 인증을 마무리 해준다.

2. 구글 앱 비밀번호 사용


1. 앱 선택 - 기기 선택 - 생성버튼

  1. 앱 비밀번호를 복사해 둔다.

이전에는 보안 수준이 낮은 앱의 액세스 허용을 통해서 SMTP를 사용할 수 있었다. 하지만 요즘에는 구글이 비밀번호 2단계 인증을 사용하면서도 SMTP를 사용할 수 있도록 앱 비밀번호 라는 것을 만들었다.

앱 비밀번호는 나중에 config를 설정할 때 이메일 패스워드 대신 사용하게 될 것이다.


구현 시작

build.gradle

	//spring-boot-starter-mail
	implementation 'org.springframework.boot:spring-boot-starter-mail'
	// mail 서포트
	implementation 'org.springframework:spring-context-support:5.3.24'
	//javax.mail
	implementation 'com.sun.mail:javax.mail:1.6.2'

signup.html

 <div class="form-floating mb-1">
    <input type="text" class="form-control" id="email" name="email" placeholder="email">
    <label for="email">이메일</label>
  </div>
  <div class="form-floating mb-1">
    <button type="button" class="btn btn-outline-secondary" id="mail-check-btn">본인인증</button>
  </div>
  <div class="form-floating mb-1">
    <input type="text" class="form-control" id="email-auth" name="email-auth" placeholder="인증번호 6자리를 입력해주세요." disabled style="background-color: #b3b2b217">
    <p id="mail-check-warn" class="mail-check-warn"></p>
  </div>

email.js

// 이메일 인증
$('#mail-check-btn').click(function() {
    const email = $('#email')[0].value; // 이메일 주소값 얻어오기!
    console.log('완성된 이메일 : ' + email); // 이메일 오는지 확인
    const checkInput = $('#email-auth'); // 인증번호 입력하는곳

    $.ajax({
        type : "GET",
        url : "/account/email?email=" + email, // GET방식이라 Url 뒤에 email을 붙힐수있다.
        success : function (data) {
            console.log("아래는 서버에서 전달 받은 데이터 값");
            console.log("data : " +  data);
            checkInput.attr('disabled',false);
            checkInput.css('background', 'white');
            code =data;
            alert('인증번호가 전송되었습니다.');
            console.log("인증번호 전송까지는 오케");
        }
    }); // end ajax
}); // end send eamil


// 인증번호 비교
// blur -> focus가 벗어나는 경우 발생
$('#email-auth').blur(function () {
    console.log("blur 작동은 하나?")
    const inputCode = $('#email-auth')[0].value;
    console.log("inuptCode : " + inputCode);
    const resultMsg = $('#mail-check-warn')[0];

    if(inputCode === code){
        console.log("code : " + code);console.log("inputCode : " + inputCode);
        resultMsg.innerText = '인증번호가 일치합니다.';
        resultMsg.style.color = 'green';
        $('#mail-check-btn').disabled = true;
        $('#email').readonly = true;

        // $('#userEmail2').attr('onFocus', 'this.initialSelect = this.selectedIndex');
        // $('#userEmail2').attr('onChange', 'this.selectedIndex = this.initialSelect');
    }else{
        resultMsg.html('인증번호가 불일치 합니다. 다시 확인해주세요!.');
        resultMsg.style.color = 'red';
    }
});

AccountController


    // 이메일 인증
    @GetMapping(value = "/email")
    @ResponseBody
    public String checkEmail(String email) {
        System.out.println("받은 이메일 : " + email);

        String data = emailAuthService.joinEmail(email);
        return data;
    }

EmailService

public interface EmailService {
    String sendSimpleMessage(String to)throws Exception;
}

EmailAuthService

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

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.util.Random;

@Service
@RequiredArgsConstructor
public class EmailAuthService implements EmailService{

    @Autowired
    private JavaMailSender mailSender;
    private int authNumber;     // 난수 발생

    public void makeRandomNumber() {
        // 난수의 범위 111111 ~ 999999 (6자리 난수)
        Random r = new Random();
        int checkNum = r.nextInt(888888) + 111111;
        System.out.println("인증번호 : " + checkNum);
        authNumber = checkNum;
    }


    //이메일 보낼 양식!
    public String joinEmail(String email) {
        makeRandomNumber();
        String setFrom = "qhtmf2059@gmail.com"; // email-config에 설정한 자신의 이메일 주소를 입력
        String toMail = email;
        String title = "회원 가입 인증 이메일 입니다."; // 이메일 제목
        String content =
                "당근마켓 클론코딩 홈페이지를 방문해주셔서 감사합니다." +    //html 형식으로 작성 !
                        "<br><br>" +
                        "인증 번호는 " + authNumber + "입니다." +
                        "<br><br>" +
                        "회원가입 페이지로 돌아가 회원가입을 마치시기 바랍니다.";   // 이메일 내용
        System.out.println("메일 데이터 : " + title);
        System.out.println("메일 데이터 : " + content);
        System.out.println("메일 데이터 : " + setFrom);

        mailSend(setFrom, toMail, title, content);

        return Integer.toString(authNumber);
    }

    //이메일 전송 메소드
    public void mailSend(String setFrom, String toMail, String title, String content)  {
        System.out.println("mailSend 확인 완료");
        MimeMessage mailMessage = mailSender.createMimeMessage();
        try {
            mailMessage.addRecipients(MimeMessage.RecipientType.TO, toMail);
            mailMessage.setSubject(title);
            mailMessage.setFrom(setFrom);
            mailMessage.setText(content, "utf-8", "html");
            System.out.println("mailSend 메일 보내기 1초 전");
            mailSender.send(mailMessage);
        } catch (MessagingException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String sendSimpleMessage(String to) throws Exception {
        return null;
    }
}


email.properties

mail.smtp.auth=true
mail.smtp.starttls.required=true
mail.smtp.starttls.enable=true
mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
mail.smtp.socketFactory.fallback=false
mail.smtp.port=465
mail.smtp.socketFactory.port=465

# admin 
AdminMail.id = 여러분의이메일
AdminMail.password = 여러분의앱비밀번호

중요!! properties 위치는 resource 아래에 둔다.
AdminMail.id를 적을 때 작은 따옴표나 큰 따옴표는 굳이 안 적어도 된다.


EmailConfig

import java.util.Properties;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;


@Configuration
@PropertySource("classpath:email.properties")
public class EmailConfig {

    @Value("${mail.smtp.port}")
    private int port;
    @Value("${mail.smtp.socketFactory.port}")
    private int socketPort;
    @Value("${mail.smtp.auth}")
    private boolean auth;
    @Value("${mail.smtp.starttls.enable}")
    private boolean starttls;
    @Value("${mail.smtp.starttls.required}")
    private boolean startlls_required;
    @Value("${mail.smtp.socketFactory.fallback}")
    private boolean fallback;
    @Value("${AdminMail.id}")
    private String id;
    @Value("${AdminMail.password}")
    private String password;

    @Bean
    public JavaMailSender javaMailService() {
        JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
        javaMailSender.setHost("smtp.gmail.com");
        javaMailSender.setUsername(id);
        javaMailSender.setPassword(password);
        javaMailSender.setPort(port);
        javaMailSender.setJavaMailProperties(getMailProperties());
        javaMailSender.setDefaultEncoding("UTF-8");
        return javaMailSender;
    }
    private Properties getMailProperties()
    {
        Properties pt = new Properties();
        pt.put("mail.smtp.socketFactory.port", socketPort);
        pt.put("mail.smtp.auth", auth);
        pt.put("mail.smtp.starttls.enable", starttls);
        pt.put("mail.smtp.starttls.required", startlls_required);
        pt.put("mail.smtp.socketFactory.fallback",fallback);
        pt.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
        return pt;
    }
}

EmailConfig에서 properties에 작성한 정보를 가져와 JavaMailSender의 빈을 등록한다.

처음에 @bean을 등록하지 않은 채로 Service에서 JavaMailSender에 @Autowired를 작성해서 오류가 났었다. 의존성 주입(DI)의 기본 중에 기본인데 아차 싶었다.

맨날 @Service, @Controller, @Repository 만 사용하다 보니 @Bean 등록을 잊었던 것 같다.

(참고로 @Service와 같이 @Component를 포함하는 어노테이션은 자동으로 Bean을 등록해주고 @Autowired를 작성하면 필드에 빈을 자동으로 주입을 해준다. )


참고 블로그
https://javaju.tistory.com/100
https://devofroad.tistory.com/43


오류

오류 발생 (라이브러리 관리를 잘 하자..)

java.util.ServiceConfigurationError: javax.xml.ws.spi.Provider: Provider org.jboss.ws.core.jaxws.spi.ProviderImpl not a subtype
모든 게 다 끝났다고 생각했을 때 위와 같은 오류가 났다.
여러 블로그를 참고하면서 구현하다보니 라이브러리를 막무가내로 집어넣어서 생긴 오류였다.
아래 링크에 올라온 글의 작성이 또한 라이브러리의 충돌 때문에 이 오류가 생겼다고 한다.

참고 글
https://stackoverflow.com/questions/24624719/exception-coming-java-util-serviceconfigurationerror

해결

글을 보고 설마.. 하는 마음에 인텔리제이 라이브러리 재설정 등을 쳐봤다.
File - Invalidate Caches를 통해 캐쉬를 삭제할 수 있다고 한다.

해봤더니 드디어 이메일 전달이 됐다.

여러 블로그를 참고하면서 다 조금씩 다른 라이브러리를 추가하는 걸 보고, 그냥 일단 넣고 사용 안 하는 건 지우면 되지.. 이런 생각이 위와 같은 오류를 발생시켰던 것 같다.
앞으로는 라이브러리를 넣을 때 충돌이 일어날 수도 있다는 사실을 염두하며 막무가내로 넣는 행동은 하지 말아야겠다😅

profile
눈도 1mm씩 쌓인다.

0개의 댓글