스프링부트 이메일인증 2 - 네이버 메일 설정과 이메일인증 구현

Yu Seong Kim·2024년 9월 11일
0
post-thumbnail

저번 포스팅에서 간단하게 이메일 동작원리에 대해서 알아보았습니다. 저는 프로젝트를 하면서 이메일인증을 구현할 때, 기본적인 동작원리와 용어들을 모르고 개발하는 것과 어느정도 이해하고 개발하는 것은 매우 다르다고 생각했습니다.

이제 본격적으로 이메일 구현을 할 것입니다. 그 이전에 이메일인증을 구현하기 위해서는 메일 설정을 해야합니다. 저는 네이버 메일을 이용하여 메일 설정을 하도록 하겠습니다.

저는 회원가입 전에 이메일 인증을 하고 인증이 완료된 사용자만 회원가입을 이어서 진행할 수 있도록 구현할 것입니다.

이메일 설정이나 환경설정을 제외한 코드 작성은 각자 스타일이 있기 때문에 참고만 부탁드립니다.

메일 API 기획

저는 2가지의 API로 이메일 인증을 구현할 것입니다.
1. 이메일 보내기(랜덤 숫자 발송)
2. 랜덤 숫자 인증 -> 인증완료가 된다면 이어서 회원가입 진행

네이버 메일 설정

  1. 네이버 메일 좌측 하단 환경설정 클릭

    2.환경설정에서 POP3/IMAP 설정 클릭
  2. POP3/IMAP 설정 - 저는 다음과 같이 설정 했습니다.

스프링부트 환경설정

저는 프로젝트를 Maven으로 진행하였습니다.

pom.xml

        <dependency>
            <groupId>javax.mail</groupId>
            <artifactId>javax.mail-api</artifactId>
            <version>1.6.2</version>
        </dependency>

applicaiotn.properties

다음 정보들을 입력합니다.

mail.host=
mail.port=
mail.username=
mail.password=
mail.auth=
mail.starttls.enable=
mail.timeout=

EMailConfig

@Configuration
public class EmailConfig {
    @Value("${mail.host}")
    private String host;

    @Value("${mail.port}")
    private int port;

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

    @Value("${mail.password}")
    private String password;

    @Value("${mail.auth}")
    private boolean auth;

    @Value("${mail.starttls.enable}")
    private boolean starttlsEnable;


    @Value("${mail.timeout}")
    private int timeout;


    @Bean
    public JavaMailSender javaMailSender(){
        JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
        mailSender.setHost(host);
        mailSender.setPort(port);
        mailSender.setUsername(username);
        mailSender.setPassword(password);
        mailSender.setDefaultEncoding("UTF-8");
        mailSender.setJavaMailProperties(getMailProperties());

        return mailSender;
    }

    private Properties getMailProperties(){
        Properties properties = new Properties();
        //"mail.smtp.auth": 이메일 서버가 사용자 인증을 필요로 하는지 여부를 설정 변수 auth의 값에 따라 true 또는 false가 될 수 있음.
        properties.put("mail.smtp.auth", auth);
        properties.put("mail.host",host);
        properties.put("mail.username",username);
        properties.put("mail.password",password);

        //"mail.smtp.starttls.enable": SMTP 서버가 STARTTLS 명령을 사용하여 보안 연결을 시작할 수 있는지 여부를 설정.. 변수 starttlsEnable에 의해 결정.
        properties.put("mail.enable", starttlsEnable);
        //"mail.smtp.timeout": 메일 서버로부터 응답을 기다리는 타임아웃 시간(밀리초 단위)을 설정. 변수 timeout에 지정된 값으로 설정.
        properties.put("mail.timeout", timeout);


        return properties;
    }

}

controller

저는 이메일 인증은 회원가입의 과정이기 때문에 SignController안에서 컨트롤러를 만들었습니다.

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/sign")
public class SignController {

    private final SignService signService;

    @PostMapping("/send-mail")
    public ResponseEntity<Map<String, String>> sendSimpleMessage(@RequestParam String email, HttpServletRequest request) throws Exception {
        return ResponseEntity.status(HttpStatus.OK).body(signService.sendSimpleMessage(email,request));
    }
    @PostMapping("/verified")
    public ResponseEntity<?> verifyEmail(String confirmationCode, HttpServletRequest request){
        Map<String,String> verified = signService.verifyEmail(@RequestParam confirmationCode,request);
        return ResponseEntity.status(HttpStatus.OK).body(verified);
    }
    // 나머지 회원가입/로그인 컨트롤러 생략 

}

