1. 이메일 인증 번호를 보내는 JS 기능
- 아래 JS는 이메일인증번호 전송하기 버튼을 클릭했을때 발생하는 이벤트 로직을 구현한 것이다.
- email input칸의 value값이 비어있거나 정규식(이메일 기준 정규식이 있음)을 통과하지 못할경우에는 return을 하여 클라이언트에게 경고창을 띄워주는 역할을 한다.
2. 이메일 인증 번호를 보내는 JS 기능(XHR)
- Cover.show(text)를 통해 인증번호가 전송되고 있다는 정보를 클라이언트에게 전달.
- formData에 인증할 email값을 실어서 보낼준비를 한다.
- xhr.open('POST', './email')을 통해 Controller에 있는 POST요청방식의 email(value)와 연결을 시켜준다
- open()이 정상적으로 실행이 되었다면 Cover.hide()함수를 통해 show하던것을 닫는다.
- case 'success'일경우 안에서의 로직은 유동적이기 때문에 각 input칸 마다 필요한 로직 처리를 해준다.
- fom태그를 부모로 가지는 태그들 중 name이 email, emailSend인 태그에 disabled라는 속성 값을 추가해준다.setAttribute('name','value')
- 기존에 disabled속성을 가지고 있던 emailAuthCode태그에 disabled속성을 삭제한다. 그와 동시에 focus()메서드를 통해 cursur가 emailAuthCode로 향하게 한다.
- Result값이 success일 경우 name이 emailAuthSalt인 태그의 value값은 responseObject['salt']값이 대입된다.(아래의 input태그안(name이 emailAuthCode)의 value 값이 [2]자료처럼 salt값으로 바뀌게 된다.) 즉, 클라이언트 본인이 사용하는 웹 페이지 안에서만 salt값을 볼 수 있게 된다.
- JSON.parse()를 해주지 않으면 Result와 success가 둘다 문자열로 들어가게 된다. Js에서 값을 받기 위해서는 Object처리를 해주어야 하기 때문 parse()를 반드시 JS에서 처리해주어야 한다.
[1] JSON.parse()를 통한 value의 변화 과정 | [2] salt 값이 input에 들어 가는 자료 |
Cover.show('인증번호를 전송하고 있습니다.\n잠시만 기다려주세요.');
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('email', form['email'].value);
// append를 통해 여러개의 값을 formData라는 것에 실어서 send할 수 있다.
xhr.open('POST', './email');
// 경로도 유심히 봐야한다. ./은
xhr.onreadystatechange = () => {
Cover.hide();
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status >= 200 && xhr.status < 300) {
const responseObject = JSON.parse(xhr.responseText);
switch (responseObject['result']) {
case 'success':
EmailWarning.show('인증 번호를 전송하였습니다. 전송된 인증 번호는 5분간만 유효합니다.');
form['email'].setAttribute('disabled', 'disabled');
form['emailSend'].setAttribute('disabled', 'disabled');
form['emailAuthCode'].removeAttribute('disabled');
form['emailAuthCode'].focus();
form['emailAuthSalt'].value = responseObject['salt'];
form['emailVerify'].removeAttribute('disabled');
break;
case 'email_duplicated':
EmailWarning.show('해당 이메일은 이미 사용 중입니다.');
form['email'].focus();
form['email'].select();
break;
default:
EmailWarning.show('알 수 없는 이유로 인증 번호를 전송하지 못 하였습니다. 잠시 후 다시 시도해 주세요.');
form['email'].focus();
form['email'].select();
}
// console.log('이게');
// console.log(xhr.responseText);
// console.log('이렇게 변함');
// console.log(JSON.parse(xhr.responseText));
// console.log('----------------');
// console.log(responseObject['result']);
// console.log(responseObject['salt']);
// Json객체로 받아온 "문자열"을 Js에서 오브젝트타입으로 바꾸기 위해서 JSON.parse를 사용하였다.
} else {
EmailWarning.show('서버와 통신하지 못하였습니다.잠시후 다시 시도해 주세요.');
}
}
}
xhr.send(formData);
});
3. 이메일 인증번호 전송 관련 POST 요청방식 Controller
- 인증번호 전송 버튼을 눌렀을시 요청이 발생함으로 POST 요청방식을 사용해야한다. 이때 전달인자로는 UserEntity와 EmailAuthEntity 둘다 받아야 한다. 그 이유는 EmailAuthEntity에서 email, salt, code가 정상적인지 확인하고 UserEntity의 email로 받아야 하기 때문이다. (중요한 포인트)
- 추가적으로 result의 값이 SUCCESS일경우 responseObject에 salt를 넣어주는 이유 Js에서 input태그에 salt의 value값을 전송하는 코드에 맞게 클라이언트에 보여주기위해서 이다.
4. 이메일 인증번호 확인 관련 PATCH 요청방식 Controller
- 인증번호 요청을 받았으면 그 요청에 대한 인증이 완료가 되었는지 어떤지는 알수가 없다.(현재 상황에선) 그래서 PATCH 요청방식을 사용하여 isExpired 레코드 값(기본값은 false로 지정되어있음)을 true로 바꿔준다면 그 이메일은 인증이 완료가 되었다라고 판단을 할 수가 있다. 서비스에서 메서드 기능은 구현이 되겠지만 수정에 관한 방식이기 때문에 PATCH를 사용해 주었다.
5. 이메일 인증코드 확인 관련 JS(XHR구현)
- form태그안 emailVerify name을 가진 태그를 클릭했을시에 emailAuthCode의 value가 비어있거나 혹은 emailAuthCode가 정규식과 어긋난다면 그에 맞는 경고창을 클라이언트에게 보여준다.
- if조건문에 걸리지 않는다면 Cover.show함수를 통해 클라이언트에게 인증번호 확인중이라는 메세지를 보여줌과 동시에 xhr구문이 실행된다.
- formData에 email, code, salt들의 value를 append 해준다.
- xhr.open()을 통해 PATCH요청방식과 email경로가 맞다면 xhr구문이 실행된다.
- 이메일 인증번호에는 만료라는 특징이 있기 때문에 클라이언트에게 무한정 인증번호입력을 기다릴 수 없다.(이렇게 되면 인증번호를 한번만 보내주면 그 인증번호는 그사람의 전용 인증번호가 되기 때문에 의미가 없다.)
- 만료 시간은 Service에서 설정을 해줌으로써 그 만료 값이 될경우 Expired Result값을 통해 클라이언트에게 만료가 되었다는 정보를 전달해 준다.
↓↓↓ 이메일 인증번호 확인 관련 JS 코드 더보기 ↓↓↓
더보기
form['emailVerify'].addEventListener('click', () => {
if (form['emailAuthCode'].value === '') {
EmailWarning.show('인증번호를 입력해 주세요.');
form['emailAuthCode'].focus();
return;
}
if (!new RegExp('^(\\d{6})$').test(form['emailAuthCode'].value)) {
EmailWarning.show('올바른 인증 번호를 입력해 주세요.');
form['emailAuthCode'].focus();
form['emailAuthCode'].select();
return;
}
Cover.show('인증 번호를 확인하고 있습니다. \n\n잠시만 기다려 주세요.');
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('email', form['email'].value);
formData.append('code', form['emailAuthCode'].value);
formData.append('salt', form['emailAuthSalt'].value);
xhr.open('PATCH', 'email');
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
Cover.hide();
if (xhr.status >= 200 && xhr.status < 300) {
const responseObject = JSON.parse(xhr.responseText);
switch (responseObject['result']) {
case 'expired':
EmailWarning.show('인증 정보가 만료되었습니다. 다시 시도해 주세요.');
form['email'].removeAttribute('disabled');
form['email'].focus();
form['email'].select();
form['emailSend'].removeAttribute('disabled');
form['emailAuthCode'].value = '';
form['emailAuthCode'].setAttribute('disabled', 'disabled');
form['emailAuthSalt'].value = '';
form['emailVerify'].setAttribute('disabled', 'disabled');
break;
case 'success':
EmailWarning.show('이메일이 정상적으로 인증되었습니다.');
form['emailAuthCode'].setAttribute('disabled', 'disabled');
form['emailVerify'].setAttribute('disabled', 'disabled');
form['password'].focus();
break;
default:
EmailWarning.show('인증번호가 올바르지 않습니다.');
form['emailAuthCode'].focus();
form['emailAuthCode'].select();
}
} else {
EmailWarning.show('서버와 통신하지 못하였습니다. 잠시 후 다시 시도해 주세요.');
}
}
}
xhr.send(formData);
});
여기서 KeyPoint! Salt는 무엇일까?
만약, email과 인증번호를 입력하는 사람이 클라이언트가 아닌 다른 누군가라면 개인정보가 쉽게 유출이 될 것이다. 이러한 상황을 막기 위해서 email인증시 Salt를 사용하는 것이다. Salt는 클라이언트 각 개인의 PC에서 보여지는 일종의 일회용 암호화 코드와 같은의미로 인증번호 전송을 누를때마다 생성된다.
이를 위해 Salt를 사용한다. Salt를 암호화 하는 방법은 Service에서 구현이 된다.
6. Email 관련 기능 및 Salt키 생성 Service
- @Transactional 어노테이션은 인증절차를 진행중 중간에 실패를 할경우 이전에 성공했던 값들에 대한 처리를 하지 않고 시작점으로 돌아가게 해주는 기능을 수행한다.
1. UserEntity 새 객체에 담을 값 및 Email 중복 검사
- UserEntity타입의 existingUser라는 새로운 객체에 Mapper.selectUserByEmail(클라이언트user.getEmail)을 하여 클라이언트가 작성한 email값을 받는 객체가 된다.
- 이때 if조건문에서 null이 아니다 라는 말인 즉슨, DB에 이미 같은 이름의 이메일이 존재한다는 뜻이므로 Email_Duplicated를 반환해준다.
2. Salt 암호화 키 생성
- 문자열 타입의 authCode 변수를 생성하여 그곳에 랜덤한 숫자 6가지를 담는다.(이때 RandomStringUtils는 Common.lang3이라는 서비스 기능구현에 있어서 편리함을 위해 추가한 의존성의 타입이다.)
- 문자열 타입의 authSalt 변수에는 String.format의 %값에 (이메일.랜덤한숫자6자리.랜덤한숫자6자리.랜덤한숫자6자리)의 값이 대입이 된다.
- 문자열 타입의 authCode 변수를 생성하여 그곳에 랜덤한 숫자 6가지를 담는다.(이때 RandomStringUtils는 Common.lang3이라는 서비스 기능구현에 있어서 편리함을 위해 추가한 의존성의 타입이다.)
- 문자열 타입의 authSalt 변수에는 String.format의 %값에 (이메일.랜덤한숫자6자리.랜덤한숫자6자리.랜덤한숫자6자리)의 값이 대입이 된다.
- StringBuilder타입의 hashing된 salt값을 받을 변수 authSaltHashBuilder 객체를 생성한다.
- MessageDigest타입을 통해 md변수에 SHA-512(512비트) 암호화 알고리즘을 받아온다.
- md.update메서드를 통해 authSalt값을 바이트 단위로 변환시켜준다.
- 향상된 for문을 통해 hashByte라는 변수를 md.digest()[128자리]만큼 반복시켜준다. 이때 hashByte의 자리수 만큼 반복을 하면서 hashByte에 값이 담기게 된다.
- authSaltHashBuilder에 hasyByte를 담아준다. 2자리씩 담기면서 랜덤으로 계속해서 변한다.
- 최종적으로 authSalt변수에는 authSaltHashBuilder를 문자열화 한 값이 대입이 된다.
3. 이메일 인증코드 만료 시간 처리
- Date타입의 객체 createdOn을 만들어 준다.(생성된 시간)
- Date타입의 expiresOn 객체에는 DateUtils타입을 사용하여 (매개변수, 앞의 매개변수에 입력한 수 만큼 더한 값) 5분이 createdOn(생성된 시간)에 5분이 더해진 값을 가진 객체가 된다.
4. 새롭게 설정된 값을 전달인자로 받고있는 emailAuth에 대입하기(Update해주기)
- emailAuth(전달인자)에 위에서 만들어준 authCode(이메일 인증 번호), authSalt(해싱된 salt값), user.getEmail()(user가 작성한 이메일), createdOn(인증코드가 생성된 시간), expiresOn(인증코드가 만료되는 시간), false(인증이 만료되기전 기본값)을 set해준다.(재설정해준다. == 업데이트를 해준다라는 뜻과 같음)
5. Service페이지에서 ModelAndView 구현하기(Html, thymeleaf)
- Context타입의 context객체를 생성해준다 (Html문서안 thymeleaf값을 받아오기 위한 타입이다.)
- 문자열타입(Html경로가 문자열이기 때문에)인 text에 templateEngine.process()메서드를 사용하여 ('Html경로',위에서 thymeleaf값을 받는 context )를 매개변수로 값을 대입한다.
- MimeMessage타입의 mail을 만들어 .createMimeMessage() 메서드를 사용한다.(MailSender 인터페이스를 상속받은 JavaMailSender는 Java Mail API의 MimeMessage를 이용해서 메일을 발송하는 추가적인 기능이다). 이 객체에다가 정보를 담아서 최종적으로 mailSender.send()메서드를 통해 메일을 발송하면 set으로 작성한 값들이 저장이되어 같이 보내지게 되는것이다.
- 인증메일이 보내진다는것은 위에서 모든 정보가 일치했음을 나타내므로 SUCCESS를 반환한다.
6. Service페이지에서 이메일 인증 확인 기능(email, code, salt) 구현하기
- EmailAuthEntity(email,code,salt값을 비교하기 위한 Entity)타입인 새로운 객체 existingEmailAuth를 만들고 그안에 값을 emailAuth의 getEmail, getCode, getSalt값을 대입한다. 이때 existingEmailAuth객체가 null이라면 인증이 되지 않은것이기 때문에 Failure을 반환한다.
- 두번째 if절에서 getExpiredOn(만료시간)과 compareTo(비교매서드(마이너스 역할을 함)) 새로운 Date객체(현재시간) 을 뺀값이 0보다 작다는 뜻은 (과거시간 - 미래시간) 이므로 음수가 나오기 때문에 EXPIRED(만료된 결과)를 반환한다.
- 위의 if절을 모두 통과했을 경우 existingEmailAuth의 인증(IsExpired)의 boolean값을 true로 업데이트 시켜준다.
이메일 인증하다가 세계일주 할거같다 =ㅅ=
'SpringBoot' 카테고리의 다른 글
SpringBoot 게시판 기능구현 1 (0) | 2022.11.20 |
---|---|
SpringBoot 회원가입 기능구현 4 , 비밀번호 재설정 기능구현 (0) | 2022.11.15 |
SpringBoot 로그인 기능 구현, session값을 통한 게시판 접근 (0) | 2022.11.15 |
SpringBoot 회원가입 기능구현 3 (0) | 2022.11.13 |
SpringBoot 회원가입 기능구현 1 (2) | 2022.11.11 |