관리 메뉴

IT.FARMER

HttpSession 중복 로그인 방지 본문

카테고리 없음

HttpSession 중복 로그인 방지

아이티.파머 2014. 11. 6. 17:09
반응형

사용자 중복 로그인 Spring Security 를 사용 중이라면 테그 하나로 간단하게 가능 하다.

하지만 직접 컨트롤 하고 싶다면? Spring Security 에서 사용중인 녀석들을 상속받아 사용 하기도 하지만.

 

다음과 같이 직접 컨트롤 할 수도 있다.

로그인 세션 생성시에  ServletContext servletContext  이녀석에게 값을 sessionID 와 함께 로그인한 userId로 값을 저장한다.  혹은 중복값이 발생되지않게 잘 만들어진 ConcurrentHashMap 을 전역 변수로 사용 하여 이용 한다.

[이렇게 sessionMap.put( session, userCode);]

 

그 뒤에 로그인 할때 와 각 URL 요청시 마다 sessionMap 에서 userId 로 값을 찾아 현재 살아 있는 세션과

비교한뒤 없으면 새롭게 만들어 주고 있으면 이전 sessionId를 sessionMap에서 삭제 함으로 이전 로그인 사용자는 세션에서 제거 시키고 새로 로그인한 사용자만 세션을 유지하는 로그인 중복 처리를 만들수 있다.

 

구현 흐름(flow)을 정해보면 다음과 같다.

1. HttpSessionAttributeListener 상속 구현

1.1. 세션에 값이 추가 될때마다 sessionMap에 중복된 사용자가 없으면 통과 시키며 값을 push 한다.

1.2. 세션에 값이 삭제 될때마다 sessionMap에 해당 sessionID로 사용자의 userId 값이 없으면 중복 로그인된 것으로 판단 하며, 중복 로그인 발생 로그 출력한다.

 

2. 로그인시에 상속 구현한 SessionAttributeListener 녀석으로 부터

    ConcurrentHashMap<HttpSession, String> sessionMap = SessionAttributeListener.sessionMap

    값을 받아와 로그인하려는 user의 sessionId 값이 있는지 체크 한뒤 있으면  oldUserId의 세션을

    sessionMap   에서 삭제 한다.

 

3. 각 호출 마다. HandlerInterceptor 를 상속 받아 구현한 클레스로 부터

  ConcurrentHashMap<HttpSession, String> sessionMap = SessionAttributeListener.sessionMap

  값을 받아와 sessionMap 에 현재 접속 중인 sessionId로 조회 후 값이 있으면 통과 , 없으면 그 sessionId를  

  invalidate() 시켜 세션 만료 한다.

 

예상 시나리오는 다음과 같다.

1. 최초 A Browser 로그인

=> 로그인(2) => 세션값 없음 값추가(1.1) = > 각 페이지마다 sessionId로 sessionMap 값이 존재 하므로 통과(3)

2. 같은 아이디 다른 B Browser (다른 곳에서 ) 로그인

=> 다른곳에서 로그인 이전 로그인 값이 sessionMap 에 존재함을 확인하고 이전것 삭제 (2) => 새로접속한 사용자의 session 을 sessionMap에 등록 (1)=> 각 요청마다 이동 sessionMap 에 값이 있으므로 통과

3. 최초 A브라우저 url 접근

=> 메뉴를 클릭하여 URL 이동 => sessionMap 값이 없으므로 현재 세션을 만료 시키고 로그아웃 함.

4. 2번 사용자 계속 사용 가능.

 

 

예제(참고)

 

LoginController (Provider)

 

로그인 시에 전역 static sessionMap을 선언하여 세션 데이터를 넣어 준다. 

이미 접속한 사용자가 잇는경우 이전 데이터를 Map에서 지워주고(remove), 새로운 데이터를 삽입(put) 한다.

 


ConcurrentHashMap<HttpSession, String> sessionMap = SessionAttributeListener.sessionMap;

  

   // 이미 접속한 사용자(세션) 

  Iterator<HttpSession> iterator = sessionMap.keySet().iterator();

  while (iterator.hasNext()) {

   HttpSession key = (HttpSession) iterator.next();

   dataMap.clear();

   //이미 접속한 사용자(세션)

   dataMap.put("userId", sessionMap.get(key));

   this.duplicationRoleCheck(Session, dataMap , sessionMap, srpCodeList);

   

  }





Enumeration<HttpSession> en =  sessionMap.keys();

   while (en.hasMoreElements()) {

    HttpSession httpSession = (HttpSession) en.nextElement();

    if(sessionMap.get(httpSession).equals(dataMap.get("userId"))){

    
     HttpSession session = httpSession;

     session.getAttribute(httpSession.getId());  

     

     //System.out.println(IpNoUtils.toIpText(SessionOdl.getIpNo()));

     sessionMap.remove(httpSession);

     

    }

   }

 

 

HttpSessionAttribute 

Session이 생성되고 삭제되는 이벤트 리스터 상속 구현 

@Component

public class SessionAttributeListener implements HttpSessionAttributeListener  {

 public static ConcurrentHashMap<HttpSession,String> sessionMap = new ConcurrentHashMap<>();
 private Logger logger = LoggerFactory.getLogger(this.getClass());

public void init(ServletConfig config) {

 }

