저작권 안내: 저작권자표시 Yes 상업적이용 No 컨텐츠변경 No

스프링5 입문

JSP 2.3

JPA 입문

DDD Start

인프런 객체 지향 입문 강의

'접속목록구현'에 해당되는 글 2건

  1. 2001.08.31 웹 프로그래밍 테크닉 Part IV
  2. 2001.08.23 웹 프로그래밍 테크닉 Part III
3부에 이어 계속해서 접속 중인 사용자 리스트의 실질적인 구현 방법을 알아본다.

다중 접속에 관하여

다중 접속을 허용하는가 금지하는가는 서버 운영자가 선택하여야 한다. 지금까지의 코드로는 여러 곳에서 같은 아이디로 접속하는 것을 허용하고 있다. 만약에 다중 접속을 막고 싶으면 어떻게 해야 할까.

몇가지 방법을 생각해볼 수 있다. 일단 접속 중인 사용자 정보를 어디에서부터 확인할 수 있을까. 특별하게 엄청난 코드를 추가하지 않아도 두 곳에 사용자 정보가 이미 저장되고 있다는 사실을 알 수 있다.

데이터베이스와 LoginUserList 객체

첫번째는 데이터베이스에 로그인 중이라는 상태를 나타내는 필드를 추가하는 방법이 있을 수 있다. MemberManager 클래스의 login() 메소드가 호출될 때 자동으로 이 필드를 true 또는 1 등의 값으로 설정하여 로그인 상태라는 것을 표시한다. 그리고 logout() 메소드가 호출될 때 이 필드를 false 또는 0 등의 값으로 설정하여 로그아웃 상태라는 것을 표시한다.

두번째는 접속 중인 사용자 리스트를 유지하고 있는 LoginUserList 클래스로부터 로그인 상태를 확인하는 방법이다. 이미 로그인 중인 사용자 리스트를 관리하는 클래스를 사용한다면 별로도 데이터베이스로부터 로그인 상태를 확인하기 위한 작업을 할 필요가 없다. 따라서 LoginUserList 클래스로부터 접속 중인지 아닌지 확인하는 방법을 찾아보자.

isLoginUser() 메소드

이미 LoginUserList 클래스에는 isLoginUser() 메소드가 있다. 또한 MemberManager 클래스에도 이 메소드를 호출할 수 있는 메소드가 똑같이 존재한다. 그렇다면 login() 메소드에서 로그인을 시도할 때 isLoginUser() 메소드를 호출하여 접속 중인지 간단히 미리 확인할 수 있다.

이 때, 시나리오를 먼저 생각해볼 필요가 있다. 로그인을 시도했을 때 이미 접속 중일 경우

  1. 기존의 접속을 강제로 끊는다.
  2. 기존의 접속을 유지하고 로그인을 거부한다.
  3. 선택할 수 있도록 한다.
중요하진 않지만 언제나 이런 상황에서는 실제 시나리오를 생각해볼 필요가 있다. 이 경우는 기존의 접속을 강제로 끊기로 한다.

그러나, 실제적인 문제는 이제부터 발생한다. 로그인/로그아웃 정보는 세션에서 유지한다. 다른 곳에서 로그인한 경우, 그 로그인 정보는 해당 세션 객체에서 유지되므로, 그 세션을 접근할 수 있어야 한다. 그러나, 지금까지의 코드로는 다른 세션을 접근할 방법이 없다.

어떤 사람은 HttpSessionContext 클래스를 이용하면 될 것이라고 생각할지 모르지만 이 클래스는 서블릿 2.1 규약에서부터 보안상의 이유로 디프리케이티드되었다. 따라서 이 클래스는 사용하지 않는 것이 옳다.

LoginBean 인터페이스와 LoginBeanImpl 클래스의 수정

가장 쉬운 방법은 LoginBean 클래스가 스스로 세션에 대한 레퍼런스를 보관하는 방법이다. 간단히 LoginBean 인터페이스와 LoginBeanImpl 클래스를 수정한다. LoginBean 인터페이스에는 다음과 같은 메소드를 추가한다.

   public HttpSession getSession();

