- 사용자가 비밀번호를 기억하지 못할 경우 이메일을 통해 임시 비밀번호를 보내주고 기존 비밀번호를 해당 임시 비밀번호로 변경하여 로그인 할 수 있도록 구현해보았습니다.
- 즉 임시 비밀번호가 발송되게 되면 기존 비밀번호로는 로그인 할 수 없습니다.
- 참고로 프로젝트에서 메일 발송을 하기 위해선 해당 포스팅에 작성된 내용 말고도 추가적으로 진행해야되는 과정이 있지만 핵심내용은 아니라고 생각하여 해당 부분은 생략하였습니다.
- 스프링부트 메일 전송에 대한 자세한 설명은 아래 링크에서 확인하실 수 있습니다.
📝 pom.xml
- 우선 스프링부트 프로젝트에서 메일을 발송하기 위해 라이브러리를 추가해주었습니다.
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-mail -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
<version>2.7.5</version>
</dependency>
📝 application.yml
- 다음으로 application.yml 파일에 아래와 같은 설정을 해주었습니다.
spring:
mail:
host: smtp.gmail.com
port: 587
username: [발송 이메일]
password: [앱 비밀번호]
properties:
mail:
smtp:
auth: true
starttls:
enable: true
📝 user.js
- 메일 발송은 ajax 요청을 통해 수행하였습니다.
find: function() {
LoadingWithMask();
let data = {
username: $("#username").val(),
email: $("#email").val()
};
$.ajax({
type: "POST",
url: "/auth/find",
data: JSON.stringify(data),
contentType: "application/json; charset=utf-8"
}).done(function(resp) {
if (resp.status == 400) {
if (resp.data.hasOwnProperty('valid_email')) {
$('#valid_email').text(resp.data.valid_email);
$('#email').focus();
} else {
$('#valid_email').text('');
}
if (resp.data.hasOwnProperty('valid_username')) {
$('#valid_username').text(resp.data.valid_username);
$('#username').focus();
} else {
$('#valid_username').text('');
}
closeLoadingWithMask();
} else {
alert("임시 비밀번호가 발송되었습니다.");
location.href = "/auth/loginForm";
}
}).fail(function(error) {
console.log(error);
});
}
📝 UserApiController
- 위의 ajax 요청을 받는 컨트롤러는 아래와 같이 구현하였습니다.
package com.cos.blog.controller.api;
...
@RestController
@RequiredArgsConstructor
public class UserApiController {
private final UserService userService;
@PostMapping("/auth/find")
public ResponseDto<?> find(@RequestBody SendTmpPwdDto dto) {
if(!userRepository.existsByUsername(dto.getUsername()) || !Pattern.matches("^[a-zA-Z0-9+-\\_.]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$", dto.getEmail())) {
Map<String, String> validResult = new HashMap<>();
if(!userRepository.existsByUsername(dto.getUsername())) {
validResult.put("valid_username", "존재하지 않는 사용자 이름입니다.");
}
if(!Pattern.matches("^[a-zA-Z0-9+-\\_.]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$", dto.getEmail())) {
validResult.put("valid_email", "올바르지 않은 이메일 형식입니다.");
}
return new ResponseDto<>(HttpStatus.BAD_REQUEST.value(), validResult);
}
userService.sendTmpPwd(dto);
return new ResponseDto<Integer>(HttpStatus.OK.value(), 1);
}
}
📝 SendTmpPwdDto
package com.cos.blog.dto;
...
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class SendTmpPwdDto {
private String username;
private String email;
}
📝 UserService
- 비즈니스로직을 수행할 UserSerivce는 아래와 같이 구현하였습니다.
package com.cos.blog.service;
...
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final JavaMailSender javaMailSender;
@Value("${spring.mail.username}")
private String sendFrom;
@Transactional
public void sendTmpPwd(SendTmpPwdDto dto) {
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 tmpPwd = "";
// 문자 배열 길이의 값을 랜덤으로 10개를 뽑아 구문을 작성함
int idx = 0;
for (int i = 0; i < 10; i++) {
idx = (int) (charSet.length * Math.random());
tmpPwd += charSet[idx];
}
try {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(dto.getEmail());
message.setFrom(sendFrom);
message.setSubject("baeg-won 임시 비밀번호 안내 이메일입니다.");
message.setText("안녕하세요.\n"
+ "baeg-won 임시비밀번호 안내 관련 이메일 입니다.\n"
+ "임시 비밀번호를 발급하오니 블로그에 접속하셔서 로그인 하신 후\n"
+ "반드시 비밀번호를 변경해주시기 바랍니다.\n\n"
+ "임시 비밀번호 : " + tmpPwd);
javaMailSender.send(message);
} catch (MailParseException e) {
e.printStackTrace();
} catch (MailAuthenticationException e) {
e.printStackTrace();
} catch (MailSendException e) {
e.printStackTrace();
} catch (MailException e) {
e.printStackTrace();
}
User user = userRepository.findByUsername(dto.getUsername()).orElseThrow(() -> {
return new IllegalArgumentException("임시 비밀번호 변경 실패: 사용자 이름을 찾을 수 없습니다.");
});
user.setPassword(encoder.encode(tmpPwd));
}
}
- JavaMailSender는 스프링부트의 starter-mail이 제공하는 인터페이스로 setTo(), setFrom(), setTitle(), setText()와 같은 메소드를 제공합니다.
📝 findForm
- 뷰 페이지는 아래와 같이 구현하였습니다.
<div class="container" align="center">
<div class="form-title">Reset your password</div>
<div class="form-style">
<form>
<div align="left" style="word-spacing: -4px;">Enter your user account's verified email address and we will send you a password reset link.</div><br>
<div class="form-group" align="left">
<label for="username">Username</label> <input type="text" class="form-control" placeholder="Enter username" id="username">
</div>
<p class="valid-text" id="valid_username" align="left"></p>
<div class="form-group" align="left">
<label for="email">Email</label> <input type="email" class="form-control" placeholder="Enter email" id="email">
</div>
<p class="valid-text" id="valid_email" align="left"></p>
</form>
<div align="right">
<button id="btn-find" class="btn btn-find"><i class="fa-solid fa-check"></i> Send password reset email</button>
</div>
</div>
</div>
📝 Result
💡 알게 된 점
- SMTP의 개념과 임시 비밀번호가 발급되는 과정
'🚗 Backend Toy Project > 스프링 부트 게시판' 카테고리의 다른 글
[스프링부트 게시판] 36. 관리자 페이지 - 게시글 통계 (0) | 2022.10.26 |
---|---|
[스프링부트 게시판] 35. 관리자 페이지 - 회원 관리 (0) | 2022.10.24 |
[스프링부트 게시판] JPA Specification을 통해 쿼리 조건 다루기 (0) | 2022.10.06 |
[스프링부트 게시판] 34. 사용자 프로필 이미지 추가 (0) | 2022.09.27 |
[스프링부트 게시판] 33. 댓글 알림 기능 (1) | 2022.09.23 |