Notice
Recent Posts
Recent Comments
Link
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

Been_DevStep

Spring Boot - Recover Password (email certification) 본문

공부/SpringBoot

Spring Boot - Recover Password (email certification)

JChBeen 2022. 11. 9. 17:57

먼저 기본이 되는 HTML 소스 중 main 부분입니다.

<main class="--main main">
    <form class="form" id="form">
        <div class="title-container">
            <h1 class="title">
                비밀번호 재설정
            </h1>
        </div>

        <table class="table">
            <tbody>
            <tr>
                <th rowspan="2">이메일</th>
                <td>
                    <label class="label email">
                        <span hidden>이메일</span>
                        <input class="--object-input input" maxlength="50" name="email"
                               placeholder="이메일 주소를 입력해 주세요." type="email">
                    </label>
                </td>
            </tr>
            <tr>
                <td>
                    <input class="--object-button" type="button"
                           value="인증하기" name="emailSend">
                </td>
            </tr>
            <tr class="message-row" rel="messageRow">
                <th></th>
                <td>
                        <span class="message">
                            <i class="fa-solid fa-hourglass icon"></i>
                            <span class="text">입력하신 이메일로 인증을 진행할 수 있는 링크를 전송하였습니다. 해당 링크 확인 후 해당 페이지로 돌아와 주세요.</span>
                        </span>
                </td>
            </tr>

<!--querySelector(rel)  은 반드시 하나만 선택한다!-->
            <tr class="password-row" rel="passwordRow">
                <th>새로운 비밀번호</th>
                <td>
                    <label class="label password">
                        <span hidden>비밀번호</span>
                        <input class="--object-input input" maxlength="50" name="password"
                               placeholder="새로운 비밀번호를 입력해 주세요." type="password">
                    </label>
                    <label class="label password">
                        <span hidden>비밀번호 재입력</span>
                        <input class="--object-input input" maxlength="50" name="passwordCheck"
                               placeholder="비밀번호를 한번더 입력해 주세요." type="password">
                    </label>
                    <input class="--object-button" type="button"
                           value="비밀번호 재설정하기" name="passwordUpdate">
                </td>
            </tr>

            <tr class="warning-row" rel="warningRow">
                <th></th>
                <td>
                        <span class="warning">
                            <i class="fa-solid fa-triangle-exclamation icon"></i>
                            <span class="text"></span>
                        </span>
                </td>
            </tr>
            </tbody>
        </table>
        <input name="code" type="hidden">
        <input name="salt" type="hidden">

    </form>
</main>

비밀번호 찾기는 이메일 인증될 경우 js를 통해서 보여지게 됩니다.

<input name="code" type="hidden">
<input name="salt" type="hidden">

이 코드는 이메일이 인증되었다면 해당 이메일을 통해 email_auths 테이블에서 code와 salt를 가져와서 입력해줍니다.

하지만 사용자가 임의로 입력하는것이 아니기 때문에 hidden으로 숨겨줍니다.

 

먼저 이메일 인증과정입니다.

js에서 인증하기 버튼을 누를 경우에 적용는 동작입니다.

form['emailSend'].addEventListener('click', () => {
    Warning.hide();
    if (form['email'].value === '') {
        Warning.show('이메일을 입력해주세요.');
        form['email'].focus();
        return;
    }
    Cover.show('계정을 확인하고 있습니다. \n 잠시만 기다려주세요.');
    const xhr = new XMLHttpRequest();

    const formData = new FormData();
    formData.append('email', form['email'].value);

    xhr.open('POST', './recoverPassword');
    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 'success' :
                        emailAuthIndex = responseObject['index'];
                        form['email'].setAttribute('disabled', 'disabled');
                        form['emailSend'].setAttribute('disabled', 'disabled');
                        form.querySelector('[rel="messageRow"]').classList.add('visible');
                        break;
                    default:
                        Warning.show("해당 이메일을 사용하는 계정을 찾을수 없습니다.");
                        form['email'].focus();
                        form['email'].select();
                }
            } else {
                Warning.show('서버와 통신하지 못하였습니다. 잠시 후 다시 시도해 주세요.');
            }
        }
    };
    xhr.send(formData);
});

 

이에 해당하는 Controller 

