Просмотр исходного кода

Merge branch 'develop' of http://112.172.147.34:4936/style24/style24.front into develop

jsshin 5 лет назад
Родитель
Сommit
d8f90f5777

+ 8 - 0
src/main/java/com/style24/front/biz/dao/TsfLoginDao.java

@@ -39,6 +39,14 @@ public interface TsfLoginDao {
 	 */
 	int getLoginFailCount(Login login);
 
+	/**
+	 * 로그인실패정보 조회
+	 * @param login - 로그인 정보
+	 * @author gagamel
+	 * @since 2021. 2. 16
+	 */
+	Login getLoginFailInfo(Login login);
+
 	/**
 	 * 최종로그인일시 Update
 	 * @param custNo - 고객번호

+ 20 - 6
src/main/java/com/style24/front/biz/service/TsfLoginService.java

@@ -70,15 +70,15 @@ public class TsfLoginService {
 
 	/**
 	 * 로그인실패 남기기. 로그인실패여부가 "N:성공"이면 실패건수 0으로 초기화
-	 * @param custNo - 고객번호
+	 * @param custId - 고객ID(=로그인ID)
 	 * @param loginFailYn - 로그인실패여부(Y:실패, N:성공)
 	 * @author gagamel
 	 * @since 2020. 2. 3
 	 */
 	@Transactional("shopTxnManager")
-	public void createLoginFail(Integer custNo, String loginFailYn) {
+	public void createLoginFail(String custId, String loginFailYn) {
 		Login login = new Login();
-		login.setCustNo(custNo);
+		login.setCustId(custId);
 		login.setIpAddr(TsfSession.getIpAddress());
 		login.setSiteCd(TscConstants.Site.STYLE24.value());
 		login.setLoginFailYn(loginFailYn);
@@ -88,20 +88,34 @@ public class TsfLoginService {
 
 	/**
 	 * 로그인 실패건수 조회
-	 * @param custNo - 고객번호
+	 * @param custId - 고객ID(=로그인ID)
 	 * @return 로그인 실패건수
 	 * @author gagamel
 	 * @since 2020. 2. 3
 	 */
-	public int getLoginFailCount(Integer custNo) {
+	public int getLoginFailCount(String custId) {
 		Login login = new Login();
-		login.setCustNo(custNo);
+		login.setCustId(custId);
 		login.setIpAddr(TsfSession.getIpAddress());
 		login.setSiteCd(TscConstants.Site.STYLE24.value());
 
 		return loginDao.getLoginFailCount(login);
 	}
 
+	/**
+	 * 로그인실패정보 조회
+	 * @param custId - 고객ID(=로그인ID)
+	 * @author gagamel
+	 * @since 2021. 2. 16
+	 */
+	public Login getLoginFailInfo(String custId) {
+		Login login = new Login();
+		login.setCustId(custId);
+		login.setIpAddr(TsfSession.getIpAddress());
+		login.setSiteCd(TscConstants.Site.STYLE24.value());
+		return loginDao.getLoginFailInfo(login);
+	}
+
 	/**
 	 * 로그인유지토큰 생성
 	 * @param custNo - 고객번호

+ 3 - 0
src/main/java/com/style24/front/support/env/TsfConstants.java

@@ -19,6 +19,9 @@ public class TsfConstants {
 	public static final String REMEMBER_ME_PARAMETER = "rememberMe";
 	public static final int REMEMBER_ME_LIMIT = 60 * 60 * 24 * 7; // 7일간유효
 
+	// 로그인블락실패건수
+	public static final int LOGIN_BLOCK_FAIL_CNT = 10;
+
 //	// 카테고리구분
 //	public enum CATE_GB {
 //		BYITEM("101"), BYBRAND("102"), BYOUTLET("103");

+ 2 - 18
src/main/java/com/style24/front/support/security/TsfAuthenticationProvider.java

@@ -76,12 +76,8 @@ public class TsfAuthenticationProvider implements AuthenticationProvider {
 			throw new TsfUsernameNotFoundException(message.getMessage("LOGN_0001"));
 		}
 
-		// 로그인 실패누적건수가 5이면
-		if (loginInfo.getLoginFailCnt() == 5) {
-			// 로그인 실패 남기기
-			loginService.createLoginFail(loginInfo.getCustNo(), "Y");
-
-//			throw new BadCredentialsException(message.getMessage("LOGN_0005"));
+		// 로그인 실패누적건수가 5회 이상이면
+		if (loginInfo.getLoginFailCnt() >= 5) {
 			throw new TsfLockedAccountException(message.getMessage("LOGN_0005"));
 		}
 
@@ -92,32 +88,20 @@ public class TsfAuthenticationProvider implements AuthenticationProvider {
 			log.info("Password is match?: {}", isMatch);
 
 			if (!isMatch) {
-				// 로그인 실패 남기기
-				loginService.createLoginFail(loginInfo.getCustNo(), "Y");
-
 				throw new BadCredentialsException(message.getMessage("LOGN_0002"));
 			}
 		} else {
 			// SNS로그인 시 로그인ID 값은 이메일로 처리했으므로
 			// loginId 값과 회원정보의 이메일 값을 비교해서 동일하면
 			if (StringUtils.isBlank(loginInfo.getSnsType()) && passwd.equals(loginInfo.getEmail())) {
-				// 로그인 실패 남기기
-				loginService.createLoginFail(loginInfo.getCustNo(), "Y");
-
 				throw new TsfEmailDuplicationException(message.getMessage("LOGN_0008"));
 			}
 		}
 
 		if (loginInfo.getCustStat().equals("20")) { // 휴면회원
-			// 로그인 실패 남기기
-			loginService.createLoginFail(loginInfo.getCustNo(), "Y");
-
 //			WfoSession.setDormantMemberNo(request, loginInfo.getCustId());
 			throw new TsfDormantAccountException(message.getMessage("LOGN_0006"));
 		} else if (loginInfo.getCustStat().equals("30")) { // 탈퇴회원
-			// 로그인 실패 남기기
-			loginService.createLoginFail(loginInfo.getCustNo(), "Y");
-
 			throw new TsfSecedeAccountException(message.getMessage("LOGN_0007"));
 		}
 

+ 10 - 7
src/main/java/com/style24/front/support/security/handler/TsfLoginFailureHandler.java

@@ -6,16 +6,16 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.web.authentication.AuthenticationFailureHandler;
 
+import com.style24.front.biz.service.TsfLoginService;
 import com.style24.front.support.exception.TsfDormantAccountException;
 import com.style24.front.support.exception.TsfEmailDuplicationException;
 import com.style24.front.support.exception.TsfLockedAccountException;
 import com.style24.front.support.exception.TsfSecedeAccountException;
 import com.style24.front.support.exception.TsfSessionExpiredException;
-import com.style24.front.support.security.session.TsfSession;
 
 import lombok.extern.slf4j.Slf4j;
 
@@ -34,10 +34,16 @@ import com.gagaframework.web.util.GagaStringUtil;
 @Slf4j
 public class TsfLoginFailureHandler implements AuthenticationFailureHandler {
 
+	@Autowired
+	private TsfLoginService loginService;
+
 	@Override
 	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
 		AuthenticationException exception) throws IOException, ServletException {
 
+		// 로그인 실패 남기기
+		loginService.createLoginFail(request.getParameter("loginId"), "Y");
+
 		GagaMap result = new GagaMap();
 		result.setString("message", exception.getMessage());
 
@@ -55,11 +61,8 @@ public class TsfLoginFailureHandler implements AuthenticationFailureHandler {
 			result.setString("status", "ETC_ERROR");
 		}
 
-		// 로그인실패건수 세션에 저장
-		int loginFailCnt = (StringUtils.isNotBlank(TsfSession.getAttribute("loginFailCnt")) ? Integer.parseInt(TsfSession.getAttribute("loginFailCnt")) : 0) + 1;
-		log.info("loginFailCnt: {}", loginFailCnt);
-		result.setInt("loginFailCnt", loginFailCnt);
-		TsfSession.setAttribute("loginFailCnt", String.valueOf(loginFailCnt));
+		// 로그인실패정보 조회
+		result.set("loginFailInfo", loginService.getLoginFailInfo(request.getParameter("loginId")));
 
 		GagaStringUtil.write(response, result);
 	}

+ 2 - 5
src/main/java/com/style24/front/support/security/handler/TsfLoginSuccessHandler.java

@@ -57,9 +57,9 @@ public class TsfLoginSuccessHandler implements AuthenticationSuccessHandler {
 
 		// 로그인 성공 시 로그인실패수가 0보다 크면 로그인실패수 reset
 		Integer custNo = loginDetails.getLoginInfo().getCustNo();
-		int failCnt = loginService.getLoginFailCount(custNo);
+		int failCnt = loginService.getLoginFailCount(loginDetails.getLoginInfo().getCustId());
 		if (failCnt > 0) {
-			loginService.createLoginFail(loginDetails.getLoginInfo().getCustNo(), "N");
+			loginService.createLoginFail(loginDetails.getLoginInfo().getCustId(), "N");
 		}
 
 		// 최종로그인일시 Update
@@ -122,9 +122,6 @@ public class TsfLoginSuccessHandler implements AuthenticationSuccessHandler {
 		HttpSession session = request.getSession(true);
 		session.setMaxInactiveInterval(1800);
 		session.setAttribute("session", loginDetails);
-
-		// 세션에 저장된 로그인실패건수 제거
-		session.removeAttribute("loginFailCnt");
 	}
 
 }

+ 2 - 5
src/main/java/com/style24/front/support/security/handler/TsfRememberMeSuccessHandler.java

@@ -36,9 +36,9 @@ public class TsfRememberMeSuccessHandler implements AuthenticationSuccessHandler
 
 		// 로그인 성공 시 로그인실패수가 0보다 크면 로그인실패수 reset
 		Integer custNo = loginDetails.getLoginInfo().getCustNo();
-		int failCnt = loginService.getLoginFailCount(custNo);
+		int failCnt = loginService.getLoginFailCount(loginDetails.getLoginInfo().getCustId());
 		if (failCnt > 0) {
-			loginService.createLoginFail(loginDetails.getLoginInfo().getCustNo(), "N");
+			loginService.createLoginFail(loginDetails.getLoginInfo().getCustId(), "N");
 		}
 
 		// 최종로그인일시 Update
@@ -74,9 +74,6 @@ public class TsfRememberMeSuccessHandler implements AuthenticationSuccessHandler
 		HttpSession session = request.getSession(true);
 		session.setMaxInactiveInterval(1800);
 		session.setAttribute("session", loginDetails);
-
-		// 세션에 저장된 로그인실패건수 제거
-		session.removeAttribute("loginFailCnt");
 	}
 
 }

+ 1 - 0
src/main/java/com/style24/persistence/domain/Login.java

@@ -34,6 +34,7 @@ public class Login extends TscBaseDomain {
 	private String snsType;			// SNS유형
 	private String snsJoinId;		// SNS가입ID
 	private int loginFailCnt;		// 로그인실패건수
+	private int blockSecs;			// 로그인블락시간(초)
 	private String siteCd;			// 가입사이트코드(공통코드G000)
 	private String frontGb;			// 프론트구분(P:PC, M:모바일)
 	private String ipAddr;			// IP주소

+ 26 - 12
src/main/java/com/style24/persistence/mybatis/shop/TsfLogin.xml

@@ -21,7 +21,7 @@
 		       END                            AS SNS_JOIN_ID    /*SNS가입ID*/
 		     , IFNULL((SELECT LOGIN_FAIL_CNT
 		               FROM   TB_LOGIN_FAIL
-		               WHERE  CUST_NO = A.CUST_NO
+		               WHERE  CUST_ID = #{custId}
 		               AND    IP_ADDR = #{ipAddr}
 		               AND    SITE_CD = #{siteCd}
 		              ),0)                    AS LOGIN_FAIL_CNT /*로그인실패건수*/
@@ -47,28 +47,27 @@
 	<insert id="createLoginFail" parameterType="Login">
 		/* TsfLogin.createLoginFail */
 		INSERT INTO TB_LOGIN_FAIL (
-		       CUST_NO
+		       CUST_ID
 		     , IP_ADDR
 		     , SITE_CD
 		     , LOGIN_FAIL_CNT
-		     , REG_NO
-		     , REG_DT
-		     , UPD_NO
 		     , UPD_DT
 		)
 		VALUES (
-		       #{custNo}
+		       #{custId}
 		     , #{ipAddr}
 		     , #{siteCd}
 		     , 1
-		     , #{custNo}
-		     , NOW()
-		     , #{custNo}
 		     , NOW()
 		)
 		ON DUPLICATE KEY UPDATE
-		       LOGIN_FAIL_CNT = CASE WHEN #{loginFailYn} = 'Y' THEN LOGIN_FAIL_CNT + 1 ELSE 0 END
-		     , UPD_NO = #{custNo}
+		       LOGIN_FAIL_CNT = CASE WHEN #{loginFailYn} = 'Y' THEN
+		                                 CASE WHEN LOGIN_FAIL_CNT = 10 THEN LOGIN_FAIL_CNT
+		                                      ELSE LOGIN_FAIL_CNT + 1
+		                                 END
+		                             ELSE
+		                                 0
+		                        END
 		     , UPD_DT = NOW()
 	</insert>
 
@@ -77,12 +76,27 @@
 		/* TsfLogin.getLoginFailCount */
 		SELECT IFNULL((SELECT LOGIN_FAIL_CNT
 		               FROM   TB_LOGIN_FAIL
-		               WHERE  CUST_NO = #{custNo}
+		               WHERE  CUST_ID = #{custId}
 		               AND    IP_ADDR = #{ipAddr}
 		               AND    SITE_CD = #{siteCd}
 		              ),0) AS CNT
 		FROM   DUAL
 	</select>
+	
+	<!-- 로그인실패정보 조회 -->
+	<select id="getLoginFailInfo" parameterType="Login" resultType="Login">
+		/* TsfLogin.getLoginFailInfo */
+		SELECT LOGIN_FAIL_CNT
+		     , CASE WHEN LOGIN_FAIL_CNT <![CDATA[>=]]> 10 THEN
+		                180 - TIMESTAMPDIFF(SECOND,UPD_DT,NOW())
+		            ELSE
+		                0
+		       END            AS BLOCK_SECS /*로그인블락시간(초)*/
+		FROM   TB_LOGIN_FAIL
+		WHERE  CUST_ID = #{custId}
+		AND    IP_ADDR = #{ipAddr}
+		AND    SITE_CD = #{siteCd}
+	</select>
 
 	<!-- 최종로그인일시 Update -->
 	<update id="updateLastLoginDate" parameterType="Integer">

+ 50 - 19
src/main/webapp/WEB-INF/views/web/SigninFormWeb.html

@@ -34,7 +34,7 @@
 							<label class="input_label sr-only">아이디</label>
 							<div class="ui_col_12">
 								<div class="input_wrap"> 
-									<input type="text" name="loginId" placeholder="아이디" class="form_control" minlength="4" maxlength="20" required="required" data-valid-name="아이디"/>
+									<input type="text" name="loginId" placeholder="아이디" class="form_control" minlength="4" maxlength="12" required="required" data-valid-type="alphaNumeric" data-valid-name="아이디"/>
 								</div>
 							</div>
 						</div>
@@ -42,7 +42,7 @@
 							<label class="input_label sr-only">비밀번호</label>
 							<div class="ui_col_12">
 								<div class="input_wrap"> 
-									<input type="password" name="passwd" class="form_control" placeholder="비밀번호 8자 ~ 20자 입력" maxlength="20" required="required" data-valid-name="비밀번호"/>
+									<input type="password" name="passwd" class="form_control" placeholder="비밀번호 8자 ~ 20자 입력" minlength="8" maxlength="20" required="required" data-valid-name="비밀번호"/>
 								</div>
 							</div>
 						</div>
@@ -59,34 +59,30 @@
 						</div>
 						<!-- case (회원 아이디 또는 비밀번호가 일치하지 않을때,보안문자 입력시) -->
 						<div class="help_block">
-							<!-- 회원 아이디 또는 비밀번호가 일치하지 않을때 -->
-							<p class="t_err t_err_login_fail" style="display: none;">
-								<span id="not_exist">회원 아이디 또는 비밀번호가 일치하지 않습니다.</span><br/>
-								(10회 이상 실패하면 180초 동안 로그인이 불가능 합니다.)<br/>
-								<span id="login_fail_cnt">4</span>회 실패 / 잔여 : <span id="login_remain_cnt">6</span>회 / <span>180</span>초 남음
-							</p>
 							<!-- //회원 아이디 또는 비밀번호가 일치하지 않을때 -->
 							<!-- 보안문자 입력시 -->
 							<div class="captcha mt40" style="display: none;"> <!-- 캡차영역 -->
 								<ul>
 									<li class="captcha_box"> <!-- 캡차이미지 -->
+										<img src="" id="imgCaptcha"/>
 									</li>
 									<li class="captcha_btn_dual">
-										<button type="button" id="play_audio">새로고침</button>
+										<button type="button" id="play_audio" onclick="fnReloadCaptchaImage();">새로고침</button>
 										<button type="button" id="swap_captcha">음성듣기</button>
 									</li>
 									<li class="captcha_area">
 										<label for="captcha" id="label_captcha_area">보안문자 입력</label>
-										<input type="text" id="captcha" name="captcha" title="문자입력">
+										<input type="text" name="captcha" title="문자입력">
 									</li>
 								</ul>
 							</div>
-							<p class="t_err t_err_captcha" style="display: none;">
-								보안문자 입력을 다시 시도해 주세요.<br/>
-								(10회 이상 실패하면 300초 동안 로그인이 불가능 합니다.)<br/>
-								<span>10</span>회 실패 / 잔여 : <span>0</span>회 / <span>180</span>초 남음
-							</p>
 							<!-- //보안문자 입력시 -->
+							<!-- 회원 아이디 또는 비밀번호가 일치하지 않을때 -->
+							<p class="t_err t_err_login_fail" style="display: none;">
+								<span id="err_msg">보안문자 입력을 다시 시도해 주세요.</span><br/>
+								(10회 이상 실패하면 180초 동안 로그인이 불가능 합니다.)<br/>
+								<span id="login_fail_cnt">4</span>회 실패 / 잔여 : <span id="login_remain_cnt">6</span>회<span id="blockSecs1" style="display: none;"> / <span id="blockSecs2">180</span>초 남음</span>
+							</p>
 						</div>
 						<!-- //case (회원 아이디 또는 비밀번호가 일치하지 않을때,보안문자 입력시) -->
 						<div class="ui_row mt40">
@@ -131,6 +127,18 @@
 /*<![CDATA[*/
 	let ckLoginId = "ckLoginId";
 
+	// 로그인블락시간(초) 설정
+	let fnSetLoginBlockTime = function() {
+		let blockSecs = Number($("#blockSecs2").html()) - 1;
+		$("#blockSecs2").html(blockSecs);
+		if (blockSecs == 0) {
+			clearTimeout(fnSetLoginBlockTime);
+			$('#btnLogin').attr('disabled',false);
+		} else {
+			setTimeout(fnSetLoginBlockTime, 1000);
+		}
+	}
+	
 	// 로그인
 	$('#btnLogin').on('click', function() {
 		if (!gagajf.validation($('#loginForm'))) {
@@ -146,14 +154,32 @@
 			, function(result) {
 				if (result.status != 'OK') {
 					if (!gagajf.isNull(result.message)) {
-						$("#not_exist").html(result.message);
-						$("#login_fail_cnt").html(result.loginFailCnt);
-						$("#login_remain_cnt").html(10 - Number(result.loginFailCnt));
+						let loginFailCnt = Number(result.loginFailInfo.loginFailCnt);
+						
+						$("#err_msg").html(result.message);
+						$("#login_fail_cnt").html(loginFailCnt);
+						$("#login_remain_cnt").html(10 - loginFailCnt);
 						$(".t_err_login_fail").show();
+						
+						if (loginFailCnt >= 5 && loginFailCnt < 10) {
+							// 비밀번호 5회 이상 틀린 경우 캡챠 노출
+							$('.captcha').show();
+						} else if (loginFailCnt >= 10) {
+							$('.captcha').hide();
+							
+							// 비밀번호 10회 이상 틀린 경우 로그인 블락
+							let blockSecs = Number(result.loginFailInfo.blockSecs);
+							if (blockSecs > 0) {
+								$("#blockSecs2").html(blockSecs);
+								fnSetLoginBlockTime();
+								$("#blockSecs1").show();
+								$('#btnLogin').attr('disabled',true);
+							}
+						}
 					}
 					
 					if (result.status == 'PWD_5WRONG') {
-						// 비밀번호 5회 이상 틀린 경우
+						// Do nothing
 					} else if (result.status == 'DORMANT_CUST') {
 						// 휴면회원
 						//cfnGoToPage(_PAGE_DORMANT_GUIDE);
@@ -200,6 +226,11 @@
 		}
 	});
 	
+	// 캡챠 이미지 로딩
+	let fnReloadCaptchaImage = function() {
+		$('#imgCaptcha').attr('src', '/common/captcha.do?dummy=' + new Date());
+	}
+	
 	// 카카오 로그인
 	var fnLoginKakao = function() {
 		document.location.href = _frontUrl + '/signin/kakologin';

+ 1 - 1
src/main/webapp/ux/plugins/gaga/gaga.validation.js

@@ -9,7 +9,7 @@
  *
  * Using)
  * 		1. Add "data-valid-type" and "data-valid-name" attribute to Elements of form
- * 			ex) <input type="text" name="userNm" data-valid-type="alpahNumeric" data-valid-name="User Name"/>
+ * 			ex) <input type="text" name="userNm" data-valid-type="alphaNumeric" data-valid-name="User Name"/>
  *
  * 		2. data-valid-type
  * 			numeric, alphaNumeric, email, cellPhone, ipAddress