취약점 점검 수정
This commit is contained in:
@@ -175,6 +175,6 @@
|
|||||||
</jsp-config>
|
</jsp-config>
|
||||||
|
|
||||||
<session-config>
|
<session-config>
|
||||||
<session-timeout>60</session-timeout>
|
<session-timeout>10</session-timeout>
|
||||||
</session-config>
|
</session-config>
|
||||||
</web-app>
|
</web-app>
|
||||||
@@ -145,6 +145,6 @@
|
|||||||
</jsp-config>
|
</jsp-config>
|
||||||
|
|
||||||
<session-config>
|
<session-config>
|
||||||
<session-timeout>60</session-timeout>
|
<session-timeout>10</session-timeout>
|
||||||
</session-config>
|
</session-config>
|
||||||
</web-app>
|
</web-app>
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
window.location.href = response.redirectUrl;
|
window.location.href = response.redirectUrl;
|
||||||
} else {
|
} else {
|
||||||
// 로그인 실패
|
// 로그인 실패
|
||||||
alert(response.errorMessage || '로그인에 실패했습니다.');
|
alert('로그인에 실패했습니다');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: function(xhr, status, error) {
|
error: function(xhr, status, error) {
|
||||||
@@ -262,7 +262,36 @@
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 비밀번호 변경 요청 (AJAX) - 서버 룰 검증 실패 시 모달을 닫지 않고 비밀번호 입력값만 초기화
|
||||||
|
function submitChangePassword() {
|
||||||
|
if (!checkPwd()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '<c:url value="/changePassword.do"/>',
|
||||||
|
type: 'POST',
|
||||||
|
data: $('#modalLoginForm').serialize(),
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(response) {
|
||||||
|
alert(response.message);
|
||||||
|
if (response.success) {
|
||||||
|
window.location.href = response.redirectUrl || '<c:url value="/emergency.jsp"/>';
|
||||||
|
} else {
|
||||||
|
$("input[name=changePassword]").val('');
|
||||||
|
$("input[name=confirmPassword]").val('');
|
||||||
|
$("input[name=changePassword]").trigger("focus");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
alert("<%= localeMessage.getString("login.pwdchangefail") %>");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$("input[name=userId]").keydown(function(event){
|
$("input[name=userId]").keydown(function(event){
|
||||||
if ( event.which == 13 ) {
|
if ( event.which == 13 ) {
|
||||||
@@ -276,12 +305,12 @@
|
|||||||
});
|
});
|
||||||
$("input[name=changePassword]").keydown(function(event){
|
$("input[name=changePassword]").keydown(function(event){
|
||||||
if ( event.which == 13 ) {
|
if ( event.which == 13 ) {
|
||||||
$('#modalLoginForm').submit();
|
submitChangePassword();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$("input[name=confirmPassword]").keydown(function(event){
|
$("input[name=confirmPassword]").keydown(function(event){
|
||||||
if ( event.which == 13 ) {
|
if ( event.which == 13 ) {
|
||||||
$('#modalLoginForm').submit();
|
submitChangePassword();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Password Change 링크로 직접 열 때만 초기화 (JS에서 modal('show') 호출 시 relatedTarget 없음)
|
// Password Change 링크로 직접 열 때만 초기화 (JS에서 modal('show') 호출 시 relatedTarget 없음)
|
||||||
@@ -387,7 +416,7 @@
|
|||||||
<span aria-hidden="true">X</span>
|
<span aria-hidden="true">X</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form id="modalLoginForm" action="<c:url value="/changePassword.do"/>" method="post" onsubmit="return checkPwd()" novalidate>
|
<form id="modalLoginForm" action="<c:url value="/changePassword.do"/>" method="post" onsubmit="return submitChangePassword()" novalidate>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div id="changeInitPassword">
|
<div id="changeInitPassword">
|
||||||
<div class="form-group d-flex">
|
<div class="form-group d-flex">
|
||||||
@@ -405,7 +434,8 @@
|
|||||||
<input type="password" name="confirmPassword" class="form-control rounded-left" placeholder="<%= localeMessage.getString("login.placeholderConfirmationPassword") %>" autocomplete="off" required>
|
<input type="password" name="confirmPassword" class="form-control rounded-left" placeholder="<%= localeMessage.getString("login.placeholderConfirmationPassword") %>" autocomplete="off" required>
|
||||||
</div>
|
</div>
|
||||||
<span style="font-size:0.8em; color:red">
|
<span style="font-size:0.8em; color:red">
|
||||||
※ 7글자 이상 & 영문/숫자/특수문자 2종류 이상
|
※ 7글자 이상 & 영문/숫자/특수문자 2종류 이상<br/>
|
||||||
|
※ 연속된 숫자/문자(예: 123, abc, qwer), 동일 문자 3자 이상 반복 사용 불가
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
|||||||
@@ -269,7 +269,10 @@ public interface MonitoringContext {
|
|||||||
|
|
||||||
// 최대 로그인 실패 허용횟수 (0이면 계정 잠금 미적용)
|
// 최대 로그인 실패 허용횟수 (0이면 계정 잠금 미적용)
|
||||||
public static final String RMS_PASSWORD_FAIL_COUNT = "rms.password.fail.count";
|
public static final String RMS_PASSWORD_FAIL_COUNT = "rms.password.fail.count";
|
||||||
|
|
||||||
|
// 비밀번호 변경 시 재사용을 금지할 최근 이력 개수
|
||||||
|
public static final String RMS_PASSWORD_HISTORY_COUNT = "rms.password.history.count";
|
||||||
|
|
||||||
// API 상태변화 스윙챗 발송여부
|
// API 상태변화 스윙챗 발송여부
|
||||||
public static final String DJB_UMS_MESSENGER_APIMONITOR_ENABLED = "djb.ums.messenger.api-monitor.enabled";
|
public static final String DJB_UMS_MESSENGER_APIMONITOR_ENABLED = "djb.ums.messenger.api-monitor.enabled";
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.eactive.eai.rms.common.login;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ChangePasswordResponseDto {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 비밀번호 변경 성공 여부
|
||||||
|
*/
|
||||||
|
private boolean success;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사용자에게 alert로 표시할 메시지
|
||||||
|
*/
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 성공 시 이동할 URL
|
||||||
|
*/
|
||||||
|
private String redirectUrl;
|
||||||
|
}
|
||||||
@@ -47,6 +47,7 @@ import com.eactive.apim.portal.user.entity.UserInfo;
|
|||||||
import com.eactive.eai.rms.data.entity.man.user.UserInfoService;
|
import com.eactive.eai.rms.data.entity.man.user.UserInfoService;
|
||||||
import com.eactive.eai.rms.data.entity.man.user.UserLoginHistory;
|
import com.eactive.eai.rms.data.entity.man.user.UserLoginHistory;
|
||||||
import com.eactive.eai.rms.data.entity.man.user.UserLoginHistoryService;
|
import com.eactive.eai.rms.data.entity.man.user.UserLoginHistoryService;
|
||||||
|
import com.eactive.eai.rms.data.entity.man.user.AdminUserPasswordHistoryService;
|
||||||
import com.eactive.eai.rms.data.entity.man.user.UserRole;
|
import com.eactive.eai.rms.data.entity.man.user.UserRole;
|
||||||
import com.eactive.eai.rms.data.entity.man.user.UserRoleService;
|
import com.eactive.eai.rms.data.entity.man.user.UserRoleService;
|
||||||
import com.eactive.eai.rms.data.entity.man.user.UserServiceTypeService;
|
import com.eactive.eai.rms.data.entity.man.user.UserServiceTypeService;
|
||||||
@@ -62,10 +63,10 @@ public class MainController implements InterceptorSkipController {
|
|||||||
private static final String USER_NOT_FOUND_KEY_2 = "login.useridnotfound2";
|
private static final String USER_NOT_FOUND_KEY_2 = "login.useridnotfound2";
|
||||||
|
|
||||||
private static final String REDIRECT_LOGIN_URL = "redirect:/emergency.jsp";
|
private static final String REDIRECT_LOGIN_URL = "redirect:/emergency.jsp";
|
||||||
private static final String REDIRECT_CHANGE_PASSWORD_URL = "redirect:/emergency.jsp";
|
|
||||||
|
|
||||||
private static final String USER_STATUS_LOCKED = "2";
|
private static final String USER_STATUS_LOCKED = "2";
|
||||||
private static final int DEFAULT_MAX_LOGIN_FAIL_COUNT = 5;
|
private static final int DEFAULT_MAX_LOGIN_FAIL_COUNT = 5;
|
||||||
|
private static final int DEFAULT_PASSWORD_HISTORY_COUNT = 5;
|
||||||
|
|
||||||
private final LocaleMessage localeMessage;
|
private final LocaleMessage localeMessage;
|
||||||
private final MonitoringContext monitoringContext;
|
private final MonitoringContext monitoringContext;
|
||||||
@@ -76,6 +77,7 @@ public class MainController implements InterceptorSkipController {
|
|||||||
private final LoginService loginService;
|
private final LoginService loginService;
|
||||||
private final MonitoringPropertyService monitoringPropertyService;
|
private final MonitoringPropertyService monitoringPropertyService;
|
||||||
private final UserLoginHistoryService userLoginHistoryService;
|
private final UserLoginHistoryService userLoginHistoryService;
|
||||||
|
private final AdminUserPasswordHistoryService userPasswordHistoryService;
|
||||||
private final SmsAuthService smsAuthService;
|
private final SmsAuthService smsAuthService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -87,6 +89,7 @@ public class MainController implements InterceptorSkipController {
|
|||||||
UserServiceTypeService userServiceTypeService,
|
UserServiceTypeService userServiceTypeService,
|
||||||
MonitoringPropertyService monitoringPropertyService,
|
MonitoringPropertyService monitoringPropertyService,
|
||||||
UserLoginHistoryService userLoginHistoryService,
|
UserLoginHistoryService userLoginHistoryService,
|
||||||
|
AdminUserPasswordHistoryService userPasswordHistoryService,
|
||||||
SmsAuthService smsAuthService) {
|
SmsAuthService smsAuthService) {
|
||||||
this.localeMessage = localeMessage;
|
this.localeMessage = localeMessage;
|
||||||
this.monitoringContext = monitoringContext;
|
this.monitoringContext = monitoringContext;
|
||||||
@@ -96,6 +99,7 @@ public class MainController implements InterceptorSkipController {
|
|||||||
this.userServiceTypeService = userServiceTypeService;
|
this.userServiceTypeService = userServiceTypeService;
|
||||||
this.monitoringPropertyService = monitoringPropertyService;
|
this.monitoringPropertyService = monitoringPropertyService;
|
||||||
this.userLoginHistoryService = userLoginHistoryService;
|
this.userLoginHistoryService = userLoginHistoryService;
|
||||||
|
this.userPasswordHistoryService = userPasswordHistoryService;
|
||||||
this.smsAuthService = smsAuthService;
|
this.smsAuthService = smsAuthService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,6 +457,12 @@ public class MainController implements InterceptorSkipController {
|
|||||||
MonitoringContext.RMS_PASSWORD_FAIL_COUNT, DEFAULT_MAX_LOGIN_FAIL_COUNT);
|
MonitoringContext.RMS_PASSWORD_FAIL_COUNT, DEFAULT_MAX_LOGIN_FAIL_COUNT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 비밀번호 재사용 금지 대상 최근 이력 개수 조회 (rms.password.history.count)
|
||||||
|
private int getPasswordHistoryCount() {
|
||||||
|
return monitoringContext.getIntProperty(
|
||||||
|
MonitoringContext.RMS_PASSWORD_HISTORY_COUNT, DEFAULT_PASSWORD_HISTORY_COUNT);
|
||||||
|
}
|
||||||
|
|
||||||
// 로그인 실패 횟수 증가, 임계치 도달 시 계정 잠금
|
// 로그인 실패 횟수 증가, 임계치 도달 시 계정 잠금
|
||||||
private int increaseLoginFailCount(UserInfo userInfo) {
|
private int increaseLoginFailCount(UserInfo userInfo) {
|
||||||
int failCount = (userInfo.getLoginfailcount() == null ? 0 : userInfo.getLoginfailcount()) + 1;
|
int failCount = (userInfo.getLoginfailcount() == null ? 0 : userInfo.getLoginfailcount()) + 1;
|
||||||
@@ -824,7 +834,8 @@ public class MainController implements InterceptorSkipController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping("/changePassword.do")
|
@RequestMapping("/changePassword.do")
|
||||||
public String changePassword(HttpServletRequest request,
|
@ResponseBody
|
||||||
|
public ChangePasswordResponseDto changePassword(HttpServletRequest request,
|
||||||
String changePassword, String confirmPassword,
|
String changePassword, String confirmPassword,
|
||||||
String resetUserId, String resetPassword) {
|
String resetUserId, String resetPassword) {
|
||||||
HttpSession session = request.getSession();
|
HttpSession session = request.getSession();
|
||||||
@@ -842,9 +853,10 @@ public class MainController implements InterceptorSkipController {
|
|||||||
UserAccessLogger.log(request, LOG_CATEGORY_LOGIN, "F",
|
UserAccessLogger.log(request, LOG_CATEGORY_LOGIN, "F",
|
||||||
localeMessage
|
localeMessage
|
||||||
.getString(USER_NOT_FOUND_KEY_1));
|
.getString(USER_NOT_FOUND_KEY_1));
|
||||||
session.setAttribute(RESULT_MSG, localeMessage
|
return ChangePasswordResponseDto.builder()
|
||||||
.getString(USER_NOT_FOUND_KEY_1));
|
.success(false)
|
||||||
return REDIRECT_LOGIN_URL;
|
.message(toAlertMessage(localeMessage.getString(USER_NOT_FOUND_KEY_1)))
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
UserInfo userInfo = userInfoOptional.get();
|
UserInfo userInfo = userInfoOptional.get();
|
||||||
@@ -854,9 +866,10 @@ public class MainController implements InterceptorSkipController {
|
|||||||
UserAccessLogger.log(request, LOG_CATEGORY_LOGIN, "F",
|
UserAccessLogger.log(request, LOG_CATEGORY_LOGIN, "F",
|
||||||
localeMessage
|
localeMessage
|
||||||
.getString("login.pwdmismatch1"));
|
.getString("login.pwdmismatch1"));
|
||||||
session.setAttribute(RESULT_MSG, localeMessage
|
return ChangePasswordResponseDto.builder()
|
||||||
.getString("login.pwdmismatch2"));
|
.success(false)
|
||||||
return REDIRECT_CHANGE_PASSWORD_URL;
|
.message(toAlertMessage(localeMessage.getString("login.pwdmismatch2")))
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 비밀번호 Validation
|
// 2. 비밀번호 Validation
|
||||||
@@ -864,48 +877,80 @@ public class MainController implements InterceptorSkipController {
|
|||||||
UserAccessLogger.log(request, LOG_CATEGORY_LOGIN, "F",
|
UserAccessLogger.log(request, LOG_CATEGORY_LOGIN, "F",
|
||||||
localeMessage
|
localeMessage
|
||||||
.getString("login.pwdmismatch3"));
|
.getString("login.pwdmismatch3"));
|
||||||
session.setAttribute(RESULT_MSG, localeMessage
|
return ChangePasswordResponseDto.builder()
|
||||||
.getString("login.pwdmismatch4"));
|
.success(false)
|
||||||
return REDIRECT_CHANGE_PASSWORD_URL;
|
.message(toAlertMessage(localeMessage.getString("login.pwdmismatch4")))
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 비밀번호 체크
|
// 3. 비밀번호 체크
|
||||||
if (!userInfo.getPassword().equals(DamoManager.getInstance().hash(DamoManager.SHA256,resetPassword))
|
if (!userInfo.getPassword().equals(DamoManager.getInstance().hash(DamoManager.SHA256,resetPassword))
|
||||||
|| !userInfo.getUserid().equals(resetUserId)) {
|
|| !userInfo.getUserid().equals(resetUserId)) {
|
||||||
UserAccessLogger.log(request, LOG_CATEGORY_LOGIN, "F",
|
UserAccessLogger.log(request, LOG_CATEGORY_LOGIN, "F",
|
||||||
localeMessage
|
localeMessage
|
||||||
.getString("login.pwdmismatch5"));
|
.getString("login.pwdmismatch5"));
|
||||||
session.setAttribute(RESULT_MSG, localeMessage
|
return ChangePasswordResponseDto.builder()
|
||||||
.getString("login.pwdmismatch6"));
|
.success(false)
|
||||||
return REDIRECT_CHANGE_PASSWORD_URL;
|
.message(toAlertMessage(localeMessage.getString("login.pwdmismatch6")))
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 비밀번호 변경
|
// 4. 최근 사용한 비밀번호 재사용 금지
|
||||||
userInfo.setPassword(DamoManager.getInstance().hash(DamoManager.SHA256,changePassword));
|
String newPasswordHash = DamoManager.getInstance().hash(DamoManager.SHA256, changePassword);
|
||||||
|
if (userPasswordHistoryService.isPasswordReused(resetUserId, newPasswordHash, getPasswordHistoryCount())) {
|
||||||
|
UserAccessLogger.log(request, LOG_CATEGORY_LOGIN, "F",
|
||||||
|
localeMessage
|
||||||
|
.getString("login.pwdreused1"));
|
||||||
|
return ChangePasswordResponseDto.builder()
|
||||||
|
.success(false)
|
||||||
|
.message(toAlertMessage(localeMessage.getString("login.pwdreused2")))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 비밀번호 변경
|
||||||
|
userInfo.setPassword(newPasswordHash);
|
||||||
userInfoService.save(userInfo);
|
userInfoService.save(userInfo);
|
||||||
|
|
||||||
|
// 6. 비밀번호 변경 이력 저장 (재사용 금지 검증용)
|
||||||
|
userPasswordHistoryService.recordPasswordChange(resetUserId, newPasswordHash);
|
||||||
|
|
||||||
UserAccessLogger.log(request, LOG_CATEGORY_LOGIN, "S", localeMessage
|
UserAccessLogger.log(request, LOG_CATEGORY_LOGIN, "S", localeMessage
|
||||||
.getString("login.pwdchanged"));
|
.getString("login.pwdchanged"));
|
||||||
session.setAttribute(RESULT_MSG, localeMessage
|
|
||||||
.getString("login.pwdchanged"));
|
return ChangePasswordResponseDto.builder()
|
||||||
logger.debug("\n\n\n\n===================" + localeMessage
|
.success(true)
|
||||||
.getString("login.pwdchanged"));
|
.message(toAlertMessage(localeMessage.getString("login.pwdchanged")))
|
||||||
return REDIRECT_LOGIN_URL;
|
.redirectUrl(request.getContextPath() + "/emergency.jsp")
|
||||||
|
.build();
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("changePassword Error ", e);
|
logger.error("changePassword Error ", e);
|
||||||
return "redirect:/common/errors/errorLogon.jsp";
|
return ChangePasswordResponseDto.builder()
|
||||||
|
.success(false)
|
||||||
|
.message(toAlertMessage(localeMessage.getString("login.pwdchangefail")))
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final int PASSWORD_PATTERN_CHECK_LENGTH = 3;
|
||||||
|
|
||||||
|
private static final String[] KEYBOARD_SEQUENTIAL_ROWS = {
|
||||||
|
"1234567890",
|
||||||
|
"!@#$%^&*()",
|
||||||
|
"qwertyuiop",
|
||||||
|
"asdfghjkl",
|
||||||
|
"zxcvbnm"
|
||||||
|
};
|
||||||
|
|
||||||
private boolean isPasswordValid(String password) {
|
private boolean isPasswordValid(String password) {
|
||||||
|
|
||||||
return password != null && password.getBytes().length == password
|
return password != null && password.getBytes().length == password
|
||||||
.length() && password.getBytes().length >= 7
|
.length() && password.getBytes().length >= 7
|
||||||
&& isCombination(password)
|
&& isCombination(password)
|
||||||
&& !isRepeatPassword(password, 5)
|
&& !isRepeatPassword(password, PASSWORD_PATTERN_CHECK_LENGTH)
|
||||||
&& !isSerialPassword(password, 5);
|
&& !isSerialPassword(password, PASSWORD_PATTERN_CHECK_LENGTH)
|
||||||
|
&& !isKeyboardSequentialPassword(password, PASSWORD_PATTERN_CHECK_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isRepeatPassword(String password, int count) {
|
private boolean isRepeatPassword(String password, int count) {
|
||||||
@@ -930,21 +975,41 @@ public class MainController implements InterceptorSkipController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isSerialPassword(String password, int count) {
|
private boolean isSerialPassword(String password, int count) {
|
||||||
int preData = 0;
|
int ascCount = 1;
|
||||||
int serialcount = 1;
|
int descCount = 1;
|
||||||
|
|
||||||
for (int i = 0; i < password.length(); i++) {
|
for (int i = 1; i < password.length(); i++) {
|
||||||
|
|
||||||
|
char prev = password.charAt(i - 1);
|
||||||
|
char curr = password.charAt(i);
|
||||||
|
|
||||||
|
ascCount = (curr == prev + 1) ? ascCount + 1 : 1;
|
||||||
|
descCount = (curr == prev - 1) ? descCount + 1 : 1;
|
||||||
|
|
||||||
|
if (ascCount >= count || descCount >= count) { return true; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 키보드 자판 배열상 연속된 문자열(qwer, asdf 등, 역순 포함) 사용 여부 체크
|
||||||
|
private boolean isKeyboardSequentialPassword(String password, int count) {
|
||||||
|
String lowerPassword = password.toLowerCase();
|
||||||
|
|
||||||
|
for (String row : KEYBOARD_SEQUENTIAL_ROWS) {
|
||||||
|
|
||||||
|
for (int i = 0; i <= row.length() - count; i++) {
|
||||||
|
|
||||||
|
String forward = row.substring(i, i + count);
|
||||||
|
String backward = new StringBuilder(forward).reverse().toString();
|
||||||
|
|
||||||
|
if (lowerPassword.contains(forward) || lowerPassword.contains(backward)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (preData + 1 == password.charAt(i)) {
|
|
||||||
serialcount++;
|
|
||||||
} else {
|
|
||||||
serialcount = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
preData = password.charAt(i);
|
|
||||||
|
|
||||||
if (serialcount == count) { return true; }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package com.eactive.eai.rms.data.entity.man.user;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.Comment;
|
||||||
|
import org.hibernate.annotations.GenericGenerator;
|
||||||
|
|
||||||
|
import com.eactive.eai.data.entity.AbstractEntity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 관리콘솔(TSEAIRM02) 로그인 사용자의 비밀번호 변경 이력 (TSEAIRM08).
|
||||||
|
* 포털 API 소비자 비밀번호 이력({@code com.eactive.apim.portal.portaluser.entity.UserPasswordHistory})과는 별개 테이블/도메인이다.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true)
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "TSEAIRM08")
|
||||||
|
@org.hibernate.annotations.Table(appliesTo = "TSEAIRM08", comment = "관리콘솔 사용자 비밀번호 변경 이력")
|
||||||
|
public class AdminUserPasswordHistory extends AbstractEntity<Integer> implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GenericGenerator(
|
||||||
|
name = "TSEAIRM08_GEN",
|
||||||
|
strategy = "com.eactive.eai.rms.data.jpa.SchemaPrefixedSequenceGenerator",
|
||||||
|
parameters = {
|
||||||
|
@org.hibernate.annotations.Parameter(name = "sequence_name", value = "SEQ_TSEAIRM08"),
|
||||||
|
@org.hibernate.annotations.Parameter(name = "initial_value", value = "1"),
|
||||||
|
@org.hibernate.annotations.Parameter(name = "increment_size", value = "1")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@GeneratedValue(
|
||||||
|
strategy = GenerationType.SEQUENCE,
|
||||||
|
generator = "TSEAIRM08_GEN"
|
||||||
|
)
|
||||||
|
@Column(name = "ID", unique = true, nullable = false)
|
||||||
|
@Comment("비밀번호 변경 이력 식별 ID")
|
||||||
|
private int pwdHistId;
|
||||||
|
|
||||||
|
@Column(name = "USERID", length = 255, nullable = false)
|
||||||
|
@Comment("사용자 식별자(사번)")
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
@Column(name = "PASSWORD_HASH", length = 255, nullable = false)
|
||||||
|
@Comment("변경된 비밀번호 해시값")
|
||||||
|
private String passwordHash;
|
||||||
|
|
||||||
|
@Column(name = "CHANGE_DATE", length = 14)
|
||||||
|
@Comment("비밀번호 변경 일시 (yyyyMMddHHmmss)")
|
||||||
|
private String changeDate;
|
||||||
|
|
||||||
|
@Column(name = "CREATED_BY", length = 200)
|
||||||
|
@Comment("등록자 식별자")
|
||||||
|
private String createdBy;
|
||||||
|
|
||||||
|
@Column(name = "CREATED_DATE", length = 14)
|
||||||
|
@Comment("레코드 생성 일시 (yyyyMMddHHmmss)")
|
||||||
|
private String createdDate;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull Integer getId() {
|
||||||
|
return pwdHistId;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
package com.eactive.eai.rms.data.entity.man.user;
|
||||||
|
|
||||||
|
import com.eactive.eai.rms.data.EMSDataSource;
|
||||||
|
import com.eactive.eai.data.jpa.BaseRepository;
|
||||||
|
|
||||||
|
@EMSDataSource
|
||||||
|
public interface AdminUserPasswordHistoryRepository extends BaseRepository<AdminUserPasswordHistory, Integer> {
|
||||||
|
|
||||||
|
}
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
package com.eactive.eai.rms.data.entity.man.user;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import com.eactive.eai.data.converter.LocalDateTimeFormatters;
|
||||||
|
import com.eactive.eai.rms.data.jpa.AbstractEMSDataSerivce;
|
||||||
|
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Transactional(transactionManager = "transactionManagerForEMS")
|
||||||
|
public class AdminUserPasswordHistoryService
|
||||||
|
extends AbstractEMSDataSerivce<AdminUserPasswordHistory, Integer, AdminUserPasswordHistoryRepository> {
|
||||||
|
|
||||||
|
/** 사용자의 최근 비밀번호 이력을 최신순으로 historyCount건 조회 */
|
||||||
|
public List<AdminUserPasswordHistory> findRecent(String userId, int historyCount) {
|
||||||
|
BooleanExpression predicate = QAdminUserPasswordHistory.adminUserPasswordHistory.userId.eq(userId);
|
||||||
|
Pageable pageable = PageRequest.of(0, historyCount, Sort.by(Sort.Direction.DESC, "pwdHistId"));
|
||||||
|
|
||||||
|
return repository.findAll(predicate, pageable).getContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 최근 historyCount건 이내에 동일한 비밀번호(해시값)가 있었는지 확인 */
|
||||||
|
public boolean isPasswordReused(String userId, String newPasswordHash, int historyCount) {
|
||||||
|
if (historyCount <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return findRecent(userId, historyCount).stream()
|
||||||
|
.anyMatch(history -> history.getPasswordHash().equals(newPasswordHash));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 비밀번호 변경 이력 저장 */
|
||||||
|
public void recordPasswordChange(String userId, String passwordHash) {
|
||||||
|
String now = LocalDateTime.now().format(LocalDateTimeFormatters.FORMATTER_YYYYMMDDHHMMSS_14);
|
||||||
|
|
||||||
|
AdminUserPasswordHistory history = new AdminUserPasswordHistory();
|
||||||
|
history.setUserId(userId);
|
||||||
|
history.setPasswordHash(passwordHash);
|
||||||
|
history.setChangeDate(now);
|
||||||
|
history.setCreatedBy(userId);
|
||||||
|
history.setCreatedDate(now);
|
||||||
|
|
||||||
|
save(history);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1215,9 +1215,12 @@ login.pwdchanged = Your password has been changed.
|
|||||||
login.pwdmismatch1 = Your change password is different from your verification password.
|
login.pwdmismatch1 = Your change password is different from your verification password.
|
||||||
login.pwdmismatch2 = Your change password is different from your verification password. <BR/> Please check your login information.
|
login.pwdmismatch2 = Your change password is different from your verification password. <BR/> Please check your login information.
|
||||||
login.pwdmismatch3 = The change password does not match the rule.
|
login.pwdmismatch3 = The change password does not match the rule.
|
||||||
login.pwdmismatch4 = The change password does not match the rule. <BR/> Please check your login information.
|
login.pwdmismatch4 = The change password does not match the rule.
|
||||||
login.pwdmismatch5 = Your password is different.
|
login.pwdmismatch5 = Your password is different.
|
||||||
login.pwdmismatch6 = Your password is different. <BR/> Please check your login information.
|
login.pwdmismatch6 = Your password is different.
|
||||||
|
login.pwdreused1 = You cannot reuse a recently used password.
|
||||||
|
login.pwdreused2 = You cannot reuse a recently used password. <BR/> Please enter a different password.
|
||||||
|
login.pwdchangefail = An error occurred while changing the password.
|
||||||
login.pwdnotchanged1 = You did not change your password after resetting your password. Please change your password and login.
|
login.pwdnotchanged1 = You did not change your password after resetting your password. Please change your password and login.
|
||||||
login.pwdnotchanged2 = You did not change your password after resetting your password.<BR/>Please change your password and login.
|
login.pwdnotchanged2 = You did not change your password after resetting your password.<BR/>Please change your password and login.
|
||||||
login.seedencriptfail = Seed encryption failed. <BR/> Please sign in again.
|
login.seedencriptfail = Seed encryption failed. <BR/> Please sign in again.
|
||||||
|
|||||||
@@ -1360,9 +1360,12 @@ login.pwdchanged = Your password has been changed.
|
|||||||
login.pwdmismatch1 = Your change password is different from your verification password.
|
login.pwdmismatch1 = Your change password is different from your verification password.
|
||||||
login.pwdmismatch2 = Your change password is different from your verification password. <BR/> Please check your login information.
|
login.pwdmismatch2 = Your change password is different from your verification password. <BR/> Please check your login information.
|
||||||
login.pwdmismatch3 = The change password does not match the rule.
|
login.pwdmismatch3 = The change password does not match the rule.
|
||||||
login.pwdmismatch4 = The change password does not match the rule. <BR/> Please check your login information.
|
login.pwdmismatch4 = The change password does not match the rule.
|
||||||
login.pwdmismatch5 = Your password is different.
|
login.pwdmismatch5 = Your password is different.
|
||||||
login.pwdmismatch6 = Your password is different. <BR/> Please check your login information.
|
login.pwdmismatch6 = Your password is different.
|
||||||
|
login.pwdreused1 = You cannot reuse a recently used password.
|
||||||
|
login.pwdreused2 = You cannot reuse a recently used password. <BR/> Please enter a different password.
|
||||||
|
login.pwdchangefail = An error occurred while changing the password.
|
||||||
login.pwdnotchanged1 = You did not change your password after resetting your password. Please change your password and login.
|
login.pwdnotchanged1 = You did not change your password after resetting your password. Please change your password and login.
|
||||||
login.pwdnotchanged2 = You did not change your password after resetting your password.<BR/>Please change your password and login.
|
login.pwdnotchanged2 = You did not change your password after resetting your password.<BR/>Please change your password and login.
|
||||||
login.seedencriptfail = Seed encryption failed. <BR/> Please sign in again.
|
login.seedencriptfail = Seed encryption failed. <BR/> Please sign in again.
|
||||||
|
|||||||
@@ -1440,9 +1440,12 @@ login.pwdchanged = \uBE44\uBC00\uBC88\uD638\uAC00 \uBCC0\uA
|
|||||||
login.pwdmismatch1 = \uBCC0\uACBD \uBE44\uBC00\uBC88\uD638 \uC640 \uD655\uC778 \uBE44\uBC00\uBC88\uD638\uAC00 \uB2E4\uB985\uB2C8\uB2E4.
|
login.pwdmismatch1 = \uBCC0\uACBD \uBE44\uBC00\uBC88\uD638 \uC640 \uD655\uC778 \uBE44\uBC00\uBC88\uD638\uAC00 \uB2E4\uB985\uB2C8\uB2E4.
|
||||||
login.pwdmismatch2 = \uBCC0\uACBD \uBE44\uBC00\uBC88\uD638 \uC640 \uD655\uC778 \uBE44\uBC00\uBC88\uD638\uAC00 \uB2E4\uB985\uB2C8\uB2E4.<BR/> \uB85C\uADF8\uC778\uC815\uBCF4\uB97C \uD655\uC778\uD558\uC138\uC694.
|
login.pwdmismatch2 = \uBCC0\uACBD \uBE44\uBC00\uBC88\uD638 \uC640 \uD655\uC778 \uBE44\uBC00\uBC88\uD638\uAC00 \uB2E4\uB985\uB2C8\uB2E4.<BR/> \uB85C\uADF8\uC778\uC815\uBCF4\uB97C \uD655\uC778\uD558\uC138\uC694.
|
||||||
login.pwdmismatch3 = \uBCC0\uACBD\uBE44\uBC00\uBC88\uD638\uAC00 \uB8F0\uC5D0 \uC548\uB9DE\uC2B5\uB2C8\uB2E4.
|
login.pwdmismatch3 = \uBCC0\uACBD\uBE44\uBC00\uBC88\uD638\uAC00 \uB8F0\uC5D0 \uC548\uB9DE\uC2B5\uB2C8\uB2E4.
|
||||||
login.pwdmismatch4 = \uBCC0\uACBD\uBE44\uBC00\uBC88\uD638\uAC00 \uB8F0\uC5D0 \uC548\uB9DE\uC2B5\uB2C8\uB2E4.<BR/> \uB85C\uADF8\uC778\uC815\uBCF4\uB97C \uD655\uC778\uD558\uC138\uC694.
|
login.pwdmismatch4 = \uBCC0\uACBD\uBE44\uBC00\uBC88\uD638\uAC00 \uB8F0\uC5D0 \uC548\uB9DE\uC2B5\uB2C8\uB2E4.
|
||||||
login.pwdmismatch5 = \uBCC0\uACBD\uC804 \uBE44\uBC00\uBC88\uD638\uAC00 \uB2E4\uB985\uB2C8\uB2E4.
|
login.pwdmismatch5 = \uBCC0\uACBD\uC804 \uBE44\uBC00\uBC88\uD638\uAC00 \uB2E4\uB985\uB2C8\uB2E4.
|
||||||
login.pwdmismatch6 = \uBCC0\uACBD\uC804 \uBE44\uBC00\uBC88\uD638\uAC00 \uB2E4\uB985\uB2C8\uB2E4.<BR/> \uB85C\uADF8\uC778\uC815\uBCF4\uB97C \uD655\uC778\uD558\uC138\uC694.
|
login.pwdmismatch6 = \uBCC0\uACBD\uC804 \uBE44\uBC00\uBC88\uD638\uAC00 \uB2E4\uB985\uB2C8\uB2E4.
|
||||||
|
login.pwdreused1 = \uCD5C\uADFC \uC0AC\uC6A9\uD55C \uBE44\uBC00\uBC88\uD638\uB294 \uB2E4\uC2DC \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.
|
||||||
|
login.pwdreused2 = \uCD5C\uADFC \uC0AC\uC6A9\uD55C \uBE44\uBC00\uBC88\uD638\uB294 \uB2E4\uC2DC \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.<BR/> \uB2E4\uB978 \uBE44\uBC00\uBC88\uD638\uB97C \uC785\uB825\uD558\uC138\uC694.
|
||||||
|
login.pwdchangefail = \uBE44\uBC00\uBC88\uD638 \uBCC0\uACBD \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.
|
||||||
login.pwdnotchanged1 = \uBE44\uBC00\uBC88\uD638 \uCD08\uAE30\uD654\uD6C4 \uBE44\uBC00\uBC88\uD638 \uBCC0\uACBD\uC744 \uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\uBE44\uBC00\uBC88\uD638\uB97C \uBCC0\uACBD\uD558\uC2DC\uACE0 \uB85C\uADF8\uC778 \uD558\uC138\uC694
|
login.pwdnotchanged1 = \uBE44\uBC00\uBC88\uD638 \uCD08\uAE30\uD654\uD6C4 \uBE44\uBC00\uBC88\uD638 \uBCC0\uACBD\uC744 \uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\uBE44\uBC00\uBC88\uD638\uB97C \uBCC0\uACBD\uD558\uC2DC\uACE0 \uB85C\uADF8\uC778 \uD558\uC138\uC694
|
||||||
login.pwdnotchanged2 = \uBE44\uBC00\uBC88\uD638 \uCD08\uAE30\uD654\uD6C4 \uBE44\uBC00\uBC88\uD638 \uBCC0\uACBD\uC744 \uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\uBE44\uBC00\uBC88\uD638\uB97C \uBCC0\uACBD\uD558\uC2DC\uACE0 \uB85C\uADF8\uC778 \uD558\uC138\uC694.<BR/> \uB85C\uADF8\uC778\uC815\uBCF4\uB97C \uD655\uC778\uD558\uC138\uC694.
|
login.pwdnotchanged2 = \uBE44\uBC00\uBC88\uD638 \uCD08\uAE30\uD654\uD6C4 \uBE44\uBC00\uBC88\uD638 \uBCC0\uACBD\uC744 \uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\uBE44\uBC00\uBC88\uD638\uB97C \uBCC0\uACBD\uD558\uC2DC\uACE0 \uB85C\uADF8\uC778 \uD558\uC138\uC694.<BR/> \uB85C\uADF8\uC778\uC815\uBCF4\uB97C \uD655\uC778\uD558\uC138\uC694.
|
||||||
login.seedencriptfail = Seed \uC554\uD638\uD654\uC5D0 \uC2E4\uD328 \uD558\uC600\uC2B5\uB2C8\uB2E4.<BR/> \uB2E4\uC2DC \uB85C\uADF8\uC778\uD558\uC138\uC694.
|
login.seedencriptfail = Seed \uC554\uD638\uD654\uC5D0 \uC2E4\uD328 \uD558\uC600\uC2B5\uB2C8\uB2E4.<BR/> \uB2E4\uC2DC \uB85C\uADF8\uC778\uD558\uC138\uC694.
|
||||||
|
|||||||
Reference in New Issue
Block a user