문홍의 공부장

[Spring/Java] 회원가입 이메일 인증 구현하기 본문

개발/Spring

[Spring/Java] 회원가입 이메일 인증 구현하기

moonong 2020. 3. 31. 23:42
반응형

많은 사이트에서 회원가입 시 이메일 인증을 요구하고 있다. 

이메일로 인증번호를 발송하여 해당 인증번호를 입력하게 하는 경우도 있지만, 회원가입 시 인증 메일을 보내, 해당 메일에서 링크를 클릭하여 인증 및 회원가입을 완료하는 경우도 있다. 

이전 팀 프로젝트에서 전자의 방법으로 이메일 인증을 구현해 보았으니, 이번 개인 프로젝트에서는 후자의 방법으로 구현하고, 그 내용을 정리해보려고 한다. 

큰 틀을 동일하나, 인증키, 인증상태의 DB 저장 여부에 따라 약간의 차이가 있다. 인증상태를 어디에서 관리하느냐(프론트/백) 에 따라 DB 저장 여부가 결정된다고 볼 수 있다. (3-1 방법으로 했을 때에는 인증에 관한 데이터는 DB에 저장하지 않고, 유효성 검사를 통해 걸러냈다.)

1. 메일 인증 관련 라이브러리 다운받기(의존성 주입)

2. 설정파일(Configuration.java 혹은 .xml)에 bean 설정 

3-1. 인증번호를 발송하는 경우 

  • 인증번호(인증키) 발송 후, 해당 인증번호를 쿠키에 저장해 두었다가 제한 시간(ex 03:00) 내에 옳게 입력할 경우 유효성 검사 통과. 그렇지 않을 경우 인증번호 재발송 필요 (DB에 인증키(authKey)를 저장하지 않음)

3-2. 메일 인증 링크를 통하는 경우 

  • '회원가입' 버튼 클릭 시, DB에 기본정보 & 인증키(authKey) 를 저장 (authStatus = default 0)
  • 메일 링크 클릭 시 파라미터로 전달받은 데이터(email, authKey) 가 일치할 경우 authStatus = 1 으로 업데이트 (인증 완료 & 회원가입 최종 성공)
  • 로그인 시 authStatus != 1 일 경우 인증되지 않은 계정이므로 로그인 제한 

4. 완료! 

 

1. 메일 인증 관련 라이브러리 다운받기(의존성 주입)
pom.xml
<!-- 이메일 인증: https://mvnrepository.com/artifact/javax.mail/mail -->
<dependency>
	<groupId>javax.mail</groupId>
	<artifactId>mail</artifactId>
	<version>1.4.7</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-context-support -->
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context-support</artifactId>
	<version>5.2.0.RELEASE</version>
</dependency>

 

2. 설정파일(Configuration.java 혹은 .xml)에 bean 설정 
email-context.xml

이전에 Configuration.java 파일로 설정해보았으니, 이번엔 email-context.xml 파일을 만들어 빈 설정을 해 보았다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- email 인증 관련   -->
    <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
        <property name="host" value="smtp.gmail.com" />
        <property name="port" value="587" />
        <property name="username" value="이메일 주소"/>
        <property name="password" value="비밀번호" />
        <property name="javaMailProperties">
            <props>
                <prop key="mail.transport.protocol">smtp</prop>
                <prop key="mail.smtp.auth">true</prop>
                <prop key="mail.smtp.starttls.enable">true</prop>
                <prop key="mail.debug">true</prop>
            </props>
        </property>
    </bean>
</beans>

 

(참고) 아래는 Configuration.java 파일로 빈 설정하였을 시.

@Configuration
public class MailAuthConfiguration {
	@Bean(name="mailSender")
	public JavaMailSender getJavaMailSender() {
		Properties properties = new Properties(); 
		properties.put("mail.smtp.auth", true);
		properties.put("mail.transport.protocol", "smtp");
		properties.put("mail.smtp.starttls.enable", true);
		properties.put("mail.smtp.starttls.required", true);
		properties.put("mail.debug", true);
		
		JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
		mailSender.setHost("smtp.gmail.com");
		mailSender.setPort(587);
		mailSender.setUsername("이메일 주소");
		mailSender.setPassword("비밀번호");
		mailSender.setDefaultEncoding("utf-8");
		mailSender.setJavaMailProperties(properties);
		
		return mailSender;
		
	}
	
}

 

메일 인증 링크를 통하는 경우 : '회원가입' 버튼 클릭 시,  
3-1. DB에 기본정보 저장
MemberController.java - signUp (회원가입)
@RestController
public class MemberRController {
    @Autowired
    private MemberService memberService;
    @Autowired
    private MailSendService mss;


    @RequestMapping("/member/signUp")
     public void signUp(@ModelAttribute MemberDTO memberDTO){
        // DB에 기본정보 insert
        memberService.signUp(memberDTO);

        //임의의 authKey 생성 & 이메일 발송
        String authKey = mss.sendAuthMail(memberDTO.getEmail());
        memberDTO.setAuthKey(authKey);

        Map<String, String> map = new HashMap<String, String>();
        map.put("email", memberDTO.getEmail());
        map.put("authKey", memberDTO.getAuthKey());
        System.out.println(map);

      //DB에 authKey 업데이트
      memberService.updateAuthKey(map);

  	}
}

 

