Spring Security/일반로그인

[Spring Security] 이메일 인증 회원가입 및 로그인 구현(1)

limwngur 2023. 8. 18. 18:27
728x90
로그인: spring security의 form login을 사용하였다.
회원가입: 단순한 회원가입 api구현
이메일 인증:
네이버SMTP을 통해 인증 코드를 메일 발송하고, Redis를 통해 인증 코드 검증을 하였다.

즉, 이메일 인증을 통한 서버 자체(소셜X) 회원가입 및 로그인에 필요한 Api는
(1) 로그인 api (Controller가 아닌, 스프링 시큐리티의 formLogin을 사용함)
(2) 회원가입 api
(3) 이메일 인증 코드 발송 api
(4) 이메일 인증 코드 검증 api
총 4가지를 구현하였다.

[Member.class]

package BE.MyRoute.member.entity;

import BE.MyRoute.member.entity.type.Role;
import lombok.*;

import javax.persistence.*;

@Entity
@Table(name = "member")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column
    private Long memberId;

    @Column
    private String email;

    @Column
    private String password;

    @Column
    private String nickname;

    @Column(name = "profile_image")
    private String profileImage;

    @Column
    @Enumerated(EnumType.STRING)
    private Role role;

    @Builder
    public Member(String email, String password, String nickname, String profileImage, Role role) {
        this.email = email;
        this.password = password;
        this.nickname = nickname;
        this.profileImage = profileImage;
        this.role = role;
    }
}

※ 본 내용에 앞서, Member(User) 엔티티를 위와 같이 설계하였음을 알립니다.


"시작하기에 앞서"

이번 소셜 jwt 로그인에 이어 또 다른 프로젝트에서 일반 회원가입에서 회원의 인증을 위해 이메일 인증 기능을 넣게 되었다.

 

이메일 인증 코드는 google SMTP서버, Naver SMTP서버 등 다양한 서버를 사용해서 인증코드 메일을 전송할 수 있다.

이번에는 Naver STMP서버를 통한 메일 인증을 구현하였다.


[1. SMTP용 계정 설정]

spring에서 mail 라이브러리를 통해 메일 인증 코드를 전송할 때 사용할 Naver SMTP서버를 활성화 시켜주어야한다.
네이버 메일 서버를 외부에서 사용하기 위해서는 pop3/SMTP 설정을 필수로 활성화 시켜주어야한다.

네이버 메일을 들어가보면 왼쪽 하단에 "환경설정"을 눌러준다.

 

환경 설정 내부 카테고리에서 "POP3/IMAP 설정" ---> "POP3/SMTP 설정"에 들어온다.

 

위와 같이 설정해준다.

 

그리고!! 추후 spring에서 해당 SMTP서버와 연결을 하기 위해서 아래로 스크롤하면 보이는 

이 정보들을 application.properties나 application.yml에서 서버와 연결시켜 줄 예정임을 기억하자.


[2. Spring에서 의존성 및 properties파일 설정]

spring와 Naver SMTP서버와 연결을 .properties 에서 설정하고
연결한 서버로 메일을 만들어 보낼 수 있는 기능을 사용하기 위해 의존성을 추가해주어야한다.

[2-1 build.gradle]

spring에서 메일 서버와 연결해서 메일을 보내는 작업을 하는데 필요한 lib

[2-2 application.properties]

※ .yml 형식을 사용하고 있다면, https://mageddo.com/tools/yaml-converter 에서 쉽게 변환할 수 있다.

username에는 네이버 아이디를 (example123@naver.com)     password에는 네이버 비밀번호를 넣어준다.

위 내용은 처음 언급했던 Naver SMTP 계정 설정에서 나왔던 값들로 설정하면 된다.

 

[3. MailService 구현 tymeleaf 이용]

MemberService Interface를 먼저 살펴 보자.

createCode : Random 함수를 통해 난수로 된 이메일 인증 코드를 만드는 메소드

createEmailForm : 전송할 Email 형식 세팅(가령, 받는 사람/ 보내는 사람/ 메일 내부 내용 등)

sendEmail : createCode를 통해 인증코드를 만들고 createEmailForm을 통해 최종 메일 내용을 세팅하고 나서 toEmail 주소로 만든 이메일을 전송하는 메소드

verifyEmailCode : Redis를 사용. key-value를 email_addr - auth_code로 하여 이메일을 전송할 때, redis에 저장했던 인증코드와 요청값에 넘어온 인증번호를 비교하여 검증하는 메소드

 

