본문 바로가기

보안/시큐어코딩

[시큐어코딩실습] 로그인 횟수 제한

로그인 횟수 제한 설정




(1) 로그인을 처리에 사용되는 클래스들


kr.co.openeg.lab.login.controller.LoginController.java  

   : login.do 요청을 처리하는 컨트롤러 컴포넌트

kr.co.openeg.lab.login.service.LoginService.java

   : 로그인 요청을 처리하는 서비스 컴포넌트

kr.co.openeg.lab.login.dao.LoginDao.java

   : 로그인 요청에 대한 DB CRUD 처리 규격을 정의하고 있는 인터페이스

kr.co.openeg.lab.login.dao.LoginDaoImpl.java

   :  로그인 요청에 대한 DB 처리 구현 클래스

kr.co.openeg.lab.common.interceptor.SessionInterceptor.java

   : 시스템 연결 요청에 대한 세션 관리를 위한 Interceptor 클래스

kr.co.openeg.lab.member.model.SecVO.java

   : 오픈이지 시스템 사용자에 대한 보안 정책을 저장하는 모델 컴포넌트

login.jsp

   : 로그인 요청 처리 UI 뷰 컴포넌트



(2) 사용자 보안 정보를 담는 그릇 SecVO


package kr.co.openeg.lab.member.model;


// board_security 테이블 정보를 사용하기 위한 모델 컴포넌트

public class SecVO {

    private String userId;

    private String username;

    private String salt;

    private String secKey;

    private int loginStatus;

    private int loginFailedCount;

    private long lastFailedLogin;

    private long lastSuccessedLogin;

    

    public SecVO() {}


  

    public SecVO(String userId, String username, String salt, String secKey, 

                        int loginStatus, int loginFailedCount,

long lastFailedLogin, long lastSuccessedLogin) {

super();

this.userId = userId;

                this.username = username;

this.salt = salt;

this.secKey = secKey;

this.loginStatus = loginStatus;

this.loginFailedCount = loginFailedCount;

this.lastFailedLogin = lastFailedLogin;

this.lastSuccessedLogin = lastSuccessedLogin;

   }

   /* private 멤버 변수에 대한 getter/setter 메서드 선언 */ 



(3) SessionInterceptor을 이용한 요청 수락/차단 정책 설정


@Override

public boolean preHandle(HttpServletRequest request,

                                      HttpServletResponse response, Object handler) 

                      throws Exception {

// 세션에 저장된 보안 정책을 읽어온다.

SecVO secVO= (SecVO)request.getSession().getAttribute("secVO");

// 최초 접속인 경우 보안정책 모델을 생성하여 세션에 저장한다.

if(secVO == null) {

secVO=new SecVO();

request.getSession().setAttribute("secVO", secVO);

}

//  login.do나 join.do 페이지 요청인 경우

if(request.getRequestURI().equals("/openeg/login.do") || 

   request.getRequestURI().equals("/openeg/member/join.do")){


          // 로그인 상태를 확인하고 이미 로그인 한 사용자 이면 리스트 페이지로 이동

      if(secVO.getLoginStatus() ==1 ){

response.sendRedirect(request.getContextPath() + "/board/list.do");

return true;

 } else {

                // 로그인을 요청하거나 회원가입을 요청하면 해당 페이지로 이동

return true;

 }

       }

          // 로그인 하지 않은 사용자가 로그인이나 회원가입 이외의 페이지를 

          // 요청한 경우 로그인 페이지로 이동

if(secVO.getLoginStatus() == 0){

response.sendRedirect(request.getContextPath() + "/login.do");

HttpSession session=request.getSession();

session.setAttribute("errCode", "4");

return false;

} else {

                // 이미 로그인한 사용자는 해당 페이지로 이동

return true;

}

}



(4) LoginController에서 로그인 시도횟수 제한 


@RequestMapping(value="/login.do", method = RequestMethod.POST)

public ModelAndView loginProc(@ModelAttribute("LoginModel") 

                  LoginSessionModel loginModel, BindingResult result, 

                  HttpSession session) {

 

ModelAndView mav = new ModelAndView();

SecVO secVO=(SecVO)session.getAttribute("secVO");

String userId = loginModel.getUserId();

String userPw = loginModel.getUserPw();

// 처음 로그인 시도이면 secVO에 보안정책 설정

if ( secVO.getUserId() == null ) {

secVO=service.getSecurityInfo(userId);

}

int tryCount=secVO.getLoginFailedCount();

// 로그인 시도 횟수가 5회 이상이면 10초 간격을 두고 다시 

        // 로그인을 수행하도록 설정

if ( tryCount >= 5 ) {

    if ( getCurrentTime() - secVO.getLastFailedLogin() < 10000 ) {

       mav.addObject("errCode", 5);

       mav.setViewName("/board/login");

       return mav;

    } 

}

 

 

// 입력값 검증이 실패하면 tryCount를 1증가 시키고 로그인 화면으로 이동

new LoginValidator().validate(loginModel, result);

if(result.hasErrors()){

secVO.setLoginFailedCount(++tryCount);

session.setAttribute("secVO", secVO);

mav.setViewName("/board/login");

return mav;

}

        // 로그인 처리(DB 쿼리 수행)

LoginSessionModel loginCheckResult = 

                                           service.checkUserId(userId,userPw);

//로그인이 실패했으면 

if(loginCheckResult == null){

secVO.setLoginFailedCount(++tryCount);

secVO.setLastFailedLogin(new Date().getTime());

session.setAttribute("secVO", secVO);

mav.addObject("errCode", 1); // not exist userId in database

mav.setViewName("/board/login");

return mav; 

}else {

// 로그인이 성공했으면

secVO.setLoginFailedCount(0);

secVO.setLastSuccessedLogin(new Date().getTime());

secVO.setLoginStatus(1);

session.setAttribute("secVO", secVO);

mav.setViewName("redirect:/board/list.do");

return mav;

}

}



(5) 로그인 화면(login.jsp)

<div id="aside">

   <spring:hasBindErrors name="LoginModel" />

   <form:errors path="LoginModel" />

   <form action="login.do" method="post">

<fieldset>

<center>

<label for="userId">사용자명 : </label> 

                <input type="text" id="userId" name="userId" class="loginInput" 

                          value="${secVO.userId}" />

<span class="error">

                       <form:errors path="LoginModel.userId" />

                </span><br />

<label for="userPw">비밀번호 : </label> 

                <input type="password" id="userPw" name="userPw" 

                          class="loginInput" /> 

                <span class="error"><form:errors path="LoginModel.userPw" />

                </span><br />

<br /> 

                <input type="submit" value="로그인" class="submitBt" /> 

                <input ype="button" value="회원가입" class="submitBt"

  onClick='window.open("member/join.do","_blank",

                          "width=400,height=400, toolbar=no, menubar=no, 

                           scrollbars=no, resizable=no, copyhistory=no");' />

</center>

</fieldset>

     </form>   

</div>



(6) 로그인 성공후 보여지는 화면(list.jsp)

 <div id="aside">

<fieldset>

<center>

   <label>[ ${secVO.userName} ]님 환영합니다.</label><br/>

   <a href="../logout.do">로그아웃</a>&nbsp;&nbsp;&nbsp;

   <a href="../member/modify.do">정보수정</a>

</center>

</fieldset>

  </div>

 

플로우차트 그리기:

https://www.draw.io/