ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [이메일 인증] 이메일 인증 구현 & 코드 검증!
    Health-Genie 2023. 11. 18. 14:55
    728x90

    배경

    해당 프로젝트에서 이메일 인증 기능이 필요해졌다!

    그래서 나는 Google SMTP 서버를 이용해서 이메일 인증을 진행하도록 했다.

     

    개발 환경

    • Java 17
    • Spring 3.x
    • Gradle
    • MySQL
    • IntelliJ

     

    흐름

    1. 사용자는 이메일을 입력 후 이메일 인증 버튼 클릭
    2. 클라이언트 서버에게 사용자의 이메일로 인증 번호 전송 요청
    3. 서버는 랜덤 인증 번호 생성. 인증 번호를 auth code table에 저장 후 사용자의 이메일로 인증 번호 전송
    4. 사용자는 인증 번호 확인 후 인증 번호 입력 후 확인 버튼 클릭
    5. 클라이언트는 서버에게 인증 번호 검증 요청
    6. 서버는 전달받은 인증 번호가 auth code table에 저장된 인증 번호와 동일한지 확인 후 동일하면 true 반환

     

    SMTP에서 사용할 구글계정을 등록하고, application.yml에 적어준다. [ 생략 ] 

    spring:
      mail:
        host: smtp.gmail.com
        port: 587
        username: ENC(mN89mzWmJjg24UA4+WAfpRWd3ouJi+ncg0L6b9vQLCM=)
        password: ENC(OWlYfU6Qj8xrpbXmFILpeMG6/uaoOZR4fiZNb9Coov8=)
        properties:
          mail:
            smtp:
              auth: true
              starttls:
                enable: true
                required: true
              connectiontimeout: 5000
              timeout: 5000
              writetimeout: 5000
        auth-code-expiration-millis: 1800000  # 30 * 60 * 1000 == 30분

     

    가물가물해져서 헷갈릴 수 있는 부부은 가령 mysql에서는 빈번히 username을 root로 설정해서 여기서도 root를 작성하는 경우가 있는데, username 은 [아이디]@gmail.com 이런식으로 해줘야 한다.

     

    참고로 ENC는 자바의 jasypt를 사용하여 암호화를 진행했다.

    https://techj9972.tistory.com/174

     

    build.gradle

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

     

    config

    @Configuration
    public class EmailConfig {
    
        @Value("${spring.mail.host}")
        private String host;
    
        @Value("${spring.mail.port}")
        private int port;
    
        @Value("${spring.mail.username}")
        private String username;
    
        @Value("${spring.mail.password}")
        private String password;
    
        @Value("${spring.mail.properties.mail.smtp.auth}")
        private boolean auth;
    
        @Value("${spring.mail.properties.mail.smtp.starttls.enable}")
        private boolean starttlsEnable;
    
        @Value("${spring.mail.properties.mail.smtp.starttls.required}")
        private boolean starttlsRequired;
    
        @Value("${spring.mail.properties.mail.smtp.connectiontimeout}")
        private int connectionTimeout;
    
        @Value("${spring.mail.properties.mail.smtp.timeout}")
        private int timeout;
    
        @Value("${spring.mail.properties.mail.smtp.writetimeout}")
        private int writeTimeout;
    
        @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();
            properties.put("mail.smtp.auth", auth);
            properties.put("mail.smtp.starttls.enable", starttlsEnable);
            properties.put("mail.smtp.starttls.required", starttlsRequired);
            properties.put("mail.smtp.connectiontimeout", connectionTimeout);
            properties.put("mail.smtp.timeout", timeout);
            properties.put("mail.smtp.writetimeout", writeTimeout);
    
            return properties;
        }
    }

     

    - JavaMailSender 인터페이스를 구현하는 클래스로, yml 파일에 설정한 환경 변수들을 사용해서 객체를 만들고, 해당 객체를 통해서 이메일을 보낼 수 있다 ( 따로 이메일 송신자를 체크 안해줘도 된다 )

     

     

    controller

        // 이메일 코드전송,이메일유효성검사
        @PostMapping("/mail/send") 
        public ResponseEntity authMail(@RequestBody String email) throws MessagingException {
    
            userMailService.sendCode(email);
    
            return new ResponseEntity("이메일이 성공적으로 보내쟜습니다", HttpStatus.OK);
        }
    
        //이메일 코드검증
        @GetMapping("/mail/verify") 
        public ResponseEntity validMailCode(@RequestParam("email") String email,
                                            @RequestParam("authCode") String authCode){
            boolean result = userMailService.verify(email, authCode);
            if (result) {
                return new ResponseEntity("authcode is correct",HttpStatus.OK);
            }
            return new ResponseEntity("authcode is wrong ",HttpStatus.BAD_REQUEST);
        }

     

    - sendCode() : 이메일 수신자에서 코드를 보내는 메소드

    - verify() : 수신자에게 보낸 코드와 수신자가 작성한 코드가 맞는지 검증하는 메소드

     

     

    service

    UserMailService

        @Transactional
        public EmailAuthResponseDto sendCode(String toEmail) throws MessagingException {
    
            String title = "Health Genie 이메일 인증 번호";
            String authCode = this.createCode();
    
            JsonObject jsonObject = new Gson().fromJson(toEmail, JsonObject.class);
            String email = jsonObject.get("email").getAsString();
    
            mailService.sendEmail(email, title, authCode);
    
            // 이메일 인증 요청 시 인증 번호 DTO에 저장
            EmailAuthCode code = EmailAuthCode.builder()
                    .code(authCode)
                    .email(email)
                    .build();
    
            EmailAuthCode savedCode = emailAuthRepository.save(code);
    
            return EmailAuthResponseDto.builder().id(savedCode.getId()).build();
        }
    
        private String createCode() {
            int lenth = 8;
            try {
    
                Random random = SecureRandom.getInstanceStrong();
                StringBuilder builder = new StringBuilder();
                for (int i = 0; i < lenth; i++) {
                    builder.append(random.nextInt(10));
                }
    
                return builder.toString();
            } catch (NoSuchAlgorithmException e) {
                log.debug("MemberService.createCode() exception occur");
                throw new CommonException(CommonErrorResult.NO_SUCH_ALGORITHM);
            }
        }
    
        @Transactional
        public boolean verify(String email, String authCode) {
            EmailAuthCode code = getCodeByEmail(email); // email을 이용해 코드 가져오기 (예시 메서드)
    
            // 코드가 없으면 false 반환하거나 예외처리
            if (code == null) {
                return false;
            }
    
            // DB에서 가져온 코드와 파라미터로 받은 코드(authCode)를 비교
            return code.getCode().equals(authCode);
        }
    
        @Transactional(readOnly = true)
        public EmailAuthCode getCodeByEmail(String email) {
            return emailAuthRepository.findByEmail(email).orElseThrow();
        }

     

    - sendCode() : JSON 형태로 이메일을 받아서 Gson() 라이브러리를 통해서 email값을 string으로 얻고, authcode와 email 을 값을 DB에 저장해준다.

    - createCode() : 8자리 랜덤 숫자를 생성 해준다

    - verify() : DB에 있는 authcode와 사용자가 작성하는 code를 비교해서 boolean 값을 반환해준다

    - EmailAuthCode() : 이메일을 통해서 해당 code값을 찾아준다

     

    MailService

        public void sendEmail(String toEmail,
                              String title,
                              String text) throws MessagingException {
            SimpleMailMessage emailForm = createEmailForm(toEmail, title, text);
    
            try {
                emailSender.send(emailForm);
            } catch (RuntimeException e) {
                log.debug("MailService.sendEmail exception occur toEmail: {}, " +
                        "title: {}, text: {}", toEmail, title, text);
                throw new CommonException(CommonErrorResult.UNABLE_TO_SEND_EMAIL);
            }
        }
    
        // 발신할 이메일 데이터 세팅
        private SimpleMailMessage createEmailForm(String toEmail,
                                                  String title,
                                                  String text) {
            SimpleMailMessage message = new SimpleMailMessage();
            message.setTo(toEmail);
            message.setSubject(title);
            message.setText(text);
    
            return message;
        }

     

    - createEmailForm() : 이메일 데이터를 세팅하고, SimpleMailMessage 객체를 생성해서 반환한다

    - sendEmail() : 이메일 양식을 전달해준다

     

    repository

        Optional<EmailAuthCode> findByEmail(String email);

     

    결과

    인증 코드 보내기

    코드 확인

     

    인증 코드 검사하기

    인증 코드 검사하기

Designed by Tistory.