[Spring Security] 세션 제어 필터 SessionManagementFilter & ConcurrentSessionFilter

식빵·2022년 8월 12일
0
post-thumbnail

이 시리즈에 나오는 모든 내용은 인프런 인터넷 강의 - 스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security - 에서 기반된 것입니다. 그리고 여기서 인용되는 PPT 이미지 또한 모두 해당 강의에서 가져왔음을 알립니다.

그림이 너무 작게 보이면 클릭해서 크게 보시기 바랍니다.




🌴 SessionManagementFilter

이전 글에서 동시 세션 제어, 세션 고정 보호, 세션 생성 정책에 대해서 가볍게
알아봤다. 이번에는 그게 정말 가능하게 해주는 SessionManagementFilter 를 알아보자.


🥝 기능

저번 글에서 배운 것들을 다 한다고 보면 된다.

1. 세션관리
:인증 이후 사용자의 세션정보를 메모리에 등록/조회/삭제(=이력관리),
이 기능을 사용해서 동시세션제어의 세션 수 체크

2. 동시 세션 제어
: 동일 계정 세션 수 제한

3. 세션고정보호
: 매 인증마다 새로운 세션쿠키 발급

4. 세션 생성 정책
: Always, If_Required, Never, Stateless 에 따라 다른 방식으로 세션 생성




🥝 ConcurrentSessionFilter 와 협력

SessionManagementFilterConcurrentSessionFilter와 협력하여
동시 세션 제어를 수행한다.

이 필터는 매 요청마다 사용자의 세션 만료 여부를 체크하고
세션이 만료되었을 경우 즉시 만료 처리한다.




🥝 두 필터의 연계 동작 프로세스

1. 연계 동작 방식

SessionManagementFilterConcurrentSessionFilter의 연계 동작을 알아보자.


2. 코드 흐름

실제 코드상의 프로세스는 아래 그림과 같다.



3. 코드 추적해보기

위 그림의 흐림대로 정말 진행되는지 눈으로 확인해보자.

일단 서로 다른 종류의 브라우저 창을 하나씩 키자.
나는 왼쪽에는 Chrome, 오른쪽에는 FireFox 브라우저를 각각 하나씩 띄웠다.
이 상태에서 먼저 FireFox(오른쪽 브라우저)로 로그인을 시도한다.



3-1. AbstractAuthenticationProcessingFilter

그러면 먼저 인증과 관련된 작업을 돕는 AbstractAuthenticationProcessingFilter 필터가
호출되고 해당 필터에서 세션과 관련된 작업을 위한 메소드를 호출하는 것을 확인할 수 있다.




3-2. CompositeSessionAuthenticationStrategy

인증 필터에서 호출된 메소드는 CompositeSessionAuthenticationStrategy의 메소드이다.
그리고 메소드 내용은 위 그림과 같다.
for 문을 돌면서 SessionAuthenticationStrategy 에게 인증 작업을 위임하는 코드임을 금방 알 수 있다.
그런데 CompositeSessionAuthenticationStrategy 객체의 역할은 뭘까?


위 그림은 CompositeSessionAuthenticationStrategyjava document이다.
대충 읽어보면 알겠지만, Session 과 관련된 Strategy(전략)들을 모으고, 해당 전략들에게
작업을 순차적으로 위임해주는 객체이다.


참고: 실제 디버깅 포인트를 잡아보면?

실제 디버그 포인트를 잡으면 java doc에서 말한 3가지 Strategy(전략)이 보인다.
지금부터는 저 전략들이 수행되는 과정을 따라가보자.
(CsrfAuthenticationStrategy 제외!)



3-3. ConcurrentSessionControlAuthenticationStrategy

CompositeSessionAuthenticationStrategy 의 for 문에서 첫번째로 사용하는
전략인 ConcurrentSessionControlAuthenticationStrategy가 수행된다.
코드를 가볍게 보면, sessionRegistry 라는 곳에서 현재 사용자의 세션 개수를 세오고,
그 세션의 숫자가 allowedSessions를 넘으면 어떤 처리를 하는 것을 알 수 있다.
하지만 지금은 첫 로그인이라서 별문제 없이 그냥 메소드가 return 된다.



