본문 바로가기

Spring Security

[Spring Security] 기본 API 및 Filter 이해(2)

이번 포스팅에서는 스프링 시큐리티를 크게 3가지로 알아보려고 합니다.

  • RememberMeAuthenticationFilter 동작원리
  • AnonymousAuthenticationFilter 동작원리
  • 동시 세션 제어 필터(SessionManagementFilter, ConcurrentSessionFilter) 동작원리
RememberMeAuthenticationFilter 동작원리

RememberMeAuthenticationFilter 필터는 세션에서 응답값으로 보내준 RememberMe 쿠키값을 사용하여 세션이 만료되거나 브라우저가 종료된 후에도 사용자를 기억할 수 있도록 도와주는 필터입니다.

 

RememberMe와 관련된 API는 아래와 같습니다.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .rememberMe()   // rememberMe 기능이 동작하게됨
            .rememberMeParameter("remember")  // 체크박스의 파라미터명(기본 : remember-me)
            .tokenValiditySeconds(3600)   // 토큰의 만료시간(default는 14일)
            .userDetailsService(userDetailsService);  // 사용자 계정을 조회할때 사용하는 클래스
    }
}
  • rememberMe()를 선언하게되면, Login페이지에 자동으로 체크박스가 생성되게 됩니다.
  • 브라우저에서 rememberMe()기능을 사용하여 로그인을 진행한후에 쿠키를 지우더라도 rememberMe쿠키가 살아있기 때문에 로그인 상태가 유지됩니다.

 

RememberMeAuthenticationFilter 필터의 동작원리를 살펴보면 아래와 같은 Flow로 진행되는 것을 알 수 있습니다.

이 필터에 동작방식을 코드로 보기위해 어떤 클래스가 사용되고 있는지 알아보도록 하겠습니다.

  • RememberMeAuthenticationFilter.java
  • AbstractRememberMeServices.java
  • TokenBasedRememberMeServices.java

먼저 RememberMeAuthenticationFilter클래스의 doFilter가 실행되면서 아래와 같이 로직이 실행되게 됩니다.

Flow에 해당되는 코드가 너무길어 중간중간 코드를 생략하여 캡쳐하였습니다.

 

1. RememberMeAuthenticationFilter에서 SecurityContext에 인증된 객체가 있는지 확인합니다.

RememberMeAuthenticationFilter

2. RememberMeAuthenticationFilter에서 autoLogin메소드를 실행하여 rememberMe쿠키가 있는지 확인합니다.

RememberMeAuthenticationFilter

3. rememberMe쿠기가 있는지 확인하고, 쿠키가 있다면 디코딩하여 토큰의 형식 및 값이 일치하는지 확인하고 User의 계정이 존재하는지 확인합니다.

AbstractRememberMeServices

4. 위에 로직이 모두 통과되었으면 새로운 Authentication 객체를 생성하여 User정보 및 권한정보를 넣은 후, SecurityContext에 인증객체를 다시 집어넣습니다.

AbstractRememberMeServices
AbstractRememberMeServices

 

AnonymousAuthenticationFilter 동작원리

AnonymousAuthenticationFilter 필터는 현재 접속하고 있는 사용자가 인증을 받지 않은 경우, 익명 사용자용 토큰을 만들어서 SecurityContext에 인증객체를 저장해주는 역할을 하고 있습니다.

이러한 이유는 인증 사용자와 미인증 사용자간의 구분이 필요하기 때문입니다.

 

AnonymousAuthenticationFilter 필터의 동작원리를 살펴보면 아래와 같은 Flow로 진행되는 것을 알 수 있습니다.

이 필터에 동작방식을 코드로 보기위해 어떤 클래스가 사용되고 있는지 알아보도록 하겠습니다.

  • AnonymousAuthenticationFilter.java

먼저 AnonymousAuthenticationFilter클래스의 doFilter가 실행되면서 아래와 같이 로직이 실행되게 됩니다.

 

1. SecurityContext에서 인증객체가 있는지 확인합니다.

AnonymousAuthenticationFilter

2. 인증된 객체가 없다면 익명사용자로 판단하고, 익명사용자용 인증객체를 만들어 SecurityContext에 저장하게 됩니다.

AnonymousAuthenticationFilter

아래를 보시면 AnonymousAuthenticationToken 즉, 익명사용자용 인증객체 입니다.

 

