ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 서블릿의 세션 관리 (Servlet Session Management)
    Java 2021. 2. 21. 16:35
    반응형

    세션은 특정 시간의 구간을 의미하며, 세션 관리는 사용자의 상태를 유지하는 방법을 말합니다. 이러한 세션관리가 필요한 이유는 사용자에게 상태를 부여해야할 경우가 존재하기 때문입니다. 예로, 사용자 상태를 관리하여 매 페이지를 이동할 때마다 로그인을 요구하지 않고 세션을 통해 이미 로그인 한 사용자임을 입증할 수 있습니다.

     

    그러나, HTTP 프로토콜은 그 자체로 stateless하기 때문에, HTTP 프로토콜 '바깥'에서 이러한 상태 관리를 담당하여야 합니다. 서블릿의 명세 상에서 이러한 세션 관리는 톰캣과 같은 서블릿 컨테이너가 담당합니다. '톰캣 내부구조'에서 살펴본 바와 같이, 세션은 주로 아래의 2가지 방법으로 관리됩니다:

     

    • 브라우저 상의 쿠키를 통해서
    • URL Rewriting을 통해서

    이번 글에서는 위 2가지 방법이 톰캣을 통한 HTTP 요청 / 응답 상에서 어떻게 구현되어있는지 코드레벨로 살펴보도록 하겠습니다.

     

    • Tomcat이 HTTP 요청을 받고 나서 요청 객체에 세션 ID를 설정하기까지
    • 요청 및 응답 객체에서 세션을 사용하는 방식
    • Manager의 createSession

     


     

    Tomcat이 HTTP 요청을 받고 나서 요청 객체에 세션 ID를 설정하기까지

    톰캣이 HTTP 요청을 받고 HTTP 요청 처리를 담당하는 Coyote Http11Processor가 Apapter의 service 메소드를 호출하게 됩니다. 이 경우 어댑터는 CoyoteAdapter이며, CoyotAdapater의 service 메소드는 아래와 같은 postParseRequest 메소드를 호출하게 됩니다:

     

    public class CoyoteAdapter implements Adapter {
        // ...
        protected boolean postParseRequest(org.apache.coyote.Request req, Request request,
                org.apache.coyote.Response res, Response response) throws IOException, ServletException {
            // ...
            
                // Now we have the context, we can parse the session ID from the URL
                // (if any). Need to do this before we redirect in case we need to
                // include the session id in the redirect
                String sessionID;
                if (request.getServletContext().getEffectiveSessionTrackingModes()
                        .contains(SessionTrackingMode.URL)) {
    
                    // Get the session ID if there was one
                    sessionID = request.getPathParameter(
                            SessionConfig.getSessionUriParamName(
                                    request.getContext()));
                    if (sessionID != null) {
                        request.setRequestedSessionId(sessionID);
                        request.setRequestedSessionURL(true);
                    }
                }
    
                // Look for session ID in cookies and SSL session
                try {
                    parseSessionCookiesId(request);
                }
                //...
            }
        }           
    }

    이 postParseRequest 메소드에서 하나의 요청에 대한 세션 설정 또는 파싱이 발생하게 됩니다.

     

    먼저, 세션트래킹모드 리스트가 URL 모드(SessionTrackingMode.URL)를 포함하면 URL Path를 파싱해 세션 ID를 찾아보고, 세션 ID가 존재하면 요청에 설정하고 프로퍼티를 변경해줍니다.

     

    이후에 parseSessionCookiesId 메소드를 통해서, 쿠키를 통한 세션 ID 찾기를 시도합니다. 

    만약 요청 상에서 쿠키가 발견되고, 같은 쿠키명이 서버에서도 발견된다면 아래와 같이 요청의 쿠키를 통한 세션 프로퍼티는 키고 (true), URL을 통한 세션 프로퍼티는 끄는 (false) 것을 볼 수 있습니다:

    protected void parseSessionCookiesId(Request request) {
        // ...
            request.setRequestedSessionId
                            (scookie.getValue().toString());
            request.setRequestedSessionCookie(true);
            request.setRequestedSessionURL(false);
        
        // ...
    }

     

    setRequestSessionId는 단순히 요청 객체의 프로퍼티에 아이디를 설정해줍니다:

    public void setRequestedSessionId(String id) {
    
        this.requestedSessionId = id;
    
    }

     

    요청 및 응답 객체에서 세션을 사용하는 방식

    요청 객체의 getSession(), getSession(boolean create), getSessionInternal(), getSessionInternal(boolean create)과 같은 메소드들은 객체 외부에서 요청 객체의 세션에 접근할 때 사용하게 됩니다. 

     

    4가지 메소드 모두 아래 doGetSession(boolean create) 메소드를 호출하여 세션을 리턴해주고 있습니다. doGetSession에서는 컨텍스트에서 매니저를 가져와서, manager.findSession 메소드를 통해 세션을 찾아 적절하다면 리턴하는 것을 볼 수 있습니다. 그리고 만약 부적절하고 create이 true라면 요청한 세션 ID를 재사용하거나 새로운 세션 아이디가 부여된 세션을 생성합니다 (manager.createSession 메소드). 

     

    이후 쿠키를 통한 트래킹모드라면 응답 객체에 쿠키를 더하는 코드까지 있는 것을 볼 수 있습니다.

    public class Request implements HttpServletRequest {
        // ...
        protected Session doGetSession(boolean create) {
    
            // ...
    
            Manager manager = context.getManager();
            if (manager == null) {
                return null;      // Sessions are not supported
            }
            if (requestedSessionId != null) {
                try {
                    session = manager.findSession(requestedSessionId);
                } catch (IOException e) {
                    // ...
                    session = null;
                }
                if ((session != null) && !session.isValid()) {
                    session = null;
                }
                if (session != null) {
                    session.access();
                    return session;
                }
            }
    
            // Create a new session if requested and the response is not committed
            if (!create) {
                return null;
            }
            // ...
    
            session = manager.createSession(sessionId);
    
            // Creating a new session cookie based on that session
            if (session != null && trackModesIncludesCookie) {
                Cookie cookie = ApplicationSessionCookieConfig.createSessionCookie(
                        context, session.getIdInternal(), isSecure());
    
                response.addSessionCookieInternal(cookie);
            }
    
            if (session == null) {
                return null;
            }
    
            session.access();
            return session;
        }
    }

     

    Manager의 createSession

    위 doGetSession을 살펴보면 manager.createSession(sessionId)를 통해서 세션을 생성하는 것을 볼 수 있습니다. 아래 ManagerBase의 createSession 메소드에서 그 내부를 살펴볼 수 있습니다:

     

    public abstract class ManagerBase extends LifecycleMBeanBase implements Manager {
        // ...
        @Override
        public Session createSession(String sessionId) {
    
            if ((maxActiveSessions >= 0) &&
                    (getActiveSessions() >= maxActiveSessions)) {
                rejectedSessions++;
                throw new TooManyActiveSessionsException(
                        sm.getString("managerBase.createSession.ise"),
                        maxActiveSessions);
            }
    
            // Recycle or create a Session instance
            Session session = createEmptySession();
    
            // Initialize the properties of the new session and return it
            session.setNew(true);
            session.setValid(true);
            session.setCreationTime(System.currentTimeMillis());
            session.setMaxInactiveInterval(getContext().getSessionTimeout() * 60);
            String id = sessionId;
            if (id == null) {
                id = generateSessionId();
            }
            session.setId(id);
            sessionCounter++;
    
            SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);
            synchronized (sessionCreationTiming) {
                sessionCreationTiming.add(timing);
                sessionCreationTiming.poll();
            }
            return session;
        }
        
        @Override
        public Session createEmptySession() {
            return getNewSession();
        }
        
        // ...
        
        /**
         * Get new session class to be used in the doLoad() method.
         * @return a new session for use with this manager
         */
        protected StandardSession getNewSession() {
            return new StandardSession(this);
        }
        
        // ...
    }

     

     

     

     

    Reference

    [1] Session Tracking in Servlet

    [2] serverStartup.txt

    [3] What is Session in Java?

    [4] Session Managers

    [5] https://stackoverflow.com/questions/3106452/how-do-servlets-work-instantiation-sessions-shared-variables-and-multithreadi

    반응형
Kaden Sungbin Cho