3-4. AbstractSessionFixationProtectionStrategy (implements SessionAuthenticationStrategy)

이후로 CompositeSessionAuthenticationStrategy 의 for 문을 돌면서 다음 전략인
AbstractSessionFixationProtectionStrategy가 동작한다.


applySessionFixation(request) 메소드의 내용물은 위와 같다.
세션 아이디를 바꾸는 것을 확인할 수 있다. 이렇게 함으로써 세션고정보호가 되는 것이다.



3-5. RegisterSessionAuthenticationStrategy

위 메소드는 SessionRegistryImpl 인스턴스의 registerNewSession 을 호출한다.

그리고 새로운 세션을 저장하는 것을 확인할 수 있다.



3-6. 다른 브라우저에서 사용자 로그인 시도

이번에는 Chrome 브라우저에서 같은 아이디로 로그인을 해보자.



3-7. 다시 ConcurrentSessionControlAuthenticationStrategy

이전과는 다르게 ConcurrentSessionControlAuthenticationStrategy 에서
allowableSessionsExceeded(sessions, allowedSessions, this.sessionRegistry); 메소드를 호출한다.

이건 if(sessionCount < allowedSessions) 분기문을 통과해서 그런 것이다.
sessionCount 는 이전에 로그인을 해서 1개이고,
allowedSessions 는 우리가 이전에 한 설정에서 http().sessionManagement().maximumSessions(1)
때문에 1개로 고정되어 있다.


해당 메소드의 내용은 위와 같고, this.exceptionIfMaximumExceeded=true여서
SessionAuthenticationException 예외를 던지게 된다.
참고로 exceptionIfMaximumExceeded 는 우리가 이전에 한 설정

http().sessionManagement().maxSessionPreventsLogin(true) 덕분에 이렇게 동작하는 것이다.

만약에 .maxSessionPreventsLogin(false)로 지정하면 처음 들어왔던 사용자의 세션을 만료시킨다.
==> session.expireNow()

일단 지금은 예외가 던져지는 경우를 계속 추적해보겠다.



3-8. 인증필터에서 try-catch 및 unsuccessfulAuthentication 메소드 호출

이전 인증 필터 게시물에서 설명했으니 이번엔 길게 설명 X.



3-9. 두번째 사용자 결국 로그인 실패하고 로그인 페이지로 튕겨냄





이전에 본 3-7 과정의 코드에 디버깅 포인트를 다시 잡고 Spring Security 설정을 위처럼 바꿔주자.
그 다음에 이전처럼 로그인 FireFox 로그인을 성공시키고, Chrome 사용자를 로그인 시켜보자



3-10. ConcurrentSessionControlAuthenticationStrategy

Chrome 사용자가 로그인을 시키면 이전 FireFox 로 로그인한 세션은 expireNow() 에 의해서 만료처리된다.
이후에 다시 FireFox 로 어떤 경로에 접근하려하면...



3-11. ConcurrentSessionFilter

ConcurrentSessionFilter 는 세션 만료를 체킹하는데, 지금은 FireFox 세션이 만료되었으므로
위처럼 info.isExpired 분기문 내부로 들어간다.

doLogout: 로그아웃 처리를 한다(= 로그아웃 핸들러들이 동작한다)

onExpiredSessionDetected 는 세션 Expired 처리 전략을 수행한다.
아무것도 세팅해주지 않았다면 기본적으로 경고 문구를 flush 해버리고 끝이다.


참고: 만약에 Expired 처리를 다르게 하고 싶다면?

.sessionManagement()
.maximumSessions(1)
.maxSessionsPreventsLogin(false)
.expiredSessionStrategy(new SessionInformationExpiredStrategy() {
    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent event) 
    throws IOException, ServletException {
        HttpServletResponse response = event.getResponse();
        response.sendRedirect("/sessionOver");
    }
});

추가적으로 이렇게 페이지 redirect 를 하려면 spring security 로
저 경로(/sessionOver)를 permitAll 해줘야 정상적으로 동작한다!!

profile
백엔드를 계속 배우고 있는 개발자입니다 😊

0개의 댓글