새소식

반응형
Java/Spring Boot

Spring Security ConcurrentSessionControlAuthenticationStrategy 설정(동시세션제어 설정)

  • -
728x90
반응형

Spring Security를 사용하여 커스텀 필터를 생성하지 않고 로그인 인증을 진행할때 AbstractAuthenticationProcessingFilter 를 상속받은 UsernamePasswordAuthenticationFilter 에 의해 진행된다.

본인은 AbstractAuthenticationProcessingFilter 를 직접 상속받아 커스텀 필터를 만들었다.

 

특정 계정이 로그인 되어 있는 상황에서 다른 브라우저에서 로그인을 진행하면 최대 세션 개수에 의해 로그아웃이 진행되었어야 했다. 설정은 아래와 같다.

maximumSession(1) 설정에 의해 최대 세션 개수는 1개이고 maxSessionsPreventsLogin(false) 로

이전 사용자 만료 설정으로 설정했다.

이전 사용자 만료 설정은 이미 로그인 되어 있는 계정일 경우 세션만료 시키고 새로운 세션으로 로그인 된다.

 

이러한 설정으로 인해 당연히 자동 세션만료가 될 줄 알았지만 그렇지 않았다.

 

천천히 이유를 살펴보기 위해 따라가보니 SessionAuthenticationStrategy 인스턴스 설정에서 문제가 있던거 같다.

(제 개인적인 판단입니다.)

 

SessionManagementFilter 와 ConcurrentSessionFilter

일단 세션 제어를 하는 필터는 SessionManagementFilter 이며, 동시적 세션 제어를 하는 필터는

ConcurrentSessionFilter 이다.

이 두개의 필터가 연계하여 세션을 만료시키고 동시적 세션을 제어한다.

 

SessionManagementFilter 의 4가지 기능

1. 세션관리 : 인증 후 사용자의 세션 관리(등록, 조회, 삭제)

2. 동시 세션 제어 : 동일 계정에 대한 세션 관리

3. 세션 고정 보호 : 인증 후 세션 쿠키를 재발급하여 세션 쿠키 조작 공격을 방지

4. 세션 생성 정책 : Always, If_Required, Never, Stateless

 

여기서 동시 세션 제어를 위한 연계 필터는 ConcurrentSessionFilter이다.

출처 : https://velog.io/@dailylifecoding/spring-security-session-control-filter

ConcurrentSessionFilter에 의해 최대 세션 허용 개수가 초과되었을 경우

.maxSessionsPreventsLogin() 설정의 default 값인 false 로 인해 이전 사용자 세션이 만료된다.

ConcurrentSessionFilter는 모든 요청마다 세션 만료 체크 및 세션 만료 시 해당 세션을 만료시키는

역할을 한다.

 

상세 코드 흐름

출처 : https://velog.io/@dailylifecoding/spring-security-session-control-filter

 

실제 SessionManagementFilter 와 ConcurrentSessionFilter 는 위 이미지와 같이 동작한다.

SessionManagementFilter 는 AbstractAuthenticationProcessingFilter 에 의해 사용자가 인증된 후에

동작하는데 SessionAuthenticationStrategy 세션 관련 전력에 따라 세션을 관리한다.

 

AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter 의 doFilter 메서드를 살펴보자.

인증이 시작되면 doFilter에서 세션관련 작업을 위해 메서드를 호출한다.

 

 

CompositeSessionAuthenticationStrategy

on Authentication 메서드에서 for 문을 통해 모든 SessionAuthenticationStrategy 를 불러와

onAuthentication() 메서드를 호출하는걸 볼 수 있다.

 

아래는 CompositeSessionAuthenticationStrategy 의 Spring Document이다.

CompositeSessionAuthenticationStrategy 는 Session 과 관련된 Strategy(전략)들을 모으고,

해당 전략들을 차례대로 호출 및 위임하여 작업을 진행한다.

 

 

ConcurrentSessionControlAuthenticationStrategy

아래는 ConcurrentSessionControlAuthenticationStrategy 의 onAuthentication 메서드이다.

ConcurrentSessionControlAuthenticationStrategy 에서는 현재 사용자의 maximumSession(최대 세션 수)를

가져오고 sessionRegitstry 를 사용하여 현재 사용자의 세션 개수도 가져온다.

이 둘을 비교해 최대 세션수를 넘지 않을때와 넘었을때의 로직을 확인할 수 있다.

 

 

동시 세션 제어를 못했던 원인

Spring Security 의 기본 인증 필터인 UsernamePasswordAuthenticationFilter 를 사용하지 않고

AbstractAuthenticationProcessingFilter 를 직접 상속받아 커스텀 필터를 적용했다.

 

아래와 같이 Spring Security를 통해 최대 세션 개수(maximumSessions(1))를 설정했고

