ToBe끝판왕

[ JAVA ] 임시 비밀번호 발급 + 메일 전송 본문

■ 공부 기록/기능 구현

[ JAVA ] 임시 비밀번호 발급 + 메일 전송

업그레이드중 2024. 12. 31. 22:04
반응형

 

 


 

 

■  임시 비밀번호 발급 로직 생각해 보기

1) 사용자 ( 클라이언트 ) 가 아이디와 이메일주소를 입력 후 임시 비밀번호 발급 요청

2) 서버 ( Controller단 ) 에서 아이디와 이메일주소를 받아 Service 클래스로 전달

3) Service 클래스 ( 비즈니스 로직 )
-  사용자 DB 조회 ( 아이디와 이메일 )
-  임시 비밀번호 생성
-  생성된 비밀번호 암호화 하여 DB 저장
-  이메일로 임시 비밀번호 발송

4) 사용자 ( 클라이어트 ) 해당 임시 비밀번호로 로그인

5) 사용자 ( 클라이어트 ) 로그인 이후, 마이페이지에서 추후 사용할 비밀번호로 변경

 

 

 

JavaMailSender

 

 

■  JavaMailSender 

 

-  SpringFramework 에서 이메일을 간편하게 발송할 수 있도록 도와주는 인터페이스

-  JavaMailSender 설정이 필요하다. ( 필자는 Config 클래스 + application.properties 설정을 하였다. )

 

-  의존성 주입 ( build.gradle )

// build.gradle 파일 안 dependencies {} 블록안에 아래 내용 삽입

// JavaMailSender 인터페이스 의존성 주입
implementation 'org.springframework.boot:spring-boot-starter-mail'

 

 

-  MailConfig 클래스 생성

•  SMTP 서버 사용을 위한 설정 ( Host , Port , username : 이메일 주소 , password : 앱 비밀번호 ) 세팅

•  일반적으로 Gmail 을 사용할 경우, host = smtp.gmail.com , port = 587 을 사용

package com.wsd.invest.user.config;

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

import java.util.Properties;

@Configuration
public class MailConfig {

    @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;
    
    @Bean
    public JavaMailSender javaMailSender() {

		// JavaMailSenderImpl 클래스를 이용하여 이메일 전송을 위한 객체 생성
        JavaMailSenderImpl mailSender = new JavaMailSenderImpl();

        // SMTP Server 설정
        // Google SMTP Server 주소
        mailSender.setHost(host);
        // Google SMTP Port 넘버
        mailSender.setPort(port);

        // 인증 정보 설정
        // 실제 사용할 gmail 주소
        mailSender.setUsername(username);
        // gmail 비밀번호
        mailSender.setPassword(password);

        // 추가 설정
        Properties props = mailSender.getJavaMailProperties();
        // SMTP 전송 프로토콜 사용 명시
        props.put("mail.transport.protocol", "smtp");
        // SMTP 인증 사용 명시
        props.put("mail.smtp.auth", "true");
        // TLS 시작 연결 사용 설정 (보안)
        props.put("mail.smtp.starttls.enable", "true");
        // 디버그 로그 활성화
        props.put("mail.debug", "true");

        return mailSender;
    }
}

 

 

 

-  application.properties 설정

# SMTP 서버 GOOGLE 설정
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=이메일주소
spring.mail.password=앱비밀번호
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.debug=true

 

 

※ Gmail 앱 비밀번호는 어떻게 생성 하나요 ?

•  아래 링크에 들어가서 본인 구글로 로그인을 하면 16자리의 앱 비밀번호를 수령 가능하다.

https://myaccount.google.com/apppasswords

 

로그인 - Google 계정

이메일 또는 휴대전화

accounts.google.com

 

 

 


 

 

 

-  코드 작성

 

•  JSP 양식 페이지에서 회원이 본인의 Id 와 Email 주소를 입력 후, 전송 버튼

•  입력받은 Id 와 Email 주소를 통해 가입이 된 회원인지 조회

•  로그인 / 회원가입 기능이 SpringSecurity를 사용중이므로 임시 비밀번호를 생성하여 암호화

•  기존 회원의 비밀번호를 암호화된 비밀번호로 업데이트

•  입력받은 이메일 주소로 회원에게 메일 발송 ( 암호화 되기 이전의 임시 비밀번호 전송 )

 

 

 

-  DTO 클래스 생성

•  JSP 입력 폼에서 유효성 검사도 진행하기 위해 따로 Dto 클래스 파일 생성

•  앞단의 js 로 유효성 검사 + 뒷단의 @Valid 를 통한 유효성 검사를 진행한다.

package com.wsd.invest.user.dto;