[3-1 EmailServiceImpl (EmailService구현체)]

※이전에 추가적인 의존성 추가가 필요하다.

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

//SpringTemplateEngine 객체 사용을 위한 의존성 추가
implementation 'org.thymeleaf:thymeleaf-spring5'

//redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

 

import BE.MyRoute.util.RedisUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring5.SpringTemplateEngine;

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

@Service
@RequiredArgsConstructor
@Slf4j
public class EmailServiceImpl implements EmailService{

    //의존성 주입을 통해서 필요한 객체를 가져온다.
    private final JavaMailSender emailSender;
    // 타임리프를사용하기 위한 객체를 의존성 주입으로 가져온다
    private final SpringTemplateEngine templateEngine;
    private String authNum; //랜덤 인증 코드
    private final RedisUtil redisUtil;

    @Value("${spring.mail.username}")
    private String fromEmail;

    //랜덤 인증 코드 생성
    public void createCode() {
        Random random = new Random();
        StringBuffer key = new StringBuffer();

        for(int i=0;i<8;i++) {
            int index = random.nextInt(3);

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

    //메일 양식 작성
    public MimeMessage createEmailForm(String email) throws MessagingException, UnsupportedEncodingException {

        createCode(); //인증 코드 생성
        String setFrom = fromEmail; //email-config에 설정한 자신의 이메일 주소(보내는 사람)
        String toEmail = email; //받는 사람
        String title = "MyRoute 회원가입 인증 번호"; //제목

        MimeMessage message = emailSender.createMimeMessage();
        message.addRecipients(MimeMessage.RecipientType.TO, email); //보낼 이메일 설정
        message.setSubject(title); //제목 설정
        message.setFrom(setFrom); //보내는 이메일
        message.setText(setContext(authNum), "utf-8", "html");

        // 난수와 수신 이메일은 Redis안에 저장한다.(30분: 테스트 때문에 시간 길게 잡음)
        redisUtil.setDataExpire(email, authNum, 60*30L);

        return message;
    }

    //실제 메일 전송
    public String sendEmail(String toEmail) throws MessagingException, UnsupportedEncodingException {

        if(redisUtil.existData(toEmail)){
            redisUtil.deleteData(toEmail);
        }

        //메일전송에 필요한 정보 설정
        MimeMessage emailForm = createEmailForm(toEmail);
        //실제 메일 전송
        emailSender.send(emailForm);

        return authNum; //인증 코드 반환 TODO: redis 잘 저장되면 반환될 필요 X
    }

    @Override
    public boolean verifyEmailCode(String email, String code) {
        String coedFoundByEmail = redisUtil.getData(email);
        if (coedFoundByEmail == null) {
            return false;
        }
        return coedFoundByEmail.equals(code);
    }

    //타임리프를 이용한 context 설정
    public String setContext(String code) {
        /*Thymeleaf기반의 html파일에 값을 넣고 연셜하는 메소드*/
        Context context = new Context();
        context.setVariable("code", code);
        return templateEngine.process("mail", context); //mail.html
    }
}

[Postman Test]

#이메일 인증 코드 전송

인증코드 전송 성공!! (직접 타임리프로 SSR로 만들어 놓은 모습으로 반겨주는 메일이 반가웠다.)

 

#인증 코드를 전송하면서 해당 코드를 Redis에 email을 키값으로하여 인스턴스가 생성되었을 것이다.(유효시간은 30분으로 설정해두었다. 개발 중 매끄러운 테스팅을 위해서!) 검증을 해보자.

(1)먼저 잘못된 인증코드 입력을 해보자.

Status가 404. NOT FOUND이다. 당연하다. Redis 내부에는 해당 Key값에 대응되는 81022XL7이 아닌 q8kxa8mO가 담겨있을테니... 다시 정상적인 코드를 입력해보자.

정상적으로 인증이 성공됐다!

 

이제 프론트는 이 status 200을 보고 정상적으로 회원가입 api를 수행해주면 되는 것이다.

 


실제로 구현 순서는 Security로 form login먼저 구현하고, 회원가입을 구현하고 난 뒤 postman테스팅을 하였습니다. 잘 돌아가는 것을 확인 한 뒤에서야 이메일 관련 api 작업을 추가해주었어요.

그래서인지, 포스팅 순서가 잘못된 것 같지만...

다음 포스팅은 로그인 내용을 정리해두겠습니다. 쉬워요. 세션방식이라
728x90