FureverHomes 프로젝트 - 회원가입 2. 이메일 인증
SMTP를 이용해서 인증코드를 보내고 이메일 인증을 진행하는 기능을 구현해볼 것이다. Java의 JavaMailSender를 이용해서 메일을 보낼건데 그러기 위해서 디펜던시를 추가해줘야 한다.
메일 전송을 위한 기본 설정
//Mail
implementation 'org.springframework.boot:spring-boot-starter-mail'
그래들에 디펜던시를 추가해주고 Google SMTP 설정을 해보도록 하겠다. 먼저 Gmail에 들어가서 톱니바퀴 모양의 설정을 눌러주고 모든 설정 보기에 들어가준다. '전달 및 POP/IMAP' 탭에 들어가서 IMAP 사용으로 바꿔주고 저장해준다.
그다음 구글 계정으로 들어가서 '보안 > Google에 로그인하는 방법 > 2단계 인증'을 진행해준다.
안내에 따라서 2단계 인증을 진행해주고 그다음 앱 비밀번호를 설정해줘야 한다. 이때 생성되는 비밀번호는 properties에 작성을 해야하므로 복사를 해두어야 한다. 앱 비밀번호 탭이 보이지 않다면 '앱 비밀번호'라고 검색을 해주면 된다.
본인이 사용하고자 하는 이름을 작성해주면 된다. 나는 'GMAIL-SMTP'라고 작성해줬다. 작성을 하고 만들기를 누르면 16자리의 비밀번호가 생성될 것이다. 비밀번호는 복사를 해둬야 한다. google 설정이 끝났으면 application.yml을 작성해줄 차례이다.
spring:
mail:
host: smtp.gmail.com
prot: 587
username: fureverh@gmail.com
password: //발급받은 비밀번호
properties:
mail:
smtp:
auth: true
starttls:
enable: true
password에 방금 발급받은 앱 비밀번호를 작성해주면 된다. 참고로 깃허브에 올릴 때 꼭 이 application.yml 파일은 gitinit을 시켜줘야 한다. 비밀번호가 적힌 파일이 업로드 되면 해킹 당해서 스팸 메일을 무더기로 보낼 지도 모른다. (경험담..)
그리고 메일을 주소와 제목, 내용 그리고 인증코드가 담겨있는 메일 dto를 하나 생성해준다.
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MailDTO {
private String email; //이메일 주소
private String title; //제목
private String message; //메시지 내용
private int authCode; //인증코드
}
기본적인 설정은 다 됐다. 이제 로직을 짜면 된다. 인증하기 버튼을 누르면 인증코드를 담고있는 인증메일이 전송되고 인증코드를 사용자가 작성해서 올바른 값을 입력하면 회원가입이 완료되는 구성이다. 서버에서 인증코드를 생성해서 메일을 보내고, 사용자가 입력한 인증코드가 생성한 인증코드가 맞는지를 비교하는 과정에 대해서 고민을 많이했는데 인증코드라는건 보안상으로 중요한 존재이므로 세션에 암호화시켜서 넣어두고 값을 비교하는 것보다 데이터베이스를 활용하는게 안전하지 않을까하는 생각을 했다. 인증코드 컬럼을 Boolean으로 만들지 않고 int 값으로 변경해서 제일 처음 인증을 받지 않은 회원은 0, 인증을 진행하는 동안은 인증코드로 값을 변경하고 인증이 성공적으로 마무리되면 1으로 변경되도록 설정했다.
MemberService - 이메일인증
//인증 메일 전송 - 인증코드 만들어서 db 저장
@Transactional
public Boolean sendEmailWithAuthCode(final MailDTO mailDTO) {
String email = mailDTO.getEmail();
int authCode = makeAuthCode();
Member member = memberRepository.findByEmail(email);
if (member != null) {
member.updateEmailAuth(authCode);
memberRepository.save(member);
return sendMail(email, authCode);
} return false;
}
//인증 메일 전송 - 메일 보내기
private Boolean sendMail(String email, int authCode) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(email);
message.setSubject("fureverhomes 인증 메일입니다.");
message.setText("인증코드: " + authCode);
System.out.println("[message] "+ message);
try{
mailSender.send(message);
System.out.println(email + " 메일 전송 성공");
return true;
} catch (MailException e){
e.printStackTrace();
System.out.println(email + " 메일 전송 실패");
return false;
}
}
//인증 메일 전송 - 랜덤 코드 생성
private int makeAuthCode() {
Random random = new Random();
return random.nextInt(888888) + 111111; // 범위: 111111~999999
}
- sendEmailWithAuthCode ()
저번 포스팅에서 회원가입 정보가 무사히 데이터베이스에 들어가게 되면 email 값을 응답코드에 담아서 전송할 수 있도록 만들었다. 클라이언트의 sessionStorage에 암호화시킨 이메일 정보를 담아두고 사용자가 인증코드 보내기 버튼을 클릭하면 sessionStorage에 담겨있는 이메일 정보를 삭제하고 서버에 다시 담아 보낸다. 이 메소드에서는 email 값을 이용해 회원 객체의 이메일 인증 컬럼을 인증 코드 값으로 변경하고 무사히 실행되면 이메일을 보내도록 만든 메소드이다.
- sendMail ()
실질적으로 메일을 보내는 메소드인데 보내고자 하는 메일 주소, 제목, 내용을 담은 mailDTO를 mailSender를 이용해서 보낸다.
- makeAuthCode ()
난수를 생성해주는 메소드이다.
MemberController
//이메일 인증
@PostMapping("/signup/emailAuth.post")
public ResponseEntity<Void> emailAuth(@RequestBody MailDTO mailDTO) {
if (memberService.sendEmailWithAuthCode(mailDTO)) {
return ResponseEntity.ok().build();
} else {
return ResponseEntity.internalServerError().build();
}
}
//인증코드 여부 유효성 검사
@PostMapping("/signup/success.post")
public ResponseEntity<Void> successEmailAuth(@RequestBody Map<String,String> mapParam) {
if(memberService.isSuccessAuth(mapParam)){
return ResponseEntity.ok().build();
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
}
emailAuth()를 이용해 이메일 정보를 받아 인증메일 전송까지 처리해주고 있다.
successEmailAuth()는 인증메일을 전송 후 사용자가 입력한 인증코드가 올바른 코드인지 확인해주기 위해 만든 메소드이다.
mail.js, ajax를 이용해 비동기 화면 구성
이메일 인증을 진행하고 코드를 입력하는 기능까지 한 페이지에서 완료해보도록 하겠다. 먼저 html을 이렇게 작성해줬다.
<body>
<main>
<!-- Section -->
<section class="min-vh-100 d-flex align-items-center py-5 py-lg-0" th:style="'background-color: rgb(255, 191, 0);'">
<div class="container">
<div class="row justify-content-center">
<div class="col-12 d-flex align-items-center justify-content-center">
<div class="signin-inner mt-3 mt-lg-0 bg-white shadow-soft border rounded border-light p-4 p-lg-5 w-100 fmxw-500">
<div class="mt-3 mb-3" id="email-auth">
<h4 class="text-center">이메일 인증을 진행하세요</h4>
<button type="button" id="send-btn" class="btn btn-block btn-warning">인증 코드 보내기</button>
</div>
</div>
</div>
</div>
</div>
</section>
</main>
인증 코드 보내기라는 버튼을 클릭하면 인증코드를 입력할 수 있도록 화면을 바꿔주는 함수를 살펴보도록 하겠다.
let dataBody = $("#email-auth");
let form = $("<form>", {id: "emailTokenForm"});
let div1 = $("<div>", {class: "form-group"});
let label1 = $("<label>", {text: "인증번호"});
let div2 = $("<div>", {class: "form-group"});
let div3 = $("<div>", {class: "input-group mb-4"});
let div4 = $("<div>", {class: "input-group-prepend"});
let span1 = $("<span>", {class: "input-group-text"});
let span2 = $("<span>", {class: "fas fa-unlock-alt"});
let input1 = $("<input>", {
class: "form-control",
id: "email-code",
placeholder: "인증번호를 입력해주세요",
type: "email_Token",
"aria-label": "email_Token",
required: true
})
let button1 = $("<button>", {
type: "button",
id: "auth-btn",
class: "btn btn-block btn-warning",
text: "인증하기"
})
$("#send-btn").click(function () {
// dataBody 재구성
dataBody.empty();
dataBody.append(form);
form.append(div1);
div1.append(label1);
div1.append(div2);
div2.append(div3);
div3.append(div4);
div4.append(span1);
span1.append(span2);
div3.append(input1);
form.append(button1);
$.ajax({
type: "POST",
url: "/fureverhomes/signup/emailAuth.post",
contentType: 'application/json',
data: JSON.stringify({
email: email
}),
success: function (data, status) {
},
error: function (data, textStatus) {
alert("인증코드 전송에 실패하였습니다. 다시 시도하여 주세요.");
location.href = "/fureverhomes/signup/emailAuth";
},
complete: function (data, textStatus) {
},
});
});
버튼을 클릭하게 되면 입력 폼이 생겨나고 ajax가 실행되도록 하였다. data에 있는 email은 sessionStorage에서 가지고 온 값이다.
$(document).on('click', '#auth-btn', function (event) {
event.preventDefault();
let emailCode = $("#email-code").val();
sessionStorage.removeItem("email");
console.log(emailCode);
$.ajax({
type: "POST",
url: "/fureverhomes/signup/success.post",
contentType: 'application/json',
data: JSON.stringify({
email : email,
emailCode : emailCode,
}),
success: function (data, status) {
alert("가입 성공했습니다. 로그인해주세요.")
location.href = "/fureverhomes/signin"
},error: function (data, status) {
alert("이메일인증에 실패했습니다. 다시 시도해주세요.")
location.href = "/fureverhomes/signup"
}
});
});
인증번호를 입력하고 인증하기 버튼을 누르게 되면 세션 스토리지에 담겨있던 email 정보가 삭제되고 인증코드 유효성 검사를 위해 ajax를 실행하고 있다. 올바른 값을 입력하면 가입 성공을 알리고 로그인 창으로 이동하고, 인증에 실패하면 다시 회원가입창으로 돌아가도록 구성했다.
MemberService - 인증코드 확인
//인증 성공 여부 확인
@Transactional
public Boolean isSuccessAuth(Map<String,String> mapParam) {
String email = mapParam.get("email");
String authCode = mapParam.get("emailCode");
Member member = memberRepository.findByEmail(email);
int getAuthCode = member.getEmail_auth();
System.out.println("입력받은 값"+authCode);
System.out.println("db안에 값"+getAuthCode);
if (authCode.equals(String.valueOf(getAuthCode))) {
member.updateEmailAuth(1);
memberRepository.save(member);
return true;
} return false;
}
회원에 속해있는 값을 가지고오고 입력받은 값을 비교 후 값이 일치하면 1으로 변경되도록 구성해줬다.
테스트
값들을 입력하고 회원가입 버튼을 눌러주면 데이터베이스에 값들이 잘 들어온다.
이메일 인증창으로 넘어가게 되고 인증 코드 보내기를 누르면 비동기로 페이지 이동없이 인증코드를 입력하는 창으로 바뀐다.
메일을 확인해보면 이렇게 메일이 와있는 모습을 확인할 수 있다.
인증번호를 제대로 입력하면 가입성공 메시지가 뜨고 회원가입이 완료된다.