인프런/스프링 시큐리티 완전 정복 [6.x 개정판]

13)세션관리 - 동시 세션 제어 , 세션 고정 보호, 세션 생성 정책

backend dev 2024. 10. 22.

세션관리 - 동시 세션 제어

 

동시 세션 제어사용자가 동시에 여러 세션을 생성하는 것을 관리하는 전략이다

 

• 이 전략은 사용자의 인증 후에 활성화된 세션의 수설정된 maximumSessions 값과 비교하여 제어 여부를 결정한다

 

하나의 계정으로 인해 세션이 여러개 생기는것 == 동시적 세션

[각 클라이언트가 동일한 계정으로 인증을 하게되면 각각 다른 세션이 서버에 생성되고

각각 다른 세션ID가 클라이언트에게 반환된다.]

 

pc,휴대폰,패드 각각 로그인을 하게된다면 

3개의 클라이언트가 각각 세션을 가지게 된다. 

 

최대 세션이 만약 1개라면

-> pc에서 로그인하고 난뒤 휴대폰에서 로그인하면 pc에 인증한게 무효화되거나 휴대폰에서 로그인이 안된다. 

 

동시 세션 제어 2 가지 유형

 

1. 사용자 세션 강제 만료

최대 세션은 1이라고 가정

 

두번째로 홍길동으로 접속한 클라이언트까지 세션을 만들어주고,

첫번째로 홍길동으로 접속한 클라이언트의 세션은 만료시킨다.

 

[세션 강제 만료 ]

최대 세션은 1이라고 가정

 

이 경우에는 두번째로 홍길동으로 로그인이 실패하게 된다.  [ 인증 시도를 차단 ] 

 

sessionManagement() API ­ - 동시 세션 제어

 

 

세션 만료 후 리다이렉션 전략

 

maxSessionsPreventsLogin()이

true이면 세션 최대갯수시 사용자 인증 시도 차단

false라면 인증하는 사용자 접근허용하지만 기존 사용자 세션 만료 [ 사용자 강제 세션 만료 ]

 

 


maxSessionsPreventsLogin()이 true이면 세션 최대갯수시 사용자 인증 시도 차단이므로 

invalidSessionUrl() , expiredUrl()은 의미가없다 -> 어차피 로그인 시도에서 차단되므로

 

maxSessionsPreventsLogin()이 false일때

invalidSessionUrl() , expiredUrl() 각각 설정되어있고 안되어있고 일때 결과는 위의 표에 나와있다.

 

테스트

@EnableWebSecurity
@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {


        http.authorizeHttpRequests(auth -> auth
                        .requestMatchers("/login").permitAll()
                        .anyRequest().authenticated())
                .formLogin(Customizer.withDefaults())
                .sessionManagement(session -> session
                        .maximumSessions(1)
                        .maxSessionsPreventsLogin(false) // default 값은 false
                );
        return http.build();

    }

 

첫번째 클라이언트 , 두번째 클라이언트 로그인하고 난뒤,  첫번째 클라이언트에서 새로고침을 했더니 다음과 같이 세션만료 메시지가 발생한다.

 

 

 

.maxSessionsPreventsLogin(true)

설정을 다음과 같이 바꾼다면

 

인증시도 조차 차단된다.

 

 

아래와 같이 설정한다면 

@EnableWebSecurity
@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {


        http.authorizeHttpRequests(auth -> auth
                        .requestMatchers("/invalidSSessionUrl","/expiredUrl").permitAll()
                        .anyRequest().authenticated())
                .formLogin(Customizer.withDefaults())
                .sessionManagement(session -> session
                        .invalidSessionUrl("/invalidSessionUrl")
                        .maximumSessions(1)
                        .maxSessionsPreventsLogin(false) // default 값은 false
                        .expiredUrl("/expiredUrl")
                );
        return http.build();

    }

 

다음과 같은 결과 확인가능하다.


세션 고정 보호 

 

 

세션 고정 공격악의적인 공격자가 사이트에 접근하여 세션을 생성한 다음

다른 사용자가 같은 세션으로 로그인하도록 유도하는 위험을 말한다

 

스프링 시큐리티 사용자가 로그인할 때 새로운 세션을 생성하거나 세션 ID를 변경함으로써

이러한 공격에 자동으로 대응한다

 

 

기본적으로 서버는 로그인을 하기전에 세션을 만들어준다.

[ 익명사용자를 위해 , spring security의 기본 동작 방식임, ]

그 세션을 공격자가 사용자에게 심어서 로그인하면 그 세션을 이용하여 사용자 정보,권한등을 탈취하는것인데

 

로그인 성공시 세션ID를 새로 생성해서 바꿔주기만 하면 해당 공격은 막을  수 있다.

[세션을 새로 만들어도 되지만, 쿠키로 전달하는 세션ID만 바꿔줘도 세션고정 공격은 막을 수 있다.]

 


 

인증되지않은 사용자도 세션을 만들어주는 것은

