3 분 소요

build.gradle

스프링에서 메일 기능을 사용하려면 의존성을 추가해줘야 한다.

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


application.yml

Gmail 계정 설정도 같이 해주는데 비밀번호는 앱 비밀번호로 설정해주자!
https://myaccount.google.com/u/0/security에서 2단계 인증 후 앱 비밀번호를 발급받아 password에 설정해주자.

spring:
  mail:
    host: smtp.gmail.com
    port: 587
    username: {구글 ID}@gmail.com
    password: {앱 비밀번호}
    properties:
      mail.smtp.auth: true
      mail.smtp.starttls.enable: true


MailController

호출할 API를 컨트롤러에 정의해주자. 이메일이 존재하는지 검증하고, 임시 비밀번호 발급하는 내용이다.

@RestController
@RequiredArgsConstructor
public class MailController {

  private final UserService userService;
  private final MailService mailService;

  @PostMapping("/check/email")
  public ResponseEntity<?> checkEmail(@RequestParam("email") String email) {
      if(!userService.emailExist(email)) {
          return new ResponseEntity<>("일치하는 메일이 없습니다.", HttpStatus.BAD_REQUEST);
      }
      return new ResponseEntity<>("이메일을 사용하는 유저가 존재합니다.", HttpStatus.OK);
  }

  @PostMapping("/send/password")
  public ResponseEntity<?> sendPassword(@RequestParam("email") String email) {
      if(!userService.updatePasswordToken(email)) {
          return new ResponseEntity<>("비밀번호 찾기는 1시간에 한 번 가능합니다.", HttpStatus.BAD_REQUEST);
      }

      String tmpPassword = userService.getTmpPassword();
      userService.updatePassword(tmpPassword, email);
      MailDto mail = mailService.createMail(tmpPassword, email);

      mailService.sendMail(mail);

      return new ResponseEntity<>("비밀번호 발급이 완료되었습니다.", HttpStatus.OK);
  }
}
  • checkEmail() : 이메일이 DB에 있는지 검증
    • 쿼리 파라미터로 email 정보 넘김
    • OK : DB에 해당 이메일 사용 유저 존재
    • BAD_REQUEST : 일치하는 이메일 없음
  • sendPassword() : 임시 비밀번호 검증
    • 쿼리 파라미터로 email 정보 넘김
    • OK : 임시 비밀번호 발급 완료 및 메일 전송
    • BAD_REQUEST : 발급 실패(한 시간 내 2번 이상 발급 시도)

MailService

메일에 담을 내용을 설정해주고 전송하는 로직이다.

@Service
@RequiredArgsConstructor
public class MailService {

  private final JavaMailSender mailSender;
  private static final String title = "임시 비밀번호 안내 이메일입니다.";
  private static final String message = "안녕하세요. 임시 비밀번호 안내 메일입니다. "
          + "\n" + "회원님의 임시 비밀번호는 아래와 같습니다. 로그인 후 반드시 비밀번호를 변경해주세요." + "\n";

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

  public MailDto createMail(String tmpPassword, String to) {
      MailDto mailDto = new MailDto(from, to, title, message + tmpPassword);
      return mailDto;
  }

  public void sendMail(MailDto mailDto) {
      SimpleMailMessage mailMessage = new SimpleMailMessage();
      mailMessage.setTo(mailDto.getTo());
      mailMessage.setSubject(mailDto.getTitle());
      mailMessage.setText(mailDto.getMessage());
      mailMessage.setFrom(mailDto.getFrom());
      mailMessage.setReplyTo(mailDto.getFrom());

      mailSender.send(mailMessage);
  }
}
  • from : 발신자 정보 (application.yml에서 설정)
  • createMail() : 메일 생성
  • sendMail() : 메일 전송

MailDto

메일 폼 정보도 정의해주자.

@Data
@AllArgsConstructor
@NoArgsConstructor
public class MailDto {
    private String from;
    private String to;
    private String title;
    private String message;
}

UserService

임시 비밀번호 메일 전송 시 발급하는 임시 비밀번호는 해당 사용자의 비밀번호로 설정해줘야 한다.

@Service
@RequiredArgsConstructor
public class UserService {

  private final UserRepository userRepository;
  private final BCryptPasswordEncoder passwordEncoder;

  public boolean emailExist(String email) {
      return userRepository.existsByEmail(email);
  }

  public String getTmpPassword() {
      char[] charSet = new char[]{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
              'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
              'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};

      String newPassword = "";

      for (int i = 0; i < 10; i++) {
          int idx = (int) (charSet.length * Math.random());
          newPassword += charSet[idx];
      }

      return newPassword;
  }

  @Transactional
  public void updatePassword(String tmpPassword, String email) {
      String encryptedPassword = passwordEncoder.encode(tmpPassword);
      User user = userRepository.findByEmail(email).orElseThrow(() ->
              new IllegalArgumentException("해당 사용자가 존재하지 않습니다."));

      user.setPassword(encryptedPassword);
  }

  @Transactional
  public boolean updatePasswordToken(String email) {
      User user = findByEmail(email);
      return user.updatePasswordToken();
  }
}
  • emailExist() : 이메일 있는지 검증 (스프링 데이터 JPA 활용)
  • getTmpPassword() : 10자리 문자열 랜덤 생성
  • updatePassword() : 10자리 문자열 암호화 후 해당 사용자 비밀번호로 등록
  • updatePasswordToken() : 1시간에 한 번만 비밀번호 찾기 기능을 이용할 수 있도록 발급 시간 갱신

User

사용자 엔티티에서 비밀번호 찾기 기능과 관련된 부분이다.

@Entity
@Getter
public class User {

  ...
  private String password;
  private LocalDateTime passwordToken;

  public boolean updatePasswordToken() {
      LocalDateTime now = LocalDateTime.now();

      if(passwordToken == null) {
          passwordToken = now;
          return true;
      }

      if(Duration.between(passwordToken, now).getSeconds() < 3600) {
          return false;
      }

      passwordToken = now;
      return true;
  }
  • passwordToken : 마지막으로 비밀번호 찾기 기능을 이용한 시각
  • updatePasswordToken() : passwordToken을 활용해 비밀번호 찾기 기능을 사용할 수 있는지 확인
    • 1시간에 한 번만 이용할 수 있도록 함

References

카테고리:

업데이트:

댓글남기기