ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring MVC - Session을 직접 구현하여 로그인 처리
    Spring/Spring MVC 2022. 2. 8. 20:34
    728x90
    반응형

     

     

     

    Session을 직접 구현하여 로그인 처리

     Cookie를 이용하여 로그인 처리를 하게 되면 여러 가지 보안 이슈가 있었다. 이 문제를 해결하기 위해서는 로그인 정보(노출되면 안 되는 중요한 정보)는 모두 서버에 저장해야 되며 저장된 정보를 매핑할 수 있는 임의의 토큰(Sessionid)을 부여한다.

     

     

    Session 동작 방식

     

    1. 로그인 : 사용자가 로그인 정보를 전달하면 서버에서 해당 사용자가 맞는지 확인한다.
    2. Session 생성 : UUID를 이용하여 세션 ID를 생성한다.
                         생성된 세션 ID와 세션에 로그인 정보를 서버의 세션 저장소에 보관한다.
    3. Sessionid를 응답 쿠키로 전달 : 클라이언트와 서버는 결국 쿠키로 연결이 되어야 한다.
                                              서버는 클라이언트에게 세션 ID만 쿠키에 담아서 전달한다.
                                              클라이언트는 쿠키 저장소에 쿠키를 보관
                                              로그인과 관련된 정보는 전달하지 않았다는 것이 포인트
    4. 클라이언트의 요청 시 Sessionid가 저장된 쿠키 전달
           - 클라이언트는 요청할 때마다 Sessionid가 저장된 쿠키를 전달한다.
           - 서버는 전달받은 쿠키에서 Sessionid를 이용하여 세션 저장소를 조회하여
             로그인 정보를 사용한다.

     

    Cookie와 Session의 차이

     

    Cookie Session
     요청 시 쿠키 값을 변조 가능  복잡한 Sessionid를 사용하여 변조가 무의미
     클라이언트에서 쿠키 정보 해킹으로 인한 정보 유출  쿠키에는 Sessionid만 저장되어 있으므로 상관 x
     ( 중요한 정보들은 서버에 저장되어있다)
     쿠키 탈취 후 사용 요청에 사용  Sessionid는 시간이 지나면 사용할 수 없도록 세션의
     만료시간을 짧게 유지한다. (default 유지 시간 30분)
     또한 해킹이 의심되는 경우 서버에서 해당 세션을 제거

     

     

    Session을 직접 구현하기

     세션을 직접 구현하기 위해서는 크게 3가지 기능을 제공하면 된다. 지금 구현하고자 하는 Session은 실제 사용하는 Session과 로직이 거의 비슷하므로 알아두는 것이 좋다.

    • 세션 생성
    • 세션 조회
    • 세션 만료

     

    public static final String SESSION_COOKIE_NAME = "mySessionId";
    private Map<String, Object> sessionStore = new ConcurrentHashMap<>();

     ▶ sessionId의 key로 사용할 상수저장소 선언
        (key = SESSION_COOKIE_NAME, 저장소 = sessionStore)

     

    세션 생성

     

    • sessionid 생성 (추정 불가능한 랜덤 값)
    • 세션 저장소에 sessionid와 로그인 정보를 저장
    • sessionid로 응답 쿠키를 생성하여 클라이언트에 전달

     

    public void createSession(Object value, HttpServletResponse response) {
    
        //세션 id를 생성하고, 값을 세션에 저장
        String sessionId = UUID.randomUUID().toString();
        sessionStore.put(sessionId, value);
    
        //쿠키 생성
        Cookie mySessionCookie = new Cookie(SESSION_COOKIE_NAME, sessionId);
        response.addCookie(mySessionCookie);
    }

     

    세션 조회

     

    • 클라이언트가 요청한 sessionId 쿠키의 값으로, 세션 저장소에 보관한 값 조회

     

    public Object getSession(HttpServletRequest request) {
        Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
        if (sessionCookie == null) {
            return null;
        }
        return sessionStore.get(sessionCookie.getValue());
    }

     

    세션 만료

     

    • 클라이언트가 전달한 세션 쿠키로, 세션 저장소에 보관된 sessionid와 값 제거

     

    public void expire(HttpServletRequest request) {
        Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
        if (sessionCookie != null) {
            sessionStore.remove(sessionCookie.getValue());
        }
    }
    
    
    public Cookie findCookie(HttpServletRequest request, String cookieName) {
        if (request.getCookies() == null) {
            return null;
        }
        return Arrays.stream(request.getCookies())
                .filter(cookie -> cookie.getName().equals(cookieName))
                .findAny()
                .orElse(null);
    }

     

    더보기
    /**
     * 세션 관리
     */
    @Component
    public class SessionManager {
    
        public static final String SESSION_COOKIE_NAME = "mySessionId";
        private Map<String, Object> sessionStore = new ConcurrentHashMap<>();
    
        /**
         * 세션 생성
         */
        public void createSession(Object value, HttpServletResponse response) {
    
            //세션 id를 생성하고, 값을 세션에 저장
            String sessionId = UUID.randomUUID().toString();
            sessionStore.put(sessionId, value);
    
            //쿠키 생성
            Cookie mySessionCookie = new Cookie(SESSION_COOKIE_NAME, sessionId);
            response.addCookie(mySessionCookie);
        }
    
        /**
         * 세션 조회
         */
        public Object getSession(HttpServletRequest request) {
            Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
            if (sessionCookie == null) {
                return null;
            }
            return sessionStore.get(sessionCookie.getValue());
        }
    
        /**
         * 세션 만료
         */
        public void expire(HttpServletRequest request) {
            Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
            if (sessionCookie != null) {
                sessionStore.remove(sessionCookie.getValue());
            }
        }
    
    
        public Cookie findCookie(HttpServletRequest request, String cookieName) {
            if (request.getCookies() == null) {
                return null;
            }
            return Arrays.stream(request.getCookies())
                    .filter(cookie -> cookie.getName().equals(cookieName))
                    .findAny()
                    .orElse(null);
        }
    
    }

     

    세션 테스트

     

    class SessionManagerTest {
    
        SessionManager sessionManager = new SessionManager();
    
        @Test
        void sessionTest() {
    
            //세션 생성
            MockHttpServletResponse response = new MockHttpServletResponse();
            Member member = new Member();
            sessionManager.createSession(member, response);
    
            //요청에 응답 쿠키 저장
            MockHttpServletRequest request = new MockHttpServletRequest();
            request.setCookies(response.getCookies());
    
            //세션 조회
            Object result = sessionManager.getSession(request);
            assertThat(result).isEqualTo(member);
    
            //세션 만료
            sessionManager.expire(request);
            Object expired = sessionManager.getSession(request);
            assertThat(expired).isNull();
    
        }
    }

     

    세션 정보 조회

     

    @Slf4j
    @RestController
    public class SessionInfoController {
    
        @GetMapping("/session-info")
        public String sessionInfo(HttpServletRequest request) {
            HttpSession session = request.getSession(false);
            if (session == null) {
                return "세션이 없습니다.";
            }
            // 세션 id와 저장된 객체 정보 출력
            System.out.println(session.getId() + ", " + session.getAttribute("loginMember"));
    
            //세션 데이터 출력
            session.getAttributeNames().asIterator()
                    .forEachRemaining(name -> log.info("session name={}, value={}", name, session.getAttribute(name)));
    
            log.info("sessionId={}", session.getId());
            log.info("getMaxInactiveInterval={}", session.getMaxInactiveInterval());
            log.info("creationTime={}", new Date(session.getCreationTime()));
            log.info("lastAccessedTime={}", new Date(session.getLastAccessedTime()));
            log.info("isNew={}", session.isNew());
    
            return "세션 출력";
    
        }
    }

     

     

     

    728x90
    반응형

    댓글

Designed by Tistory.