다음과 같은 이유로 자주 사용됩니다:

  1. 사용자 상태 유지: 사용자가 로그인하지 않은 상태에서도 사이트 내에서 상태를 유지할 필요가 있을 때 세션이 유용합니다. 예를 들어, 사용자가 로그인을 하지 않은 상태에서 쇼핑몰에서 장바구니에 물건을 추가하거나, 특정 설정을 유지해야 할 때 세션이 사용됩니다.
  2. 보안상의 이유: 서버는 익명 사용자와 상호작용하는 동안도 세션을 통해 보안 컨텍스트를 관리할 수 있습니다. 이는 사용자가 로그인할 때 세션 고정 공격을 방지하기 위해, 로그인 전후 세션 ID를 재생성하는 방식과 연관됩니다.
  3. 사이트 기능 제공: 세션을 통해 비로그인 사용자에게도 맞춤형 경험(예: 지역에 맞는 콘텐츠 제공)을 제공할 수 있습니다.

 

Spring Security의 기본 동작 방식 중 하나는 인증되지 않은 사용자가 접근하더라도 세션을 생성하는 것입니다.

즉, 익명 사용자가 웹 애플리케이션에 처음 접근할 때 세션을 생성하는 것이 일반적인 동작입니다.

 

  • 익명 사용자에 대한 세션 생성:
    • 사용자가 로그인하지 않은 상태로 웹 애플리케이션에 접근할 때, Spring Security는 기본적으로 세션을 생성하여 익명 사용자를 관리합니다. 이 세션을 통해 익명 사용자의 활동을 추적하고, 로그인을 하기 전에도 상태를 유지할 수 있게 해줍니다.
  • 로그인 후 세션 ID 재생성:
    • 사용자가 인증(로그인)하면, Spring Security는 세션 고정 보호(Session Fixation Protection)를 기본적으로 제공합니다. 이를 통해 로그인 성공 시 기존 세션 ID를 폐기하고 새로운 세션 ID를 생성하여 세션 고정 공격을 방지합니다.
  • 세션 관리 정책:
    • Spring Security는 세션을 어떻게 관리할지 설정할 수 있는 다양한 옵션을 제공합니다. 예를 들어, sessionCreationPolicy 설정을 통해 세션을 항상 생성할지, 필요할 때만 생성할지, 또는 절대 생성하지 않을지 결정할 수 있습니다.
      • ALWAYS: 세션을 항상 생성
      • IF_REQUIRED: 기본값으로, 요청에 세션이 필요하면 생성
      • NEVER: Spring Security가 세션을 생성하지 않지만, 기존 세션은 사용할 수 있음
      • STATELESS: 세션을 사용하지 않음 (JWT 등 토큰 기반 인증 시 사용)

 


 

sessionManagement() API -­ 세션 고정 보호

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.sessionManagement((session) -> session
            .sessionFixation(sessionFixation -> sessionFixation.newSession())
    );
    return http.build();
}

세션 고정 보호 전략

• changeSessionId()
기존 세션을 유지하면서 세션 ID만 변경하여 인증 과정에서 세션 고정 공격을 방지하는 방식이다. 기본 값으로 설정되어 있다

• newSession()
새로운 세션을 생성하고 기존 세션 데이터를 복사하지 않는 방식이다(SPRING_SECURITY_ 로 시작하는 속성은 복사한다)

• migrateSession()
새로운 세션을 생성하고 모든 기존 세션 속성을 새 세션으로 복사한다

• none()
기존 세션을 그대로 사용한다

changeSessionId()는 Spring Security 기본설정이다.

 


세션 생성 정책

스프링 시큐리티에서는 인증된 사용자에 대한 세션 생성 정책을 설정하여

어떻게 세션을 관리할지 결정할 수 있으며 이 정책은 SessionCreationPolicy 로 설정된다

 

 

 

세션 생성 정책 전략

 

SessionCreationPolicy.ALWAYS

인증 여부에 상관없이 항상 세션을 생성한다

ForceEagerSessionCreationFilter 클래스를 추가 구성하고 세션을 강제로 생성시킨다

 

 

SessionCreationPolicy.NEVER


스프링 시큐리티가 세션을 생성하지 않지만 애플리케이션이 이미 생성한 세션은 사용할 수 있다

 

SessionCreationPolicy.IF_REQUIRED


필요한 경우에만 세션을 생성한다. 예를 들어 인증이 필요한 자원에 접근할 때 세션을 생성한다 [ Spring Security 기본값 ]

 

SessionCreationPolicy.STATELESS


세션을 전혀 생성하거나 사용하지 않는다
인증 필터는 인증 완료 후 SecurityContext 를 세션에 저장하지 않으며

JWT 와 같이 세션을 사용하지 않는 방식으로 인증을 관리할 때 유용할 수 있다


SecurityContextHolderFilter세션 단위가 아닌 요청 단위항상 새로운 SecurityContext 객체를 생성하므로

컨텍스트 영속성이 유지되지 않는다

 

sessionManagement() API ­ - 세션 생성 정책

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.sessionManagement((session) -> session
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    );
    return http.build();
}

STATELESS설정에도 세션이 생성될 수 있다

스프링 시큐리티에서 CSRF 기능이 활성화 되어 있고 CSRF 기능이 수행 될 경우

사용자의 세션을 생성해서 CSRF 토큰을 저장하게 된다 

 

세션은 생성되지만 CSRF 기능을 위해서 사용될 뿐 인증 프로세스의 SecurityContext 영속성에 영향을 미치지는 않는다.

 

 

댓글