Implement SMS two-factor authentication with AJAX support

This commit is contained in:
Rinjae
2026-01-15 11:29:58 +09:00
parent b4e149251f
commit c4ae6ce4e8
7 changed files with 967 additions and 17 deletions
+171 -11
View File
@@ -43,13 +43,6 @@
}
%>
<script src="js/jquery-1.12.1.min.js"></script>
<!-- <script src="js/popper.js"></script> -->
<script src="js/bootstrap.min.js"></script>
<link rel="stylesheet" href="css/login-style.css">
<link rel="stylesheet" href="css/login-fontawesome_all.css">
<html>
<head>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
@@ -59,13 +52,20 @@
href="<c:url value="/img/ic_eai_favi_${themeColor}.png"/>"
type="image/ico" sizes="16x16" />
<title><%=systemModeTitle%><%=localeMessage.getString("screen.title")%></title>
<%-- <jsp:include page="/jsp/common/include/css.jsp"/> --%>
<jsp:include page="/jsp/common/include/script.jsp"/>
<link rel="stylesheet" href="<c:url value="/css/login-style.css"/>">
<link rel="stylesheet" href="<c:url value="/css/login-fontawesome_all.css"/>">
<link rel="stylesheet" href="<c:url value="/addon/bootstrap-4.6.2/css/bootstrap.min.css"/>">
<script src="<c:url value="/js/jquery-1.12.1.min.js"/>"></script>
<script src="<c:url value="/addon/bootstrap-4.6.2/js/bootstrap.bundle.min.js"/>"></script>
<script type="text/javascript">
if( self.name == 'mainFrame' || self.name == 'leftFrame' || self.name == 'topFrame') {
parent.window.location.href='<c:url value="/"/>';
}
// SMS 인증 관련 변수
var smsAuthTimer = null;
var resendTimer = null;
function fncPreMain() {
if ($("input[name=userId]").val() ==null || $("input[name=userId]").val().length == 0 ){
alert("<%= localeMessage.getString("login.checkid") %>");
@@ -83,7 +83,140 @@
return;
}
sessionStorage["serviceType"]= $("select[name=serviceType]").val();
$("#loginForm").submit();
// AJAX 로그인 요청
$.ajax({
url: '<c:url value="/login-ajax.do"/>',
type: 'POST',
data: $('#loginForm').serialize(),
dataType: 'json',
success: function(response) {
if (response.smsAuthRequired) {
// SMS 인증 필요
showSmsAuthModal(response);
} else if (response.success) {
// 직접 로그인 성공
window.location.href = response.redirectUrl;
} else {
// 로그인 실패
alert(response.errorMessage || '로그인에 실패했습니다.');
}
},
error: function(xhr, status, error) {
console.error('Login error:', error);
alert('로그인 처리 중 오류가 발생했습니다.');
}
});
}
// SMS 인증 모달 표시
function showSmsAuthModal(data) {
$('#smsAuthMaskedPhone').text(data.maskedPhone);
$('#smsAuthCode').val('');
startExpiryTimer(data.expiresAt);
startResendTimer(data.resendableAt);
$('#smsAuthModal').modal('show');
}
// 유효시간 타이머
function startExpiryTimer(expiresAt) {
clearInterval(smsAuthTimer);
updateRemainingTime(expiresAt);
smsAuthTimer = setInterval(function() {
var remaining = Math.max(0, Math.floor((expiresAt - Date.now()) / 1000));
$('#smsAuthRemainingTime').text(remaining);
if (remaining <= 0) {
clearInterval(smsAuthTimer);
alert('인증 시간이 만료되었습니다. 다시 시도해주세요.');
$('#smsAuthModal').modal('hide');
}
}, 1000);
}
function updateRemainingTime(expiresAt) {
var remaining = Math.max(0, Math.floor((expiresAt - Date.now()) / 1000));
$('#smsAuthRemainingTime').text(remaining);
}
// 재발송 타이머
function startResendTimer(resendableAt) {
$('#btnSmsResend').prop('disabled', true);
clearInterval(resendTimer);
updateResendButtonText(resendableAt);
resendTimer = setInterval(function() {
var remaining = Math.max(0, Math.ceil((resendableAt - Date.now()) / 1000));
if (remaining <= 0) {
$('#btnSmsResend').prop('disabled', false).text('재발송');
clearInterval(resendTimer);
} else {
$('#btnSmsResend').text('재발송 (' + remaining + '초)');
}
}, 1000);
}
function updateResendButtonText(resendableAt) {
var remaining = Math.max(0, Math.ceil((resendableAt - Date.now()) / 1000));
if (remaining > 0) {
$('#btnSmsResend').text('재발송 (' + remaining + '초)');
} else {
$('#btnSmsResend').text('재발송');
}
}
// SMS 인증번호 재발송
function resendSmsAuthCode() {
$.ajax({
url: '<c:url value="/sms-auth/resend.json"/>',
type: 'POST',
dataType: 'json',
success: function(data) {
if (data.sent) {
startExpiryTimer(data.expiresAt);
startResendTimer(data.resendableAt);
alert('인증번호가 재발송되었습니다.');
} else {
alert(data.errorMessage || '재발송에 실패했습니다.');
}
},
error: function() {
alert('재발송 요청 중 오류가 발생했습니다.');
}
});
}
// SMS 인증번호 확인
function verifySmsAuthCode() {
var code = $('#smsAuthCode').val();
if (!code || code.length !== 6) {
alert('6자리 인증번호를 입력해주세요.');
$('#smsAuthCode').focus();
return;
}
$.ajax({
url: '<c:url value="/sms-auth/verify.json"/>',
type: 'POST',
data: { authCode: code },
dataType: 'json',
success: function(data) {
if (data.success) {
$('#smsAuthModal').modal('hide');
window.location.href = data.redirectUrl;
} else {
alert(data.errorMessage || '인증에 실패했습니다.');
}
},
error: function() {
alert('인증 요청 중 오류가 발생했습니다.');
}
});
}
// SMS 인증 취소
function cancelSmsAuth() {
clearInterval(smsAuthTimer);
clearInterval(resendTimer);
$('#smsAuthModal').modal('hide');
}
function changePwd(){
if ($("input[name=resetUserId]").val() == null || $("input[name=resetUserId]").val().length == 0 ){
@@ -212,11 +345,13 @@
<div style="text-align: right !important;" class="form-group">
<a style="font-size: 12px !important;" href="#pwdChgModal" data-toggle="modal" >Password Change</a>
</div>
<!-- SSO 로그인 버튼 -->
<!-- SSO 로그인 버튼 (비상모드 시 숨김) -->
<c:if test="${not emergencyMode}">
<div class="form-group">
<input type="button" id="btn_sso_login" class="form-control btn btn-success rounded submit px-3"
value="SSO Login">
</div>
</c:if>
<p style="color:red;"><%=resultMsg %></p>
</form>
@@ -258,6 +393,31 @@
</div>
</div>
</div>
<!-- SMS 인증 모달 -->
<div class="modal fade" id="smsAuthModal" tabindex="-1" role="dialog" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">SMS 인증</h5>
</div>
<div class="modal-body">
<p>인증번호가 <strong><span id="smsAuthMaskedPhone"></span></strong>로 발송되었습니다.</p>
<div class="form-group">
<input type="text" id="smsAuthCode" class="form-control"
placeholder="인증번호 6자리" maxlength="6"
onkeypress="return event.charCode >= 48 && event.charCode <= 57">
</div>
<p class="text-muted">남은 시간: <span id="smsAuthRemainingTime" class="text-danger font-weight-bold">60</span>초</p>
</div>
<div class="modal-footer">
<button type="button" id="btnSmsResend" class="btn btn-secondary" onclick="resendSmsAuthCode()" disabled>재발송</button>
<button type="button" class="btn btn-primary" onclick="verifySmsAuthCode()">확인</button>
<button type="button" class="btn btn-danger" onclick="cancelSmsAuth()">취소</button>
</div>
</div>
</div>
</div>
</body>
</html>