Programming/Java

[Spring] Gmail SMTP를 이용하여 이메일 인증 구현 - 인증코드 검증

mar1po5a 2025. 3. 24. 23:13
// EmailService
public boolean checkAuthCode(String email, String authCode) {
	String storedAuthCode = service.getAuthCode(email);
	return storedAuthCode != null && storedAuthCode.equals(authCode);
}

사용자에게 입력받은 인증코드와 DB에 저장된 인증코드가 일치하는지 확인하는 메서드이다.

  • 두 문자열의 데이터가 일치하는지 확인하고 싶을 때엔 == 연산자가 아닌 equals()를 사용한다.
    • == : 객체의 주소를 비교하여 일치 여부를 확인
    • equals() : 객체 내부의 데이터를 비교하여 일치 여부를 확인
// MemberService
public String getAuthCode(String email) {
	return sqlSession.selectOne("memberMapper.getAuthCode", email);
}

DB에서 해당 이메일에 대한 가장 최근의 유효한 인증 코드를 조회하는 메서드이다.

  • selectOne() : 단일 결과를 문자열로 반환, 쿼리 결과가 없으면 null을 반환.
  • selectList() : 쿼리 결과를 List<E>로 반환, 쿼리 결과가 없으면 null이 아닌 빈 리스트를 반환
// MemberMapper.xml
<select id="getAuthCode" parameterType="string" resultType="string">
SELECT AUTHCODE
FROM (
        SELECT AUTHCODE
        FROM AUTHCODE
        WHERE EMAIL = #{email}
        AND EXPIRETIME > SYSDATE
        ORDER BY AUTHCODENO DESC
	)