그리고, LoginBeanImpl 클래스는 LoginBean 인터페이스를 상속하므로 당연히 getSession() 메소드를 구현하기 위하여 다음과 같은 코드를 추가한다.

   private HttpSession session = null;

   public HttpSession getSession() {
      return session;
   }

LoginBeanImpl 클래스에서 getSession() 메소드의 session 값은 어디서 설정해야 할까. 당연히 valueBound() 메소드이다. valueBound() 메소드에는 다음과 같은 코드가 추가되야 한다.

   session = event.getSession();

또한, 같은 이유로 valueUnbound() 메소드에는 다음과 같은 코드가 추가된다.

   session = null;

결과적으로 LoginBeanImpl 클래스의 완전한 소스는 다음과 같다.

   public class LoginBeanImpl implements LoginBean, HttpSessionBindingListener {
      
      private LoginBeanBindingListener listener = null;
      private String id = null;
      private HttpSession session = null;
      
      public LoginBeanImpl(String id, LoginBeanBindingListener listener) {
         this.id = id;
         this.listener = listener;
      }
      
      public String getId() {
         return id;
      }
   
      public HttpSession getSession() {
         return session;
      }
   
      public void valueBound(HttpSessionBindingEvent event) {
         session = event.getSession();
         if (listener != null) 
            listener.loginPerformed(new LoginBeanBindingEvent(this));
      }
   
      public void valueUnbound(HttpSessionBindingEvent event) {
         session = null;
         if (listener != null) 
            listener.logoutPerformed(new LoginBeanBindingEvent(this));
      }
   }

LoginUserList 클래스의 수정

다음은 LoginUserList 클래스에서 주어진 아이디에 해당하는 LoginBean 객체를 얻을 수 있는 getLoginBean() 메소드를 추가하면 된다. getLoginBean() 메소드의 코드는 다음과 같다.

   public LoginBean getLoginBean(String id) {
      try {
         return (LoginBean)users.get(id);
      } catch (Exception ex) {
         return null;
      }
   }

MemberManager 클래스의 login() 메소드의 수정

이제 마지막으로 MemberManager 클래스의 login() 메소드를 수정한다.

   public void login(HttpSession session, String id, String password)
               throws NoSuchMemberException,
                      InvalidPasswordException,
                      ServiceNotActiveException {
      accessor.checkPassword(id, password);
      // 다중 접속을 막기 위해 추가된 if 문장 
      if (isLoginUser(id)) {
         LoginBean lb = users.getLoginBean(id);
         lb.getSession().removeAttribute(LOGIN_BEAN);
      }
      LoginBean login = new LoginBeanImpl(id, this);
      session.setAttribute(LOGIN_BEAN, login);
   }

이제 로그인 중인 사용자가 다른 곳에서 같은 아이디로 정상적으로 접속할 경우 이미 로그인한 세션에 대해 자동적으로 로그아웃을 시키게 된다.

결론

Part IV 에서는 접속 중인 사용자가 다른 곳에서 다중으로 로그인 하는 것을 막는 방법을 제시해보았다. 이로서 4회에 걸쳐 로그인과 로그아웃의 기본적인 구현 알고리즘과 개념, 세션 타임 아웃으로 인한 로그아웃의 구현, 커플링(coupling)을 제거하기 위한 방법, 로그인 유저 리스트의 구현과 접근에 유지 방법, 그리고 다중 접속을 막는 방법 등을 실제로 구현하여보았다.



본 글의 저작권은 이동훈에 있으며 저작권자의 허락없이 온라인/오프라인으로 본 글을 유보/복사하는 것을 금합니다.
Posted by 최범균 madvirus

댓글을 달아 주세요

접속 중인 사용자 리스트의 실질적인 구현 방법을 알아본다.

접속 중인 사용자 리스트 구현