3-2. 인증키 생성 & 이메일 발송
MailSendService.java
@Service("mss")
public class MailSendService {
    @Autowired
    private JavaMailSenderImpl mailSender;

    //인증키 생성
    private String getKey(int size) {
        this.size = size;
        return getAuthCode();
    }

    //인증코드 난수 발생
    private String getAuthCode() {
        Random random = new Random();
        StringBuffer buffer = new StringBuffer();
        int num = 0;

        while(buffer.length() < size) {
            num = random.nextInt(10);
            buffer.append(num);
        }

        return buffer.toString();
    }

    //인증메일 보내기
    public String sendAuthMail(String email) {
        //6자리 난수 인증번호 생성
        String authKey = getKey(6);

        //인증메일 보내기
        try {
            MailUtils sendMail = new MailUtils(mailSender);
            sendMail.setSubject("회원가입 이메일 인증");
            sendMail.setText(new StringBuffer().append("<h1>[이메일 인증]</h1>")
            .append("<p>아래 링크를 클릭하시면 이메일 인증이 완료됩니다.</p>")
            .append("<a href='http://localhost:9080/member/signUpConfirm?email=")
            .append(email)
            .append("&authKey=")
            .append(authKey)
            .append("' target='_blenk'>이메일 인증 확인</a>")
            .toString());
            sendMail.setFrom("이메일 주소", "관리자");
            sendMail.setTo(email);
            sendMail.send();
        } catch (MessagingException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

          return authKey;
    }
}

 

MailUtils 는 이메일 전송을 위한 메세지 메일 발송, 메세지 설정 등을 위해 만든 클래스이다. 클래스를 따로 만들지 않고, 바로 MimeMessasge 클래스를 사용하여 메일 발송 및 메세지를 구성하여도 무방하다. 

MailUtils.java
public class MailUtils {
    private JavaMailSender mailSender;
    private MimeMessage message;
    private MimeMessageHelper messageHelper;

    public MailUtils(JavaMailSender mailSender) throws MessagingException {
        this.mailSender = mailSender;
        message = this.mailSender.createMimeMessage();
        messageHelper = new MimeMessageHelper(message, true, "UTF-8");
    }

    public void setSubject(String subject) throws MessagingException {
        messageHelper.setSubject(subject);
    }

    public void setText(String htmlContent) throws MessagingException {
        messageHelper.setText(htmlContent, true);
    }

    public void setFrom(String email, String name) throws UnsupportedEncodingException, MessagingException {
        messageHelper.setFrom(email, name);
    }

    public void setTo(String email) throws MessagingException {
        messageHelper.setTo(email);
    }

    public void addInline(String contentId, DataSource dataSource) throws MessagingException {
        messageHelper.addInline(contentId, dataSource);
    }

    public void send() {
        mailSender.send(message);
    }
}

 

(참고) MimeMessasge 클래스를 사용하여 메일 구성할 경우

MimeMessage mail = mailSender.createMimeMessage();
String mailContent = "<h1>[이메일 인증]</h1><br><p>아래 링크를 클릭하시면 이메일 인증이 완료됩니다.</p>"
                    + "<a href='http://localhost:9080/member/signUpConfirm?email=" 
                    + email + "&authKey=" + authKey + "' target='_blenk'>이메일 인증 확인</a>";

try {
    mail.setSubject("회원가입 이메일 인증 ", "utf-8");
    mail.setText(mailContent, "utf-8", "html");
    mail.addRecipient(Message.RecipientType.TO, new InternetAddress(email));
    mailSender.send(mail);
} catch (MessagingException e) {
    e.printStackTrace();
}

 

위 단계까지 성공적으로 진행되었다면, 파라미터로 받은 이메일 주소로 메일이 와 있을 것이다.

아! 그 전에, Gmail의 보안 수준(2단계 인증) 을 낮춰 놓아야 접근이 가능하다. 그렇지 않으면 아래와 같은 인증 에러 메세지가 뜰 것이다. 

AuthenticationFailedException: Username and Password not accepted
 

방법은 간단하다. 아래 링크에 들어가서 [보안 수준이 낮은 앱 허용: 사용] 으로 설정을 변경해 놓으면 된다. (보안 수준을 낮추는 것이기 때문에 당연히! 평상시에는 권장하지 않는다.)

https://myaccount.google.com/lesssecureapps

 

자, 이제 진짜로 이메일이 보내졌을 것이다. [이메일 인증 확인] 링크(/member/signUpConfirm)를 클릭하여 인증을 마무리한다. 

 

3-3. 파라미터로 전달받은 데이터(authKey) 가 일치할 경우 authStatus = 1 으로 업데이트 (인증 완료 & 회원가입 최종 성공)
MemberController.java - signUpConfirm
 @GetMapping("/member/signUpConfirm")
 public ModelAndView signUpConfirm(@RequestParam Map<String, String> map, ModelAndView mav){
    //email, authKey 가 일치할경우 authStatus 업데이트
    memberService.updateAuthStatus(map);
    
    mav.addObject("display", "/view/member/signUp_confirm.jsp");
    mav.setViewName("/view/index");
    return mav;
}

 

4. 로그인 시 authStatus != 1 일 경우(인증되지 않은 계정) 로그인 제한 
SELECT * FROM MEMBER WHERE EMAIL = #{email} AND PWD = #{pwd} AND AUTHSTATUS = 1
반응형