import jakarta.validation.constraints.NotBlank;
import lombok.Data;

@Data
public class FindPwDto {

    @NotBlank(message = "아이디를 입력해주세요.")
    private String memberId;

    @NotBlank(message = "올바른 이메일 형식이 아닙니다.")
    private String memberMail;

}

 

 

 

 

-  Controller 클래스 내 메서드 생성

•  @Valid 어노테이션 + BindingResult 를 통한 유효성 검증 ( JSP input 값 유효성 검증 )

•  임시 비밀번호 이메일 발송

•  이메일 발송 성공 시, 성공 메시지 / 이메일 발송 실패 시, 비밀번호 찾기 페이지로 redirect

 

※ 검증 ( @Valid 어노테이션 + BindingResult )

•  @Valid 어노테이션  :  검증할 객체의 필드에 제약조건 어노테이션들의 검증을 실시한다.

•  @Valid 어노테이션은 하나의 객체 전체에 대해 정의된 제약조건들을 한꺼번에 검사

    ( 한 필드는 검증을 만족하고 한 필드는 검증을 만족하지 못하는 경우 에러 발생 )

•  BindingResult  :  @Valid 어노테이션과 함께 사용하여 .hasErrors() 메서드를 통해 검증에 실패했는지 확인 가능

•  BindingResult  :  @Valid 어노테이션이 붙은 객체 바로 뒤에 위치해야 한다.

 

※ RedirectAttributes

•  RedircetAttributes 는 Spring MVC 에서 Redirect 시 플래시 메시지를 사용할때 자주 사용된다.