접속 중인 사용자 리스트를 구현하기 위해서는 간단한 클래스를 하나 따로 만드는 것이 편하다. 이 클래스의 이름을 LoginUserList 클래스라고 붙인다. 이제 몇가지 가정을 생각해 본다. 접속 중인 사용자 리스트를 구현할 필요가 있는 것은 몇가지 용도가 있기 때문이다. 예를 들면 어떤 특정 사용자가 접속 중인지도 알 수 있고, 현재 접속 중인 사용자가 몇 명이지도 알 수 있으며 각각의 사용자에 대한 로그인/아웃 기록도 남길 수 있다.

이제 접속 중인 사용자 리스트를 어떻게 관리할 것인가를 생각해 본다. Vector 클래스를 사용해 리스트를 유지할 수도 있다. 하지만 사용자들은 아이디라는 고유의 항목을 가지고 있다는 특징과 이 아이디를 통해 쉽게 원하는 항목을 찾을 수 있다는 점을 생각하면 Hashtable 클래스가 더 적합할 수도 있다. 여기서는 Hashtable 클래스를 이용하기로 한다. 해쉬테이블의 키 값은 사용자의 아이디를 이용한다.

LoginUserList 클래스

간단한 접속 중인 사용자 리스트를 구현하기 위한 LoginUserList 클래스는 다음과 같다.

   public class LoginUserList {
      
      private Hashtable users = null;
   
      public LoginUserList() {
         users = new Hashtable();
      }
   
      public boolean isLoginUser(String id) {
         return users.get(id) != null;
      }   
   
      public Iterator iterator() {
         return users.values().iterator();
      }
   
      public void addLoginUser(LoginBean login) {
         users.put(login.getId(), login);
      }
      
      public void removeLoginUser (LoginBean login) {
         users.remove(login.getId());
      }
   }

LoginUserList 클래스는 간단히 Hashtable 클래스를 이용해 접속 중인 사용자의 리스트를 유지하게 된다. 새로 사용자가 접속할 경우 addLoginUser() 메소드를 통해 LoginUserList 객체에 알려주고 사용자가 로그아웃할 경우 removeLoginUser() 메소드를 통해 LoginUserList 객체에 알려주면 된다.

isLoginUser() 메소드는 접속 중인 사용자인가 알려주는 메소드이며, iterator() 메소드는 접속 중인 사용자의 리스트를 담고 있는 Hashtable 객체의 뷰(view)를 제공해 준다. collection의 view를 모르는 사람은 iterator 대신에 같은 기능의 enumeration 을 사용해도 좋다. 자바 2에서는 enumeration 보다는 iterator 을 추천한다. 그 이유는 여기서 다루기엔 범위가 초과하므로 생략한다.

MemberManager 클래스의 수정

LoginUserList 클래스는 어느 클래스의 어느 메소드에 연결해야 할까? 이미 우리는 그 답을 알고 있다. 바로 MemberManager 클래스이다. MemberManager 클래스는 사용자 로그인/아웃 및 사용자 관련 메소드들의 총 관리를 맡고 있다. 바로 여기가 LoginUserList 클래스가 위치할 곳이다.

MemberManager 클래스에 다름과 같은 필드를 추가한다.

   private LoginUserList users = null;

그리고 생성자에 다음과 같은 코드를 추가한다.

   users = new LoginUserList();

MemberManager 클래스가 싱글톤 패턴으로 단 하나의 객체만 생성되므로 LoginUserList 클래스도 단 한 개의 객체만이 생성되어 사용된다. 이제 실제로 사용자가 로그인하거나 로그아웃할 때 LoginUserList 클래스의 addLoginUser() 메소드와 removeLoginUser() 메소드를 통해 알려주도록 연결하는 코드를 추가하기만 하면 된다. 그 위치는 Part I, II 에서부터 상세히 다루어 온 MemberManager 클래스의 loginPerformed() 메소드와 logoutPerformed() 메소드이다. 이제 두 메소드는 다음과 같은 코드를 추가하게 된다.

   public void loginPerformed(LoginBeanBindingEvent event) {
      users.addLoginUser(event.getLoginBean());
   }

   public void logoutPerformed(LoginBeanBindingEvent event) {
      users.removeLoginUser(event.getLoginBean());
   }

