[Spring] 비밀번호 찾기 메일 보내기 (Gmail)
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시간에 한 번만 이용할 수 있도록 함
댓글남기기