비밀번호를 분실한 회원에게 임시 비밀번호를 전송하여 임시 비밀번호로 로그인할 수 있는 기능을 추가했다.
Gmail SMTP 프로토콜을 이용하여 이메일을 전송하는 기능을 구현했다.
spring-boot-starter-mail
의존성을 추가한다.SMTP서버
: 이메일을 송수신하는 서버구글 계정만 있으면 무료로 발송할 수 있는 Gmail SMTP Server
https://myaccount.google.com/u/2/security 링크로 접속하여 2단계 인증을 사용하도록 한다(휴대폰 인증 등을 거침)
앱 비밀번호 생성
spring:
mail:
host: smtp.gmail.com
port: 587
username: helpringproject@gmail.com
password: ...
properties:
mail.smtp.auth: true
mail.smtp.starttls.enable: true
구글은 인증에 TLS를 사용한다.
TLS는 SSL과 비슷한 것인데, SSL 3.0 부터 TLS 라고 부르며, 이는 통신에 사용되는 데이터를 암호화 하는 것이다.
dependencies {
...
implementation 'org.springframework.boot:spring-boot-starter-mail'
spring-boot-starter-mail 의존성을 추가한다.
스프링부트가 자동으로 JavaMailSender을 빈으로 등록해준다.
로그인 화면
에서 비밀번호 찾기
를 누르면 모달창으로 비밀번호 창이 뜬다.JavaMailSender
인터페이스를 이용하여 해당 이메일로 임시 비밀번호를 포함한 메일을 전송한다.<!-- 비밀번호 찾기 모달 -->
<th:block th:replace="/layout/fragment/modal::findPasswordFragment"></th:block>
...
<button type="button" class="btn btn-link" data-bs-toggle="modal"
data-bs-target="#findPw">비밀번호를 잊으셨나요?</button>
...
<!--임시 비밀번호 모달-->
<th:block th:fragment="findPasswordFragment">
<!-- 비밀번호 찾기 모달 -->
<div id="findPw" class="modal fade">
...
<div class="card-body">
<div class="text-start">
<p>입력한 이메일로 임시 비밀번호가 전송됩니다.</p>
<div class="input-group input-group-outline my-3">
<label class="form-label">Email</label>
<input type="email" id="memberEmail" name="memberEmail" class="form-control" required>
</div>
<div class="text-center">
<button type="button" class="btn bg-gradient-primary w-100 my-4 mb-2"
id="checkEmail">비밀번호 발송</button>
...
</th:block>
<script>
const header = $("meta[name='_csrf_header']").attr('content');
const token = $("meta[name='_csrf']").attr('content');
$('#checkEmail').on('click', function(){
checkEmail();
});
function checkEmail(){
const memberEmail = $('#memberEmail').val();
if(!memberEmail || memberEmail.trim() === ""){
alert("이메일을 입력하세요.");
} else {
$.ajax({
type: 'GET',
url: '/rest/checkEmail',
data: {
'memberEmail': memberEmail
},
dataType: "text",
beforeSend: function (xhr) {
xhr.setRequestHeader(header, token);
}
}).done(function(result){
console.log("result :" + result);
if (result == "true") {
sendEmail();
alert('임시비밀번호를 전송 했습니다.');
window.location.href="/auth/login";
} else if (result == "false") {
alert('가입되지 않은 이메일입니다.');
}
}).fail(function(error){
alert(JSON.stringify(error));
})
}
};
function sendEmail(){
const memberEmail = $('#memberEmail').val();
$.ajax({
type: 'POST',
url: '/sendPwd',
data: {
'memberEmail' : memberEmail
},
beforeSend: function(xhr){
xhr.setRequestHeader(header, token);
},
error: function(error){
alert(JSON.stringify(error));
}
})
}
</script>
/rest/checkEmail
컨트롤러에 GET
방식으로 memberEmail
을 넘겨주면 해당 메일이 DB에 존재하는지 존재하지 않는지 result
를 반환한다.result
값에 따라 임시 비밀번호를 전송하는 컨트롤러를 호출하거나 가입되지 않은 이메일임을 알린다.뷰에서 memberEmail 을 파라미터로 받아 이메일이 존재하는지 확인하는 memberService 호출
/** 이메일이 DB에 존재하는지 확인 **/
@GetMapping("/checkEmail")
public boolean checkEmail(@RequestParam("memberEmail") String memberEmail){
log.info("checkEmail 진입");
return memberService.checkEmail(memberEmail);
}
DB에 이메일이 존재하는지 확인
/** 이메일이 존재하는지 확인 **/
@Override
public boolean checkEmail(String memberEmail) {
/* 이메일이 존재하면 true, 이메일이 없으면 false */
return memberRepository.existsByEmail(memberEmail);
}
임시 비밀번호를 생성하고 메일을 생성 & 전송하는 컨트롤러
/** 비밀번호 찾기 - 임시 비밀번호 발급 **/
@PostMapping("/sendPwd")
public String sendPwdEmail(@RequestParam("memberEmail") String memberEmail) {
log.info("sendPwdEmail 진입");
log.info("이메일 : "+ memberEmail);
/** 임시 비밀번호 생성 **/
String tmpPassword = memberService.getTmpPassword();
/** 임시 비밀번호 저장 **/
memberService.updatePassword(tmpPassword, memberEmail);
/** 메일 생성 & 전송 **/
MailVo mail = mailService.createMail(tmpPassword, memberEmail);
mailService.sendMail(mail);
log.info("임시 비밀번호 전송 완료");
return "member/member-login";
}
임시 비밀번호를 암호화하고, member 객체의
updatePassword
메소드를 호출하여 임시 비밀번호 업데이트
/** 임시 비밀번호 생성 **/
@Override
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 pwd = "";
/* 문자 배열 길이의 값을 랜덤으로 10개를 뽑아 조합 */
int idx = 0;
for(int i = 0; i < 10; i++){
idx = (int) (charSet.length * Math.random());
pwd += charSet[idx];
}
log.info("임시 비밀번호 생성");
return pwd;
}
/** 임시 비밀번호로 업데이트 **/
@Override
public void updatePassword(String tmpPassword, String memberEmail) {
String encryptPassword = encoder.encode(tmpPassword);
Member member = memberRepository.findByEmail(memberEmail).orElseThrow(() ->
new IllegalArgumentException("해당 사용자가 존재하지 않습니다."));
member.updatePassword(encryptPassword);
log.info("임시 비밀번호 업데이트");
}
DB의 데이터 값을 변경하는 메소드이므로 해당 도메인 클래스에 메소드를 정의했다
/** 비밀번호 변경 메서드 **/
public void updatePassword(String password){
this.password = password;
}
메일 전송 처리 담당 서비스
@Service
@Transactional
@RequiredArgsConstructor
@Slf4j
public class MailServiceImpl implements MailService {
private final JavaMailSender mailSender;
private static final String title = "Helpring 임시 비밀번호 안내 이메일입니다.";
private static final String message = "안녕하세요. Helpring 임시 비밀번호 안내 메일입니다. "
+"\n" + "회원님의 임시 비밀번호는 아래와 같습니다. 로그인 후 반드시 비밀번호를 변경해주세요."+"\n";
private static final String fromAddress = "helpringproject@gmail.com";
/** 이메일 생성 **/
@Override
public MailVo createMail(String tmpPassword, String memberEmail) {
MailVo mailVo = MailVo.builder()
.toAddress(memberEmail)
.title(title)
.message(message + tmpPassword)
.fromAddress(fromAddress)
.build();
log.info("메일 생성 완료");
return mailVo;
}
/** 이메일 전송 **/
@Override
public void sendMail(MailVo mailVo) {
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setTo(mailVo.getToAddress());
mailMessage.setSubject(mailVo.getTitle());
mailMessage.setText(mailVo.getMessage());
mailMessage.setFrom(mailVo.getFromAddress());
mailMessage.setReplyTo(mailVo.getFromAddress());
mailSender.send(mailMessage);
log.info("메일 전송 완료");
}
}
MainSender
이다. 이 인터페이스는 SimpleMailMessage
객체를 사용하고, 첨부파일 등을 지원하지 않는다.JavaMailSender
인터페이스는 MailSender
인터페이스를 상속받았으며, 멀티 파트 데이터를 처리할 수 있는 MainSenderMIME
를 지원한다.SimpleMailMessage
를 이용했다. (확장성을 위해 JavaMailSender
인터페이스를 이용했다.)SimpleMailMessage
mailSender.send
: 실제 메일 발송
MailVo
/** 메일 메시지 정보 **/
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MailVo {
private String toAddress; // 받는 이메일 주소
private String title; // 이메일 제목
private String message; // 이메일 내용
private String fromAddress; // 보내는 이메일 주소
}
MailVo
객체를 따로 생성하여 보낼 이메일 주소, 제목, 내용, 임시 비밀번호, 받을 이메일 주소를 입력한다.SimpleMailMessage
객체에 mailVo의 이메일 주소, 제목 등을 넘겨서 메일 정보를 설정해준다.출처
https://victorydntmd.tistory.com/342
https://www.siteground.com/kb/gmail-smtp-server/
https://intrepidgeeks.com/tutorial/spring-email-temporary-password-issuance
https://victorydntmd.tistory.com/342
https://offbyone.tistory.com/167
https://bamdule.tistory.com/238
https://velog.io/@max9106/Spring-Boot-Gmail-SMTP-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0%EB%A9%94%EC%9D%BC%EB%B3%B4%EB%82%B4%EA%B8%B0
https://ktko.tistory.com/entry/JAVA-SMTP%EC%99%80-Mail-%EB%B0%9C%EC%86%A1%ED%95%98%EA%B8%B0Google-Naver
https://privatenote.tistory.com/172