 ServletContext servletContext;

 @Override

 public void attributeAdded(HttpSessionBindingEvent event) {

  
  HttpSession session = event.getSession();
  String attributeName = event.getName();
  Object attributeValue = event.getValue();
  servletContext = session.getServletContext();

  logger.debug("Attribute added  attributeName = {}  , attributeValue  = {} " ,attributeName , attributeValue);

  if(attributeName.equals("SPRING_SECURITY_CONTEXT")){
  
   if(event.getValue() instanceof SecurityContextImpl) {
   
    SecurityContextImpl securityContextImpl = (SecurityContextImpl) event.getValue();

    Authentication authentication =securityContextImpl.getAuthentication();

    String userCode = authentication.getName();
 
    logger.debug("\n\n\n\n\n\n\n {} userCode = {}" , sessionMap , userCode);

    
    if(sessionMap.get(session) == null) {
    
     sessionMap.put( session, userCode);
    
    } //else {
    
     //String sessionMapUser = sessionMap.get(session);

     //logger.debug("{}는 중복권한으로 로그 아웃 되었습니다." , sessionMapUser);

    //}

   }

  }

 }

 @Override

 public void attributeRemoved(HttpSessionBindingEvent event) {

  String attributeName = event.getName();
  Object attributeValue = event.getValue();
  HttpSession session = event.getSession();
  servletContext = session.getServletContext();
  session.getServletContext().getSessionCookieConfig();

  logger.debug("Attribute added  attributeName = {}  , attributeValue  = {} " ,attributeName , attributeValue);
 

  if (attributeName.equals("SPRING_SECURITY_CONTEXT")) {
   SecurityContextImpl securityContextImpl = (SecurityContextImpl) event.getValue();
   Authentication authentication =securityContextImpl.getAuthentication();
   String userCode = authentication.getName();
   logger.debug("\n\n\n\n\n\n attributeRemoved userCode = {}" , userCode);
   ApplicationContext context =  WebApplicationContextUtils.getWebApplicationContext(session.getServletContext());
   AuditLogDao AuditDao = (AuditLogDao) context.getBean("AuditDao");

   if(sessionMap.get(session) == null) {
    StringBuffer logsb = new StringBuffer();
    logsb.append(" [권한 검증 - 실패] <br>");
    logsb.append(" 원인 : 중복권한 로그인 발생");

    Session Session = (Session)authentication.getDetails();
    AuditDao.insert( Session, AuditLog.TYPE_INSERT, new PermissionVerification() , null , logsb.toString() );

    logger.debug("\n {} 는 중복권한으로 로그 아웃 되었습니다." , userCode);
    logger.debug("\n\n\n\n\n\n\n Session = {}" , Session);

   } else {

    sessionMap.remove(session);

   }
  }

  //쿠키 삭제 

 }

 @Override
 public void attributeReplaced(HttpSessionBindingEvent event) {

 }

}

 

Intercepter

인터 셉터에서 세션이 없으면 로그인 페이지로 이동 

public class DuplicationCheckInterceptor implements HandlerInterceptor{


 private Logger logger = LoggerFactory.getLogger( this.getClass() );
 static final String[] EXCLUDE_URL_LIST = { "/login", "/logout", "/assets", "/errorpages", "/WEB-INF", "/auth/auth_form.do", "/sdefaultpwd" };

 private static final String REDIRECT_URL = "/auth/auth_form.do?resultMessage=DUPLICATION_ROLE";

 ServletContext servletContext;

 @Override

 public boolean preHandle(HttpServletRequest request,

   HttpServletResponse response, Object handler) throws Exception {

  String reqUrl = ((HttpServletRequest) request).getRequestURL().toString();

  for( String target : EXCLUDE_URL_LIST )  {

   if( reqUrl.indexOf( target ) > -1 ) {

    return true;
   }
  }

  

  ConcurrentHashMap<HttpSession, String > sessionMap = SessionAttributeListener.sessionMap;

  Session Session = SessionUtils.getSession();
  HttpSession session = request.getSession();
  
  if(sessionMap.get(session) == null) {

   //StringBuffer logsb = new StringBuffer();

   //logsb.append(" [권한 검증 - 실패] <br>");

   //logsb.append(" 원인 : 중복권한 로그인 발생");

   //AuditDao.insert( Session, AuditLog.TYPE_INSERT, new PermissionVerification( IpNoUtils.toIpNo(request.getRemoteAddr()) ) , null , logsb.toString() );
  

   // Cookie
   CookieUtils.deleteCookie( request, response, "-TOKEN-COOKIE" );

   // Session
   request.getSession().invalidate();

   // Login Page
   throw new ModelAndViewDefiningException(new ModelAndView("REDIRECT_URL"));
   
   return false;

  } else {

   // servletContext.setAttribute(Session.getUser().getUserCode(),  );

  }

  return true;

 }


 @Override
 public void postHandle(HttpServletRequest request,
   HttpServletResponse response, Object handler,
   ModelAndView modelAndView) throws Exception {
 }

 @Override
 public void afterCompletion(HttpServletRequest request,
   HttpServletResponse response, Object handler, Exception ex)
   throws Exception {

  
 }

}

 

반응형