•  addFalshAttribute 메서드  :  Redirect 되는 페이지에서 한번만 사용할 수 있는 FalshAttribute를 추가

    @PostMapping("/findPw")
    public String findPassword(@Valid @ModelAttribute("pwDto")FindPwDto findPwDto, BindingResult bindingResult
            , RedirectAttributes redirectAttributes ) throws Exception{

        log.info("========== 비밀번호 찾기 기능(임시 이메일 발급) 진행 ==========");

        // 유효성 검증
        if(bindingResult.hasErrors()) {
            log.info("errors={}", bindingResult);
            return "member/loginForm";
        }

        // 임시 비밀번호 메일 발송
        String resultMsg = userDetailService.findPassword(findPwDto);

        if("SUCCESS".equals(resultMsg)) {
            redirectAttributes.addFlashAttribute("message", "임시 비밀번호가 이메일로 발송되었습니다.");
            return "redirect:/user/login";
        } else {
            redirectAttributes.addFlashAttribute("message", resultMsg);
            return "redirect:/user/findPw";
        }

 

 

 

 

-  Service 클래스 생성

•  input 으로 입력받은 회원ID , 회원Email 주소로 DB 에서 가입된 회원인지 조회 및 Null 처리

•  임시 비밀번호 생성 후, 암호화를 거쳐 DB 회원테이블 내 해당 회원ID 의 비밀번호 컬럼값을 Update

•  입력받은 회원 Email 주소로 이메일 발송

 

※ MessagingException

•  JavaMailAPI 에서 이메일을 보내는 과정 중 발생할 수 있는 모든 예외를 나타내는 부모 클래스

•  주요 원인  :  SMTP 서버 연결 실패 / 인증 실패  /  메일 형식 오류 / 네트워크 오류 등

 

※ MimeMessage

•  MIME( Multipurpose Internet Mail Extensions ) 형식의 이메일 메시지를 나타내는 인터페이스

•  텍스트 , HTML , 첨부 파일 등 다양한 형식의 내용 포함 가능

•  헤더정보 ( From , To , Subject 등 ) 설정 가능

•  javax.mal 패키지에서 제공

•  MimeMessageHelper 는 MimeMessage 를 생성하고 설정하는데 사용되는 헬퍼 클래스

 

public String findPassword(FindPwDto findPwDto) throws Exception{

        try {

            // 사용자 검색
            FindPwDto pwUser = userDetailMapper.findMailUser(findPwDto);
            if( pwUser == null ) {
                return "등록되지 않은 사용자입니다.";
            }

            // 임시 비밀번호 생성 및 DB 저장
            String tempPw = PasswordUtil.makeTempPassword();
            String encodedPw = passwordEncoder.encode(tempPw);

            userDetailMapper.updatePassword(findPwDto.getMemberId(), encodedPw);

            // 이메일 발송
            sendEmail(findPwDto.getMemberMail(), tempPw);

            return "SUCCESS";

        } catch (MessagingException me) {

            log.error("임시 비밀번호 발급 중 오류 발생: {}", me.getMessage(), me);
            return "이메일 발송에 실패하였습니다, 잠시 후 다시 시도해주세요.";

        } catch( DataAccessException dae ) {
            log.error("데이터베이스 작업중 오류 발생: {}", dae.getMessage(), dae);
            return "시스템 오류가 발생하였습니다. 잠시 후 다시 시도해주시길 바랍니다.";

        } catch(Exception e ) {
            log.error("시스템 오류 발생: {}", e.getMessage(), e);
            return "시스템 오류가 발생하였습니다. 잠시 후 다시 시도해주시길 바랍니다.";
        }

    }

    private void sendEmail(String memberMail, String tempPw) throws MessagingException  {

            String emailContent = getEmailContent(tempPw);

            MimeMessage message = javaMailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");

            helper.setTo(memberMail);
            helper.setSubject("StockLand 임시 비밀번호 발급 안내");
            helper.setText(emailContent, true);

            javaMailSender.send(message);
            log.info("이메일 발송이 성공적으로 전송되었습니다. 이메일 주소:{}", memberMail);
    }

    private String getEmailContent(String tempPw) {
        return "<p>안녕하세요,</p>" +
                "<p>요청하신 임시 비밀번호는 아래와 같습니다.</p>" +
                "<p><strong>" + tempPw + "</strong></p>" +
                "<p>로그인 후 마이페이지에서 비밀번호를 변경해 주세요.</p>";
    }

 

 

 

※ 필자의 경우 Login 양식 JSP 에 비밀번호 입력칸의 유효성검사가 8~15자의 영어 대소문자 + 숫자 + 특수문자가

    되어있어, 임시비밀번호를 해당 유효성 검사 규칙에 맞게 생성 하기 위해 PasswordUtil 클래스를 생성하였다.

package com.wsd.invest.util;

import java.security.SecureRandom;

public class PasswordUtil {

    public static String makeTempPassword() {

        // 영어 대문자 상수 선언
        final String UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        // 영어 소문자 상수 선언
        final String LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
        // 숫자 상수 선언
        final String NUMBERS = "0123456789";
        // 특수문자 상수 선언
        final String SPECIAL_CHAR = "!@#$%^&*()-_+=<>?";
        // 전체 문자열 상수 선언
        final String ALL_CHARS = UPPERCASE + LOWERCASE + NUMBERS + SPECIAL_CHAR;

        // 최소 길이 상수 선언
        final int MIN_LENGTH = 8;
        // 최대 길이 상수 선언
        final int MAX_LENGTH = 15;

        // 난수 생성기 객체
        SecureRandom random = new SecureRandom();
        // 문자열 생성 객체
        StringBuilder sb = new StringBuilder();

        // 영문 대/소문자 , 숫자, 특수문자 1자 랜덤 추가
        sb.append(getRandomChar(UPPERCASE, random));
        sb.append(getRandomChar(LOWERCASE, random));
        sb.append(getRandomChar(NUMBERS, random));
        sb.append(getRandomChar(SPECIAL_CHAR, random));

        // 8~ 15 사이의 랜덤 길이 생성
        int passwordLength = random.nextInt((MAX_LENGTH - MIN_LENGTH) + 1 ) + MIN_LENGTH;

        // 나머지 글자는 랜덤하게 채움
        // 총 길이 8~15자 중 적당한 길이로 생성
        for (int i = 4; i < passwordLength; i++) {
            sb.append(getRandomChar(ALL_CHARS, random));
        }

        // 비밀번호를 랜덤하게 섞음
        return shuffleString(sb.toString(), random);
    }

    // 랜던 문자 메서드
    private static String getRandomChar(String characters, SecureRandom random) {
        // 문자열에서 랜덤한 1개의 문자를 가져온다.
        return String.valueOf(characters.charAt(random.nextInt(characters.length())));
    }

    // 문자열 섞는 메서드
    private static String shuffleString(String input, SecureRandom random) {
        // 문자열을 char[] 배열로 변환
        char[] characters = input.toCharArray();
        for (int i = characters.length - 1; i > 0; i--) {
            int j = random.nextInt(i + 1);
            char temp = characters[i];
            characters[i] = characters[j];
            characters[j] = temp;
        }
        // char 배열을 다시 문자열로 변환
        return new String(characters);
    }

}

 

 

 

-  메일 발송

•  Google SMTP 서버를 이용하여 Google 이메일 계정 -> 회원 Naver 계정으로 임시비밀번호 발급 진행

•  아래의 그림과 같이 정상적으로 임시비밀번호가 발급되어 보내진것을 알 수 있다.

 

 

 

 

▶  다음에는 Spring 메일 발송을 사용하여 사용자 메일 인증 기능 또한 구현해봐야 겠다.

 

 

 

 

 

반응형
Comments