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

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");
String sessionMobileNumber = (String) session.getAttribute("mobileNumber");
if (isVerified == null || !isVerified) {
setModelForError(redirectAttributes, "resetPassword", null, "휴대폰 번호 인증을 먼저 진행해주세요.", null, passwordReset);
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 {
// 사용자 정보 확인
portalUserAuthService.resetPassword(
@@ -101,11 +101,12 @@ public class PortalUserAuthService implements UserDetailsService {
}
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("일치하는 사용자 정보를 찾을 수 없습니다."));
// String tempPassword = EncryptionUtil.generateNewPasswordForKjbank(loginId);
String tempPassword = EncryptionUtil.generateNewPassword();
portalUser.setPasswordHash(passwordEncoder.encode(tempPassword));
if ("Y".equalsIgnoreCase(portalUser.getAccountLockYn())) {
@@ -1,22 +1,20 @@
<!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/kjbank_base_layout}">
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_title_layout}">
<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">
<div class="account-recovery-page">
<div class="account-recovery-container">
<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 -->
<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>
</div>
@@ -30,7 +28,7 @@
<!-- Name Field -->
<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"
id="userName"
name="userName"
@@ -42,7 +40,7 @@
<!-- Email ID Field -->
<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"
id="loginId"
name="loginId"
@@ -54,7 +52,7 @@
<!-- Phone Number Fields -->
<div class="form-group">
<label class="form-label">휴대폰 번호</label>
<label class="form-label">휴대폰 번호<span class="required">*</span></label>
<div class="phone-input-group">
<select id="phonePrefix" class="form-select phone-prefix" required>
<option value="">선택</option>
@@ -64,37 +62,35 @@
<option value="017">017</option>
<option value="018">018</option>
</select>
<span class="phone-separator">-</span>
<span class="phone-separator"></span>
<input type="tel"
id="phoneMiddle"
name="phoneMiddle"
maxlength="4"
class="form-input phone-middle"
placeholder="0000"
placeholder="1234"
pattern="[0-9]*"
inputmode="numeric"
required>
<span class="phone-separator">-</span>
<span class="phone-separator"></span>
<input type="tel"
id="phoneLast"
name="phoneLast"
maxlength="4"
class="form-input phone-last"
placeholder="0000"
placeholder="1234"
pattern="[0-9]*"
inputmode="numeric"
required>
<button type="button" class="auth-request-button" id="requestAuthButton">
인증번호 받기
</button>
</div>
</div>
<!-- Auth Number Request Button -->
<button type="button" class="auth-request-button" id="requestAuthButton">
인증번호 받기
</button>
<!-- Auth Number Input -->
<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">
<input type="text"
id="authNumber"
@@ -107,20 +103,18 @@
required>
<span class="auth-timer" id="authTimer">03:00</span>
</div>
</div>
<!-- Auth Number Verify 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>
<button type="button" class="auth-verify-button" id="verifyAuthButton">
인증번호 확인
</button>
</div>
</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 -->
<div class="loading-overlay" id="loadingOverlay">
<div class="spinner"></div>
@@ -135,32 +129,20 @@
$(document).ready(function() {
const errorMsg = [[${error}]];
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');
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() {
const prefix = $('#phonePrefix').val();
@@ -188,7 +170,7 @@
if (remainingTime <= 0) {
clearInterval(countdownInterval);
$('#authTimer').text('시간 초과').css('color', '#FF6B6B');
showAlert('입력 시간이 초과되었습니다. 인증번호를 다시 발송해주세요.', 'error');
customPopups.showAlert('입력 시간이 초과되었습니다. 인증번호를 다시 발송해주세요.', 'error');
// 필드 활성화
$('#phonePrefix, #phoneMiddle, #phoneLast').prop('disabled', false);
@@ -210,25 +192,25 @@
$('#requestAuthButton').on('click', function() {
const userName = $('#userName').val().trim();
if (!userName) {
showAlert('성명을 입력해주세요.', 'error');
customPopups.showAlert('성명을 입력해주세요.', 'error');
return;
}
const loginId = $('#loginId').val().trim();
if (!loginId) {
showAlert('이메일 아이디를 입력해주세요.', 'error');
customPopups.showAlert('이메일 아이디를 입력해주세요.', 'error');
return;
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(loginId)) {
showAlert('올바른 이메일 형식을 입력해주세요.', 'error');
customPopups.showAlert('올바른 이메일 형식을 입력해주세요.', 'error');
return;
}
const mobileNumber = combineMobileNumber();
if (!mobileNumber) {
showAlert('휴대폰 번호를 올바르게 입력해주세요.', 'error');
customPopups.showAlert('휴대폰 번호를 올바르게 입력해주세요.', 'error');
return;
}
@@ -246,7 +228,7 @@
$('#loadingOverlay').fadeOut(200);
if (response.valid) {
showAlert(response.message || '인증번호가 발송되었습니다.', 'success');
customPopups.showAlert(response.message || '인증번호가 발송되었습니다.', 'success');
// 휴대폰 번호 필드 비활성화
$('#phonePrefix, #phoneMiddle, #phoneLast').prop('disabled', true);
@@ -259,12 +241,12 @@
isAuthNumberRequested = true;
startAuthTimer();
} else {
showAlert(response.message || '인증번호 발송에 실패했습니다.', 'error');
customPopups.showAlert(response.message || '인증번호 발송에 실패했습니다.', 'error');
}
},
error: function() {
$('#loadingOverlay').fadeOut(200);
showAlert('처리 중 오류가 발생했습니다. 다시 시도해주세요.', 'error');
customPopups.showAlert('처리 중 오류가 발생했습니다. 다시 시도해주세요.', 'error');
}
});
});
@@ -272,14 +254,14 @@
// 인증번호 확인 버튼
$('#verifyAuthButton').on('click', function() {
if (!isAuthNumberRequested) {
showAlert('휴대폰 번호 인증을 먼저 진행해주세요.', 'error');
customPopups.showAlert('휴대폰 번호 인증을 먼저 진행해주세요.', 'error');
return;
}
const authNumber = $('#authNumber').val().trim();
if (authNumber.length !== 6) {
showAlert('인증번호 6자리를 입력해주세요.', 'error');
customPopups.showAlert('인증번호 6자리를 입력해주세요.', 'error');
return;
}
@@ -298,7 +280,7 @@
$('#loadingOverlay').fadeOut(200);
if (response.valid) {
showAlert(response.message || '인증이 완료되었습니다.', 'success');
customPopups.showAlert(response.message || '인증이 완료되었습니다.', 'success');
// 인증번호 입력 필드 비활성화
$('#authNumber').prop('disabled', true);
@@ -309,12 +291,12 @@
isAuthVerified = true;
} else {
showAlert(response.message || '인증번호가 일치하지 않습니다.', 'error');
customPopups.showAlert(response.message || '인증번호가 일치하지 않습니다.', 'error');
}
},
error: function() {
$('#loadingOverlay').fadeOut(200);
showAlert('처리 중 오류가 발생했습니다. 다시 시도해주세요.', 'error');
customPopups.showAlert('처리 중 오류가 발생했습니다. 다시 시도해주세요.', 'error');
}
});
});
@@ -325,35 +307,35 @@
const userName = $('#userName').val().trim();
if (!userName) {
showAlert('성명을 입력해주세요.', 'error');
customPopups.showAlert('성명을 입력해주세요.', 'error');
return;
}
const loginId = $('#loginId').val().trim();
if (!loginId) {
showAlert('이메일 아이디를 입력해주세요.', 'error');
customPopups.showAlert('이메일 아이디를 입력해주세요.', 'error');
return;
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(loginId)) {
showAlert('올바른 이메일 형식을 입력해주세요.', 'error');
customPopups.showAlert('올바른 이메일 형식을 입력해주세요.', 'error');
return;
}
const mobileNumber = combineMobileNumber();
if (!mobileNumber) {
showAlert('휴대폰 번호를 올바르게 입력해주세요.', 'error');
customPopups.showAlert('휴대폰 번호를 올바르게 입력해주세요.', 'error');
return;
}
if (!isAuthNumberRequested) {
showAlert('휴대폰 인증을 먼저 진행해주세요.', 'error');
customPopups.showAlert('휴대폰 인증을 먼저 진행해주세요.', 'error');
return;
}
if (!isAuthVerified) {
showAlert('휴대폰 인증을 완료해주세요.', 'error');
customPopups.showAlert('휴대폰 인증을 완료해주세요.', 'error');
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>