단지 이 두 메소드에 각각 한 줄씩의 코드를 추가하는 것으로 간단히 접속 중인 사용자 리스트를 관리하고 유지할 수 있게 된다. 그러면 서블릿이나 JSP 페이지에서 접속 중인 사용자 리스트를 보고 싶으면 어떻게 이 클래스들을 이용해야 할까. 아직 코드가 완성된 것이 아니다.

서블릿/JSP 페이지에서 LoginUserList 클래스로의 접근

이 부분에 대해서 생각해 볼 필요가 있다. 모든 서블릿 또는 JSP 페이지는 사용자 정보를 얻기 위해서 MemberManager 클래스를 통해서 접근해야만 한다. LoginUserList 클래스도 MemberManager 클래스에 의해 유지되고 관리된다. 따라서 LoginUserList 클래스가 가지고 있는 정보도 MemberManager 클래스를 통해서 얻어야 한다.

그렇다면, MemberManager 클래스로부터 LoginUserList 클래스의 정보를 얻기 위하여 MemberManager 클래스에는 필요한 메소드가 추가되어야 한다. 두가지 방법을 생각할 수 있다.

직접적인 접근

하나는 getLoginUserList() 메소드를 MemberManager 클래스에 추가하여 직접 LoginUserList 객체의 레퍼런스를 받아서 서블릿이나 JSP 페이지에서 이용하는 방법이다.

   public LoginUserList getLoginUserList() {
      return users;
   }

그러나, 이 방법은 캡슐화에 너무 부실하게 된다. 물론 코딩 상에는 문제가 없다. 이 방법보다는 두번째 방법을 추천한다.

간접적인 접근

두번째 방법은 MemberManager 클래스가 LoginUserList 클래스의 랩퍼 클래스 역할을 하는 것이다. 복잡한 것 같지만 오히려 이 방법이 단순하다. LoginUserList 클래스의 메소드 들 중에 서블릿 또는 JSP 페이지에서 필요한 메소드들을 안전한 형태로 MemberManager 클래스에서 중계해 주면 된다.

MemberManager 클래스에 다음과 같은 메소드를 추가한다.

   public Iterator getLoginUserIterator() {
      return users.iterator();
   }

   public boolean isLoginUser(String id) {
      return users.isLoginUser(id);
   }

이제 서블릿 또는 JSP 페이지에서는 손쉽게 다음 코드를 이용하여 접속 중인 사용자 리스트를 볼 수 있다. JSP 페이지라면 다음과 같은 코드가 될 것이다.

   <h2>접속 중인 사용자 리스트<hr></h2>
   <%
      for (Iterator it = MemberManager.getInstance().getLoginUserIterator(); it.hasNext(); ) {
   %>
      <%= it.next().toString() %><br>
   <%
      }
   %>
   <hr>

이와 같은 코드를 작성하면 된다. 나중에 보완한다면 exception 처리를 보완해서 페이지에 오류가 발생하지 않도록 하는 것뿐이다. 이로서 접속 중인 사용자 리스트를 구현하고 필요한 경우 확장할 수 있는 완전한 형태를 제시해보았다.

결론

접속 중인 사용자 리스트를 구현하고 필요한 경우 확장할 수 있는 완전한 형태를 제시해보았다. 이번 파트에 다중 접속을 막는 방법까지 제시하려고 했으나 내용이 길어지고 복잡해지는 것을 피하기 위해 Part IV를 추가하고 다음 파트에서 다루기로 한다.



본 글의 저작권은 이동훈에 있으며 저작권자의 허락없이 온라인/오프라인으로 본 글을 유보/복사하는 것을 금합니다.
Posted by 최범균 madvirus

댓글을 달아 주세요