본문 바로가기

SpringBoot

SpringBoot 회원가입 기능구현 2

1. 이메일 인증 번호를 보내는 JS 기능

  • 아래 JS는 이메일인증번호 전송하기 버튼을 클릭했을때 발생하는 이벤트 로직을 구현한 것이다.
  • email input칸의 value값이 비어있거나 정규식(이메일 기준 정규식이 있음)을 통과하지 못할경우에는 return을 하여 클라이언트에게 경고창을 띄워주는 역할을 한다.

Email 인증번호 전송 조건식 JS


2. 이메일 인증 번호를 보내는 JS 기능(XHR)

  1. Cover.show(text)를 통해 인증번호가 전송되고 있다는 정보를 클라이언트에게 전달.
  2.  formData에 인증할 email값을 실어서 보낼준비를 한다.
  3.  xhr.open('POST', './email')을 통해 Controller에 있는 POST요청방식의 email(value)와 연결을 시켜준다
  4. open()이 정상적으로 실행이 되었다면 Cover.hide()함수를 통해 show하던것을 닫는다.
  5. 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구현)

  1. form태그안 emailVerify name을 가진 태그를 클릭했을시에 emailAuthCode의 value가 비어있거나 혹은 emailAuthCode가 정규식과 어긋난다면 그에 맞는 경고창을 클라이언트에게 보여준다.
  2. if조건문에 걸리지 않는다면 Cover.show함수를 통해 클라이언트에게 인증번호 확인중이라는 메세지를 보여줌과 동시에 xhr구문이 실행된다.
  3. formData에 email, code, salt들의 value를 append 해준다.
  4. xhr.open()을 통해 PATCH요청방식과 email경로가 맞다면 xhr구문이 실행된다.
  5. 이메일 인증번호에는 만료라는 특징이 있기 때문에 클라이언트에게 무한정 인증번호입력을 기다릴 수 없다.(이렇게 되면 인증번호를 한번만 보내주면 그 인증번호는 그사람의 전용 인증번호가 되기 때문에 의미가 없다.)
  6. 만료 시간은 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를 사용한다. Salt를 암호화 하는 방법은 Service에서 구현이 된다.

Salt Service 로직 1


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자리)의 값이 대입이 된다.

Salt Service 로직 2

  • 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해준다.(재설정해준다. == 업데이트를 해준다라는 뜻과 같음)

Salt Service 로직 3

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를 반환한다.

Salt Service 로직 4

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로 업데이트 시켜준다. 

 

이메일 인증하다가 세계일주 할거같다 =ㅅ=