WHERE ROWNUM = 1
</select>
  • EXPIRETIME > SYSDATE : 특정 이메일에 해당하는 인증 코드 중 만료되지 않은 것들을 조회
  • ORDER BY AUTHCODENO DESC : 가장 최근에 생성된 순서대로 정렬
  • ROWNUM = 1 :가장 최근의 코드 하나만 선택
 // MemberController
    @PostMapping("/checkAuthCode.do")
    @ResponseBody
    public ResponseEntity<Map<String, String>> checkAuthCode(
            @RequestParam String email, 
            @RequestParam String authCode) {
        
        Map<String, String> response = new HashMap<>();
        
        try {
            boolean valid = eService.checkAuthCode(email, authCode);
            if (valid) {
                response.put("status", "success");
                response.put("message", "이메일 인증 성공!");
            } else {
                response.put("status", "error");
                response.put("message", "인증 코드가 일치하지 않습니다.");
            }
            return ResponseEntity.ok(response);
            
        } catch (Exception e) {
            log.error("인증 코드 확인 오류", e);
            response.put("status", "error");
            response.put("message", "서버 오류: " + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
        }
    }

Ajax(비동기 통신) 요청을 처리하는 REST 엔드포인트.

  • REST 엔드포인트 : 새로고침 없이 서버의 특정 URL에 접근하여 데이터를 주고받을 수 있게 해주는 서버 측 접점
  • @ResponseBody : JSON 형태로 응답을 반환
  • ResponseEntity : 응답 데이터와 HTTP 상태 코드를 직접 제어할 수 있는 HttpEntity 상속 클래스
  • email과 authCode를 파라미터로 전달받아 EmailService의 checkAuthCode() 호출
  • 인증 성공/실패에 따라 상태와 메시지를 담은 Map을 JSON으로 반환
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>회원가입</title>

회원 가입 기능을 이용할 수 있는 signUpForm.jsp이다.

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script>
    $(function () {
        // 이메일 유효성 검사
        $("#requestEmailAuthBtn").on("click", function () {
            let email = $("#email").val();
            if (email === "") {
                alert("이메일을 입력해주세요");
                return;
            }
            
            let pattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
            if (!pattern.test(email)) {
                $("#emailCheckMessage").text("올바른 이메일 형식이 아닙니다.").css("color", "red");
                return;
            }

            // 이메일 인증 요청
            $.ajax({
                type: "POST",
                url: "/member/requestEmailAuth.do",
                data: { email: email },
                dataType: "json",
                success: function (response) {
                    if (response.status === "success") {
                        alert(response.message);
                        $("#email").val(email);
                    } else {
                        alert(response.message);
                    }
                },   
                error: function(xhr, status, error) {
                    console.error("에러 상태:", xhr.status);
                    console.error("에러 내용:", xhr.responseText);
                    alert("이메일 인증 요청 중 오류 발생: " + error);
                }
            });
        });

        // 인증 코드 확인
        $("#checkAuthCodeBtn").on("click", function() {
            let email = $("#email").val();
            let authCode = $("#authCode").val();
            if (email === "" || authCode === "") {
                alert("이메일과 인증코드를 모두 입력해주세요");
                return;
            }
    
            $.ajax({
                type: "POST",
                url: "/member/checkAuthCode.do",
                data: { 
                    email: email,
                    authCode: authCode,
                    memberId: $("#memberId").val(),
                    memberName: $("#memberName").val()
                },
                dataType: "json",
                success: function(response) {
                    if (response.status === "success") {
                        alert(response.message);
                        // 인증 성공 후 UI 업데이트
                        $("#authCode").attr("readonly", true);
                        $("#checkAuthCodeBtn").prop("disabled", true);
                    } else {
                        alert(response.message);
                    }
                },
                error: function(xhr, status, error) {
                    console.error("에러 상태:", xhr.status);
                    console.error("에러 내용:", xhr.responseText);
                    alert("인증코드 확인 중 오류 발생: " + error);
                }
            });
        });
    });
</script>
  • $(function() { ... }) : HTML 코드가 모두 load되면 해당 코드를 실행해달라는 의미의 jQuery 함수
  • /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ : 정규 표현식으로, 보편적인 이메일 형식을 의미함.

attr을 이용하여 인증코드 검증이 정상적으로 처리되면 readonly 속성 값으로 변경시켜 데이터를 수정할 수 없도록 조치해두었다.

  • attr : 엘리먼트의 속성 값을 가져오거나 변경할 수 있는 함수
<div class="form-group">
    <label for="email">이메일</label>
    <div class="input-group">
		<input type="text" class="form-control" id="email" name="email" placeholder="이메일 입력"
			pattern="[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" value="${param.email}" required>
	<div class="input-group-append">
		<button class="btn text-white" style="background-color: #FFAFEC" type="button" id="checkEmailBtn">중복 확인</button>
		<button class="btn text-white" style="background-color: #FFAFEC" type="button" id="requestEmailAuthBtn">인증 코드 발송</button>
	</div>
	</div>
	<span id="emailCheckMessage"></span>
</div>
        
<div class="form-group">
	<label for="authCode">인증 코드</label>
	<div class="input-group">
		<input type="text" class="form-control" id="authCode" name="authCode" placeholder="인증 코드 입력" value="${param.authCode}" required>
		<div class="input-group-append">
			<button class="btn text-white" style="background-color: #FFAFEC" type="button" id="checkAuthCodeBtn">인증 확인</button>
		</div>
	</div>
</div>

JavaScript 기능이 실제로 실행되는 곳, 중복 확인 기능은 추후에 다뤄볼 예정이다.

  • required : 필수 입력란으로 지정

 

이제 사용자에게 이메일을 입력받은 후 "인증 코드 발송" 버튼을 클릭하면

  1. 인증코드를 생성한다.
  2. 생성한 인증코드를 입력받은 이메일에 전송한다.
  3. 해당 이메일과 인증코드를 DB에 저장한다.
  4. 사용자에게 인증코드를 입력받아 DB 데이터와 일치하는지 검증한다.

이 일련의 과정을 순차적으로 실행하는 "이메일 인증" 프로그램은 모두 구현되었다.