세션만료정책(maxSessionsPreventsLogin(false)) 을 설정했다.

 

최대세션개수가 1개 이므로 로그인 되어 있는 계정은 로그인이 만료되고 새로 로그인이 된다.

만료되지 않았던 이유를 찾기 위해 debug 모드에서 break point 를 찍어봤다.

 

AbstractAuthenticationProcessingFilter 의 doFilter에서 Session 에 관련된곳에 break point 를 찍었다.

 

여기까진 잘 들어온다. 

그 다음 Session 에 관련된 모든 Strategy(전략) 를 불러와 호출하여 로직을 수행하는

CompositeSessionAuthenticationStrategy 에 break point 를 찍어봤으나 나오지 않았다.

 

 

 

아래의 this.sessionStrategy 의 인스턴스 생성한 곳에 가보자

 

아래와 같이 NullAuthenticatedSessionStrategy() 를 생성한걸 볼 수 있다.

 

 

위 NullAuthenticatedSessionStrategy Class 에 break point를 찍어보니 해당 클래스로 들어오는걸 확인할 수 있다.

아무 로직이 없는 onAuthentication 메서드로 들어오다보니 세션에 관련된 설정이 동작 안하는거 같다.

 

 

설정 방법

커스텀 필터에 세션 관련된 모든 Strategy(전략)를 호출하여 로직을 수행하는 

CompositeSessionAuthenticationStrategy 를 bean 으로 등록해 직접 설정해준다.

@Bean 

SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}

@Bean
CompositeSessionAuthenticationStrategy sessionCompositeStrategy() {
    ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlAuthenticationStrategy=
         new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());

    concurrentSessionControlAuthenticationStrategy.setMaximumSessions(1);
    concurrentSessionControlAuthenticationStrategy.setExceptionIfMaximumExceeded(false);

    RegisterSessionAuthenticationStrategy registerSessionAuthenticationStrategy = 
                     new RegisterSessionAuthenticationStrategy(sessionRegistry());
        
        CompositeSessionAuthenticationStrategy sessionAuthenticationStrategy =
          new CompositeSessionAuthenticationStrategy(
                Arrays.asList(
                     concurrentSessionControlAuthenticationStrategy, registerSessionAuthenticationStrategy)

                );
    return sessionAuthenticationStrategy;
}

 

위처럼 CompositeSessionAuthenticationStrategy 생성할때 세션 동시적제어를 하는

ConcurrentSessionControlAuthenticationStrategy 와 sessionRegistry에 새로운 세션을 등록하는 RegisterSessionAuthenticationStrategy 를 설정하고 등록한다.

 

RegisterSessionAuthenticationStrategy 은 일반적으로 ConcurrentSessionAuthenticationStrategy 와 

같이 쓰인다고 아래 설명한다. 동시적 세션 제어가 필요 없을땐 단독으로 사용 가능하지만

동시적 세션 제어가 필요할땐 같이 쓰인다고 한다.

 

동시적 세션 제어를 위해 RegisterSessionAuthenticationStrategy 클래스가 새로운 세션을 등록하는 역할을 한다.

SessionRegistry registerNewSession 메서드를 통해 새로운 세션을 등록한다.

 

아래의 메서드에서 동일한 sessionId 가 존재하면 삭제한 후 map 인 sessionIds 에 put 을 통해 세션을 생성한다.

 

아래처럼 커스텀필터를 설정한곳에서 setSessionAuthenticationStrategy() 를 통해 설정해준다.

@Bean
public AuthenticationUserProcessingFilter AuthUserProcessingFilter() {
    AuthenticationUserProcessingFilter filter = new AuthenticationUserProcessingFilter("/auth/loginCheck");
    filter.setAuthenticationSuccessHandler(authenticationSuccessFilter());
    filter.setAuthenticationFailureHandler(authenticationFailHandler());
    filter.setAuthenticationManager(authenticationManager());
    filter.setSessionAuthenticationStrategy(sessionCompositeStrategy());
    return filter;
}

 

 

ConcurrentSessionFilter

ConcurrentSessionFilter 는 사용자의 모든 요청마다 Session 만료 여부를 체크하는 필터이다.

doFilter 메서드를 통해 사용자의 세션정보를 불러와 존재하면 만료 여부 체크를 한다.

 

아래의 getSessionInformation() 메서드를 통해 사용자의 세션정보를 불러온다.

 

isExpired() 메서드를 통해 세션 만료 여부를 체크한다.

 

동일 세션 제어일 경우 아래처럼 expireNow() 를 요청하면 expired 값이 true 로 변경하여 만료된다.

 

그 다음 doLogout() 메서드를 통해 LogoutHandler 를 호출하여

현재 security 에 로그인 되어 있는 인증 객체(Authentication)를 가져와 logout 시킨다.

 

728x90
반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.