@RequestMapping(value = "recoverPassword",
        method = RequestMethod.POST,
        produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String postRecoverPassword(EmailAuthEntity emailAuth) throws MessagingException {
    Enum<?> result = this.memberService.recoverPasswordSend(emailAuth);
    JSONObject responseObject = new JSONObject();

    responseObject.put("result", result.name().toLowerCase());

    if (result == CommonResult.SUCCESS)
        responseObject.put("index", emailAuth.getIndex());

    return responseObject.toString();
}

Service

@Transactional // 이메일 보내기 등등... 중간에 작업이 취소된다면 insert 철회를 위한 구문
public Enum<? extends IResult> recoverPasswordSend(EmailAuthEntity emailAuth) throws MessagingException {
    UserEntity existingUser = this.memberMapper.selectUserByEmail(emailAuth.getEmail());

    if (existingUser == null) return CommonResult.FAILURE;
    String authCode = RandomStringUtils.randomNumeric(6);
    String authSalt = String.format("%s%s%f%f",
            emailAuth.getEmail(), authCode, Math.random(), Math.random());
    authSalt = CryptoUtils.hashSha512(authSalt);
    Date createdOn = new Date();
    Date expiresOn = DateUtils.addMinutes(createdOn, 5);

    emailAuth.setEmail(emailAuth.getEmail());
    emailAuth.setCode(authCode);
    emailAuth.setSalt(authSalt);
    emailAuth.setCreatedOn(createdOn);
    emailAuth.setExpiresOn(expiresOn);
    emailAuth.setExpired(false);
    if(this.memberMapper.insertEmailAuth(emailAuth) == 0)
        return CommonResult.FAILURE;

    Context context = new Context();
    //Service에서 html파일에 접근하기 위해서 Context를 사용한다.
    context.setVariable("code", emailAuth.getCode());
    context.setVariable("email", emailAuth.getEmail());
    context.setVariable("salt", emailAuth.getSalt());

    //template에 만들어둔 email html파일 경로
    String text = this.templateEngine.process("member/recoverPasswordEmailAuth", context);
    MimeMessage mail = this.mailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(mail, "UTF-8");
    helper.setFrom("wjscdksqls@gmail.com");
    helper.setTo(emailAuth.getEmail());
    helper.setSubject("[스터디] 비밀번호 재설정 인증 링크");
    helper.setText(text, true);
    this.mailSender.send(mail);

    return CommonResult.SUCCESS;
}

 위코드에서

String text = this.templateEngine.process("member/recoverPasswordEmailAuth", context);

는 새롭게 html을 만들어서 연결시켜준 코드입니다.

 

UserEntity existingUser = this.memberMapper.selectUserByEmail(emailAuth.getEmail());

입력한 이메일이 존재하는지 DB에 검색해서 existingUser에 담아줍니다.

만약 email이 없다면 

if (existingUser == null) return CommonResult.FAILURE;

을 통해서 구문을 끝내줍니다.

 

존재한다면 email-auths에 맞게 setting을 한 뒤

emailAuth.setEmail(emailAuth.getEmail());
emailAuth.setCode(authCode);
emailAuth.setSalt(authSalt);
emailAuth.setCreatedOn(createdOn);
emailAuth.setExpiresOn(expiresOn);
emailAuth.setExpired(false);
if(this.memberMapper.insertEmailAuth(emailAuth) == 0)
    return CommonResult.FAILURE;

DB에 담아줍니다.

Context context = new Context();
//Service에서 html파일에 접근하기 위해서 Context를 사용한다.
context.setVariable("code", emailAuth.getCode());
context.setVariable("email", emailAuth.getEmail());
context.setVariable("salt", emailAuth.getSalt());

//template에 만들어둔 email html파일 경로
String text = this.templateEngine.process("member/recoverPasswordEmailAuth", context);
MimeMessage mail = this.mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mail, "UTF-8");
helper.setFrom("wjscdksqls@gmail.com");
helper.setTo(emailAuth.getEmail());
helper.setSubject("[스터디] 비밀번호 재설정 인증 링크");
helper.setText(text, true);
this.mailSender.send(mail);

구문을 통해서 정해진 메일을 발송해줍니다.

인증 이메일을 보내고 인증 결과를 기다리기 위해서 js에서 SetInterval을 사용해줍니다.

let emailAuthIndex = null;

setInterval(() => {
    if (emailAuthIndex == null) return;

    const xhr = new XMLHttpRequest();
    const formData = new FormData();
    formData.append('index', emailAuthIndex);
    xhr.open('POST', './recoverPasswordEmail');
    xhr.onreadystatechange = () => {
        if (xhr.readyState === XMLHttpRequest.DONE) {
            if (xhr.status >= 200 && xhr.status < 300) {
                const responseObject = JSON.parse(xhr.responseText);
                console.log(responseObject);
                switch (responseObject['result']) {
                    case 'success':
                        form['code'].value = responseObject['code'];
                        form['salt'].value = responseObject['salt'];
                        form.querySelector('[rel="messageRow"]').classList.remove('visible');
                        emailAuthIndex = null;
                        form.querySelector('[rel="passwordRow"]').classList.add('visible');
                        break;
                    default:
                }
            }
        }
    };
    xhr.send(formData);
}, 1000);

 setInterval(f, t)은  t밀리초마다 f함수를 실행하게 된다.

인증을 할 때까지 위 화면상태를 유지합니다.

 

이메일에서 인증을 하게되면  아래 구문을 통해 index값을 받아와서

emailAuthIndex = responseObject['index'];

 

emailAuthIndex 가 가르키는 값이 null이 아니라 받아온 값으로 셋팅되기 때문에 함수가 작동하고 함수 내에서

emailAuthIndex = null;

통해서 더이상 함수가 작동하지 않고 return 되게 되면서 이메일 인증 작업이 완료됩니다.

 

Comments