비밀번호 찾기 로직 수정, 디자인 적용

This commit is contained in:
현성필
2025-12-05 16:03:42 +09:00
parent 6c4efa81af
commit 2a9f534f1e
4 changed files with 66 additions and 113 deletions
@@ -113,11 +113,20 @@ public class AccountRecoveryController {
// 세션에서 인증 여부 확인 // 세션에서 인증 여부 확인
Boolean isVerified = (Boolean) session.getAttribute("isVerified"); Boolean isVerified = (Boolean) session.getAttribute("isVerified");
String sessionMobileNumber = (String) session.getAttribute("mobileNumber");
if (isVerified == null || !isVerified) { if (isVerified == null || !isVerified) {
setModelForError(redirectAttributes, "resetPassword", null, "휴대폰 번호 인증을 먼저 진행해주세요.", null, passwordReset); setModelForError(redirectAttributes, "resetPassword", null, "휴대폰 번호 인증을 먼저 진행해주세요.", null, passwordReset);
return "redirect:/account_recovery?tab=resetPassword"; return "redirect:/account_recovery?tab=resetPassword";
} }
// 인증된 전화번호와 입력된 전화번호 일치 여부 확인
if (passwordReset.getMobileNumber() == null ||
!passwordReset.getMobileNumber().replace("-", "").equals(sessionMobileNumber)) {
setModelForError(redirectAttributes, "resetPassword", null, "인증된 휴대폰 번호와 입력한 번호가 일치하지 않습니다.", null, passwordReset);
return "redirect:/account_recovery?tab=resetPassword";
}
try { try {
// 사용자 정보 확인 // 사용자 정보 확인
portalUserAuthService.resetPassword( portalUserAuthService.resetPassword(
@@ -101,11 +101,12 @@ public class PortalUserAuthService implements UserDetailsService {
} }
public void resetPassword(String loginId, String userName, String mobileNumber) { public void resetPassword(String loginId, String userName, String mobileNumber) {
// 전화번호에서 - 제거하여 정규화
String normalizedMobileNumber = mobileNumber != null ? mobileNumber.replace("-", "") : null;
PortalUser portalUser = portalUserRepository.findByLoginIdAndUserNameAndMobileNumber(loginId, userName, mobileNumber) PortalUser portalUser = portalUserRepository.findByLoginIdAndUserNameAndMobileNumber(loginId, userName, normalizedMobileNumber)
.orElseThrow(() -> new UserNotFoundException("일치하는 사용자 정보를 찾을 수 없습니다.")); .orElseThrow(() -> new UserNotFoundException("일치하는 사용자 정보를 찾을 수 없습니다."));
// String tempPassword = EncryptionUtil.generateNewPasswordForKjbank(loginId);
String tempPassword = EncryptionUtil.generateNewPassword(); String tempPassword = EncryptionUtil.generateNewPassword();
portalUser.setPasswordHash(passwordEncoder.encode(tempPassword)); portalUser.setPasswordHash(passwordEncoder.encode(tempPassword));
if ("Y".equalsIgnoreCase(portalUser.getAccountLockYn())) { if ("Y".equalsIgnoreCase(portalUser.getAccountLockYn())) {
@@ -1,22 +1,20 @@
<!doctype html> <!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}"> xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}">
<body> <body>
<section layout:fragment="title">
<div class="page-title-banner">
<img th:src="@{/img/img_title_bg.png}" class="title-image">
<h1>비밀번호 초기화</h1>
</div>
</section>
<th:block layout:fragment="contentFragment"> <th:block layout:fragment="contentFragment">
<div class="account-recovery-page"> <div class="account-recovery-page">
<div class="account-recovery-container"> <div class="account-recovery-container">
<div class="account-recovery-card"> <div class="account-recovery-card">
<!-- Logo -->
<div class="account-recovery-logo">
<img th:src="@{/img/logo/logo_box.png}" alt="광주은행">
</div>
<!-- Header Title -->
<h2 class="account-recovery-title">비밀번호 초기화</h2>
<!-- Tab Navigation --> <!-- Tab Navigation -->
<div class="account-recovery-tabs"> <div class="account-recovery-tabs">
<a th:href="@{/account_recovery(tab='findId')}" class="tab-link">아이디 찾기</a> <a th:href="@{/account_recovery(tab='findId')}" class="tab-link">아이디찾기</a>
<a href="#" class="tab-link active">비밀번호 초기화</a> <a href="#" class="tab-link active">비밀번호 초기화</a>
</div> </div>
@@ -30,7 +28,7 @@
<!-- Name Field --> <!-- Name Field -->
<div class="form-group"> <div class="form-group">
<label for="userName" class="form-label">성명</label> <label for="userName" class="form-label">성명<span class="required">*</span></label>
<input type="text" <input type="text"
id="userName" id="userName"
name="userName" name="userName"
@@ -42,7 +40,7 @@
<!-- Email ID Field --> <!-- Email ID Field -->
<div class="form-group"> <div class="form-group">
<label for="loginId" class="form-label">이메일 아이디</label> <label for="loginId" class="form-label">이메일 아이디<span class="required">*</span></label>
<input type="email" <input type="email"
id="loginId" id="loginId"
name="loginId" name="loginId"
@@ -54,7 +52,7 @@
<!-- Phone Number Fields --> <!-- Phone Number Fields -->
<div class="form-group"> <div class="form-group">
<label class="form-label">휴대폰 번호</label> <label class="form-label">휴대폰 번호<span class="required">*</span></label>
<div class="phone-input-group"> <div class="phone-input-group">
<select id="phonePrefix" class="form-select phone-prefix" required> <select id="phonePrefix" class="form-select phone-prefix" required>
<option value="">선택</option> <option value="">선택</option>
@@ -64,37 +62,35 @@
<option value="017">017</option> <option value="017">017</option>
<option value="018">018</option> <option value="018">018</option>
</select> </select>
<span class="phone-separator">-</span> <span class="phone-separator"></span>
<input type="tel" <input type="tel"
id="phoneMiddle" id="phoneMiddle"
name="phoneMiddle" name="phoneMiddle"
maxlength="4" maxlength="4"
class="form-input phone-middle" class="form-input phone-middle"
placeholder="0000" placeholder="1234"
pattern="[0-9]*" pattern="[0-9]*"
inputmode="numeric" inputmode="numeric"
required> required>
<span class="phone-separator">-</span> <span class="phone-separator"></span>
<input type="tel" <input type="tel"
id="phoneLast" id="phoneLast"
name="phoneLast" name="phoneLast"
maxlength="4" maxlength="4"
class="form-input phone-last" class="form-input phone-last"
placeholder="0000" placeholder="1234"
pattern="[0-9]*" pattern="[0-9]*"
inputmode="numeric" inputmode="numeric"
required> required>
<button type="button" class="auth-request-button" id="requestAuthButton">
인증번호 받기
</button>
</div> </div>
</div> </div>
<!-- Auth Number Request Button -->
<button type="button" class="auth-request-button" id="requestAuthButton">
인증번호 받기
</button>
<!-- Auth Number Input --> <!-- Auth Number Input -->
<div class="form-group auth-number-group" id="authNumberGroup" style="display: none;"> <div class="form-group auth-number-group" id="authNumberGroup" style="display: none;">
<label for="authNumber" class="form-label">인증번호 입력</label> <label for="authNumber" class="form-label">인증번호 입력<span class="required">*</span></label>
<div class="auth-input-group"> <div class="auth-input-group">
<input type="text" <input type="text"
id="authNumber" id="authNumber"
@@ -107,20 +103,18 @@
required> required>
<span class="auth-timer" id="authTimer">03:00</span> <span class="auth-timer" id="authTimer">03:00</span>
</div> </div>
</div> <button type="button" class="auth-verify-button" id="verifyAuthButton">
인증번호 확인
<!-- Auth Number Verify Button --> </button>
<button type="button" class="auth-verify-button" id="verifyAuthButton" style="display: none;">
인증번호 확인
</button>
<!-- Submit Buttons -->
<div class="form-actions">
<button type="button" class="cancel-button" id="cancelButton">취소</button>
<button type="submit" class="submit-button" id="submitButton">비밀번호 초기화</button>
</div> </div>
</form> </form>
<!-- Submit Buttons -->
<div class="form-actions">
<button type="button" class="cancel-button" id="cancelButton">취소</button>
<button type="submit" class="submit-button" id="submitButton" form="accountForm">비밀번호 초기화</button>
</div>
<!-- Loading State --> <!-- Loading State -->
<div class="loading-overlay" id="loadingOverlay"> <div class="loading-overlay" id="loadingOverlay">
<div class="spinner"></div> <div class="spinner"></div>
@@ -135,32 +129,20 @@
$(document).ready(function() { $(document).ready(function() {
const errorMsg = [[${error}]]; const errorMsg = [[${error}]];
if (errorMsg) { if (errorMsg) {
showAlert(errorMsg, 'error'); customPopups.showAlert(errorMsg, 'error');
}
const successMsg = [[${success}]];
if (successMsg) {
customPopups.showAlert(successMsg, 'success');
} }
const paramError = new URL(window.location.href).searchParams.get('error'); const paramError = new URL(window.location.href).searchParams.get('error');
if (paramError) { if (paramError) {
showAlert(decodeURIComponent(paramError), 'error'); customPopups.showAlert(decodeURIComponent(paramError), 'error');
} }
}); });
// Alert 표시 함수
function showAlert(message, type = 'info') {
const alertContainer = $('#alertContainer');
const alertClass = type === 'error' ? 'alert-error' : type === 'success' ? 'alert-success' : 'alert-info';
const iconClass = type === 'error' ? 'fa-exclamation-circle' : type === 'success' ? 'fa-check-circle' : 'fa-info-circle';
const alertHtml = `
<div class="account-alert ${alertClass}">
<i class="fas ${iconClass}"></i>
<span>${message}</span>
</div>
`;
alertContainer.html(alertHtml);
setTimeout(() => alertContainer.empty(), 5000);
}
// 휴대폰 번호 결합 함수 // 휴대폰 번호 결합 함수
function combineMobileNumber() { function combineMobileNumber() {
const prefix = $('#phonePrefix').val(); const prefix = $('#phonePrefix').val();
@@ -188,7 +170,7 @@
if (remainingTime <= 0) { if (remainingTime <= 0) {
clearInterval(countdownInterval); clearInterval(countdownInterval);
$('#authTimer').text('시간 초과').css('color', '#FF6B6B'); $('#authTimer').text('시간 초과').css('color', '#FF6B6B');
showAlert('입력 시간이 초과되었습니다. 인증번호를 다시 발송해주세요.', 'error'); customPopups.showAlert('입력 시간이 초과되었습니다. 인증번호를 다시 발송해주세요.', 'error');
// 필드 활성화 // 필드 활성화
$('#phonePrefix, #phoneMiddle, #phoneLast').prop('disabled', false); $('#phonePrefix, #phoneMiddle, #phoneLast').prop('disabled', false);
@@ -210,25 +192,25 @@
$('#requestAuthButton').on('click', function() { $('#requestAuthButton').on('click', function() {
const userName = $('#userName').val().trim(); const userName = $('#userName').val().trim();
if (!userName) { if (!userName) {
showAlert('성명을 입력해주세요.', 'error'); customPopups.showAlert('성명을 입력해주세요.', 'error');
return; return;
} }
const loginId = $('#loginId').val().trim(); const loginId = $('#loginId').val().trim();
if (!loginId) { if (!loginId) {
showAlert('이메일 아이디를 입력해주세요.', 'error'); customPopups.showAlert('이메일 아이디를 입력해주세요.', 'error');
return; return;
} }
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(loginId)) { if (!emailRegex.test(loginId)) {
showAlert('올바른 이메일 형식을 입력해주세요.', 'error'); customPopups.showAlert('올바른 이메일 형식을 입력해주세요.', 'error');
return; return;
} }
const mobileNumber = combineMobileNumber(); const mobileNumber = combineMobileNumber();
if (!mobileNumber) { if (!mobileNumber) {
showAlert('휴대폰 번호를 올바르게 입력해주세요.', 'error'); customPopups.showAlert('휴대폰 번호를 올바르게 입력해주세요.', 'error');
return; return;
} }
@@ -246,7 +228,7 @@
$('#loadingOverlay').fadeOut(200); $('#loadingOverlay').fadeOut(200);
if (response.valid) { if (response.valid) {
showAlert(response.message || '인증번호가 발송되었습니다.', 'success'); customPopups.showAlert(response.message || '인증번호가 발송되었습니다.', 'success');
// 휴대폰 번호 필드 비활성화 // 휴대폰 번호 필드 비활성화
$('#phonePrefix, #phoneMiddle, #phoneLast').prop('disabled', true); $('#phonePrefix, #phoneMiddle, #phoneLast').prop('disabled', true);
@@ -259,12 +241,12 @@
isAuthNumberRequested = true; isAuthNumberRequested = true;
startAuthTimer(); startAuthTimer();
} else { } else {
showAlert(response.message || '인증번호 발송에 실패했습니다.', 'error'); customPopups.showAlert(response.message || '인증번호 발송에 실패했습니다.', 'error');
} }
}, },
error: function() { error: function() {
$('#loadingOverlay').fadeOut(200); $('#loadingOverlay').fadeOut(200);
showAlert('처리 중 오류가 발생했습니다. 다시 시도해주세요.', 'error'); customPopups.showAlert('처리 중 오류가 발생했습니다. 다시 시도해주세요.', 'error');
} }
}); });
}); });
@@ -272,14 +254,14 @@
// 인증번호 확인 버튼 // 인증번호 확인 버튼
$('#verifyAuthButton').on('click', function() { $('#verifyAuthButton').on('click', function() {
if (!isAuthNumberRequested) { if (!isAuthNumberRequested) {
showAlert('휴대폰 번호 인증을 먼저 진행해주세요.', 'error'); customPopups.showAlert('휴대폰 번호 인증을 먼저 진행해주세요.', 'error');
return; return;
} }
const authNumber = $('#authNumber').val().trim(); const authNumber = $('#authNumber').val().trim();
if (authNumber.length !== 6) { if (authNumber.length !== 6) {
showAlert('인증번호 6자리를 입력해주세요.', 'error'); customPopups.showAlert('인증번호 6자리를 입력해주세요.', 'error');
return; return;
} }
@@ -298,7 +280,7 @@
$('#loadingOverlay').fadeOut(200); $('#loadingOverlay').fadeOut(200);
if (response.valid) { if (response.valid) {
showAlert(response.message || '인증이 완료되었습니다.', 'success'); customPopups.showAlert(response.message || '인증이 완료되었습니다.', 'success');
// 인증번호 입력 필드 비활성화 // 인증번호 입력 필드 비활성화
$('#authNumber').prop('disabled', true); $('#authNumber').prop('disabled', true);
@@ -309,12 +291,12 @@
isAuthVerified = true; isAuthVerified = true;
} else { } else {
showAlert(response.message || '인증번호가 일치하지 않습니다.', 'error'); customPopups.showAlert(response.message || '인증번호가 일치하지 않습니다.', 'error');
} }
}, },
error: function() { error: function() {
$('#loadingOverlay').fadeOut(200); $('#loadingOverlay').fadeOut(200);
showAlert('처리 중 오류가 발생했습니다. 다시 시도해주세요.', 'error'); customPopups.showAlert('처리 중 오류가 발생했습니다. 다시 시도해주세요.', 'error');
} }
}); });
}); });
@@ -325,35 +307,35 @@
const userName = $('#userName').val().trim(); const userName = $('#userName').val().trim();
if (!userName) { if (!userName) {
showAlert('성명을 입력해주세요.', 'error'); customPopups.showAlert('성명을 입력해주세요.', 'error');
return; return;
} }
const loginId = $('#loginId').val().trim(); const loginId = $('#loginId').val().trim();
if (!loginId) { if (!loginId) {
showAlert('이메일 아이디를 입력해주세요.', 'error'); customPopups.showAlert('이메일 아이디를 입력해주세요.', 'error');
return; return;
} }
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(loginId)) { if (!emailRegex.test(loginId)) {
showAlert('올바른 이메일 형식을 입력해주세요.', 'error'); customPopups.showAlert('올바른 이메일 형식을 입력해주세요.', 'error');
return; return;
} }
const mobileNumber = combineMobileNumber(); const mobileNumber = combineMobileNumber();
if (!mobileNumber) { if (!mobileNumber) {
showAlert('휴대폰 번호를 올바르게 입력해주세요.', 'error'); customPopups.showAlert('휴대폰 번호를 올바르게 입력해주세요.', 'error');
return; return;
} }
if (!isAuthNumberRequested) { if (!isAuthNumberRequested) {
showAlert('휴대폰 인증을 먼저 진행해주세요.', 'error'); customPopups.showAlert('휴대폰 인증을 먼저 진행해주세요.', 'error');
return; return;
} }
if (!isAuthVerified) { if (!isAuthVerified) {
showAlert('휴대폰 인증을 완료해주세요.', 'error'); customPopups.showAlert('휴대폰 인증을 완료해주세요.', 'error');
return; return;
} }
@@ -1,39 +0,0 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_base_layout}">
<body>
<section layout:fragment="contentFragment" class="content">
<div class="content_wrap">
<!-- 타이틀 -->
<div class="sub_title2">
<h2 class="title add5">비밀번호 초기화</h2>
</div>
<div class="inner i_cs10 h_inner2">
<!-- 탭 메뉴 -->
<div class="tab-wrap">
<div class="tabs">
<ul class="tab_nav tabm">
<li><a href="#tab1">아이디 찾기</a></li>
<li class="active"><a href="#tab2">비밀번호 초기화</a></li>
</ul>
<div class="tab active" id="tab2">
<div class="form_type">
<div class="result_view">
<p class="title" th:tex="${successMessage}"></p>
</div>
<div class="btn_application btn_top3">
<a href="/login" class="common_btn_type_1"><span>로그인</span></a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</body>
<th:block layout:fragment="contentScript">
</th:block>
</html>