SignService/SignServiceImpl

로직은 간단합니다.
1. 이메일로 인증번호 발송
2. 인증번호 검증 -> 검증되면 유저의 Verified가 true로 바뀜.
3. 이어서 회원가입 진행.

-SignService-

public interface SignService {
   Map<String,String> sendSimpleMessage(String to, HttpServletRequest request) throws Exception;
   Map<String,String> verifyEmail(String confirmationCode, HttpServletRequest request);

// 회원가입/로그인 부분은 생략
}

-SignServiceImpl-

@Service
@Slf4j
@RequiredArgsConstructor
public class SignServiceImpl implements SignService {
    private final JavaMailSender javaMailSender;
    private Logger logger = LoggerFactory.getLogger(SignServiceImpl.class);
    private final JwtProvider jwtProvider;
    private final SignDao signDao;
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final ResultStatusService resultStatusService;
    
    // 이메일 보내기
    @Override
    public Map<String, String> sendSimpleMessage(String email, HttpServletRequest request) throws Exception {
        String ePw = createKey();

        request.getSession().setAttribute("email",email);

        MimeMessage message = createMessage(email,ePw);
        try{
            javaMailSender.send(message);
        }catch (MailException e){
            e.printStackTrace();
            throw new IllegalArgumentException();
        }

        Map<String,String>response = new HashMap<>();
        response.put("Confirmation : ", ePw);

        return response;
    }
    
    // 인증번호 검증
     @Override
    public Map<String,String> verifyEmail(String confirmationCode, HttpServletRequest request) {
        String email = (String) request.getSession().getAttribute("email");
        User user = new User();
        Map<String,String> response = new HashMap<>();
        if(email != null){
            user.setEmail(email);
            user.setVerified(true);
            request.getSession().setAttribute("user",user);
            response.put("Verified : ", "true");

            return response;
        }
        response.put("Verified : ", "false");
        return response;
    }
    
    // 이메일 메시지 폼 제작
     private MimeMessage createMessage(String to, String ePw) throws Exception {
        MimeMessage message = javaMailSender.createMimeMessage();

        message.addRecipients(Message.RecipientType.TO, to);
        message.setSubject("이메일 인증");
        String msgg = "";
        msgg += "<div style='margin:20px;'>";
        msgg += "<h1> OVER-DOSE </h1>";
        msgg += "<br>";
        msgg += "<p>인증번호 입니다.</p>";
        msgg += "<br>";
        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 += ePw + "</strong><div><br/> ";
        msgg += "</div>";
        message.setText(msgg, "utf-8", "html");//내용
        message.setFrom(new InternetAddress("nankys0510@naver.com", "OverDose"));//보내는 사람

        return message;
    }
    //인증번호 제작
    public static String createKey(){
        int number = (int)(Math.random()*90000)+100000;
        return String.valueOf(number);
    }

    
// 회원가입/로그인 부분은 생략

}

중심이 되는 메서드 두 개만 알아보겠습니다.
저는 다음과 같은 로직으로 개발하였습니다.

sendSimpleMessage(String email, HttpServletRequest request)

  1. createKey()로 인증번호 생성.
  2. 세션에 이메일 저장.
  3. 이메일 메시지 생성 후 발송.
  4. 성공 시 인증번호를 반환하는 Map 리턴.
  5. 실패 시 예외 처리.
    세션 활용: 인증번호 확인을 위해 이메일을 세션에 저장.

verifyEmail(String confirmationCode, HttpServletRequest request)

  1. 세션에서 이메일 정보 확인.
  2. 이메일이 있으면 사용자 인증 처리.
  3. 인증 성공 시 사용자 정보 저장, 실패 시 오류 응답.
    세션 활용: 세션에 저장된 이메일로 인증을 진행.

결과 확인

  1. 이메일 입력

  2. 이메일확인 - 인증번호 전송 및 확인

3.인증번호 검증

이렇게 true가 나온것을 볼 수 있습니다.
저는 프론트에 넘겨주기 위하여 Map<String,String>을 사용하였습니다.

마무리

이렇게 회원가입을 위한 이메일 인증을 구현해보았습니다. 개발하는 사람마다 로직이 다를수도 있고, 코딩 스타일도 다를 수 있지만 전체적인 흐름은 비슷하다고 생각하기 때문에 다양한 방법으로도 구현해 볼 예정입니다.

profile
인생을 코딩하는 남자.

0개의 댓글