동시 세션 제어 필터(SessionManagementFilter, ConcurrentSessionFilter) 동작원리

SessionManagementFilterConcurrentSessionFilter 필터는 동시 세션을 관리해주는 역할을 하는 필터입니다.

간단한 예시!!

사용자A와 사용자B가 인프런에 등록되어있는 강의를 들으려고 하는데 강의가 너무 비싸서 고민을 하고있었습니다.

그때, 사용자A가 사용자B에게 "나의 계정으로 구매할테니 돈을 반반씩 내서 같이 듣자"라는 말을하였고, 실제로 그 둘은 그 방식으로 강의를 구매하였습니다.

이렇게 동시에 한계정으로 접속하는 것을 방지하기위해 스프링 시큐리티에서 제공하는 API가 동시 세션 제어라는 기능입니다.

 

동시 세션 제어와 관련된 API는 아래와 같습니다.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/loginPage").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin();
        http
            .sessionManagement()  // 세션관리기능 동작
            .maximumSessions(1)   // 최대허용세션 갯수
            .maxSessionsPreventsLogin(false);  // 세션초과시 처리전략(default : false)
    }
}

여기서 maxSessionsPreventsLogin() API는 2가지 처리방식으로 나뉘어지게 되는데

1. maxSessionsPreventsLogin(true) : 현재 사용자 인증실패

 - 사용자A 접속 후 동일한 계정으로 사용자B가 접속하게되면, 사용자B 인증예외 발생

2. maxSessionsPreventsLogin(false) : 기존 사용자 인증실패

 - 사용자A 접속 후 동일한 계정으로 사용자B가 접속하게되면, 정상적으로 인증이 되고 사용자A가 다른자원에 접속 시도시 사용자A 인증예외 발생

 

SessionManagementFilter  ConcurrentSessionFilter 필터의 동작원리를 살펴보면 아래와 같은 Flow로 진행되는 것을 알 수 있습니다.

이 필터에 동작방식을 코드로 보기위해 어떤 클래스가 사용되고 있는지 알아보도록 하겠습니다.

  • SessionManagementFilter.java
  • ConcurrentSessionFilter.java
  • ConcurrentSessionControlAuthenticationStrategy.java
  • ChangeSessionIdAuthenticationStrategy.java

user1과 user2를 예로 들면서 소스코드를 분석해보도록 하겠습니다.

먼저 SessionManagementFilter클래스의 doFilter가 실행되면서 아래와 같이 로직이 실행되게 됩니다.

 

1. user1이 접속을하게되면 ConcurrentSessionControlAuthenticationStrategy의 onAuthentication메소드를 실행하여 계정이 접속된 세션의 갯수와 api를 통해 설정한 세션의 갯수를 비교합니다.

최대로 설정한 세션카운트(1)가 현재계정의 세션카운트(0)보다 크기때문에 별다른 이슈없이 인증과정이 진행되게 됩니다.

SessionManagementFilter
ConcurrentSessionControlAuthenticationStrategy

2. user1이 접속한상태에서 user2가 접속을하게되면 ConcurrentSessionControlAuthenticationStrategy의 onAuthentication메소드가 실행되고 동일하게 세션의 갯수를 비교하게되는데, 최대로 설정한 세션카운트(1)와 현재계정의 세션카운트(1)가 동일하기 때문에 allowableSessionExceeded메소드가 실행되게 됩니다.

ConcurrentSessionControlAuthenticationStrategy

3. allowableSessionExceeded의 if문을보면 this.exceptionIfMaximumExceeded를 볼 수 있는데 저 값은 저희가 API에서 설정한 maxSessionsPreventsLogin()의 전략임으로 true로 설정했다면 if문에 들어가게되어 user2가 접속이 안되게 막을 것이고, false로 설정했다면 이전 사용자의 세션을 만료시키게 하는것을 볼 수 있습니다.

ConcurrentSessionControlAuthenticationStrategy - maxSessionsPreventsLogin() 전략이 true인 경우
ConcurrentSessionControlAuthenticationStrategy - maxSessionsPreventsLogin() 전략이 false인 경우

4. maxSessionsPreventsLogin() 값을 false로 했다는 가정하에 user1이 다시 접속을 시도한다면, ConcurrentSessionFilter가 매번 이 세션이 만료되었는지 체크를 하여 만료가 되었으면 로그아웃 시키는 것을 볼 수 있습니다.