Spring을 사용하면 Security에서 손쉽게 중복 로그인 방지 설정을 할 수 있다.

@Bean
public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationManager authenticationManager
,ConcurrentSessionControlAuthenticationStrategy sessionStrategy) throws Exception {

.....생략

// 세션 관리 설정
       http.sessionManagement(sessionManagement -> sessionManagement
             .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
              .enableSessionUrlRewriting(false)
              .sessionFixation().changeSessionId()
              .maximumSessions(1)
              .expiredUrl("/login?invalid-session=true")
              .maxSessionsPreventsLogin(false)
              .sessionRegistry(sessionRegistry())
       );

       return http.build();
}

 

1. sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)

  • 역할:
    • Spring Security가 세션을 생성하는 시점을 정의합니다.
  • SessionCreationPolicy 옵션:
    • ALWAYS: 요청마다 새 세션을 강제로 생성.
    • NEVER: Spring Security가 세션을 생성하지 않지만 이미 존재하는 세션은 사용.
    • IF_REQUIRED: 인증이 필요하면 세션을 생성 (기본값).
    • STATELESS: 세션을 전혀 생성하지 않음. JWT 기반 인증 같은 무상태(stateless) 애플리케이션에서 사용.
  • 적용 효과:
    • 이 설정으로 세션은 "필요할 때만" 생성되며, 세션이 없는 상태에서 인증 요청이 들어오면 새로 생성됩니다.

2. enableSessionUrlRewriting(false)

  • 역할:
    • 세션 ID를 URL에 추가하는 것을 허용할지 여부를 설정.
  • 배경:
    • URL에 세션 ID가 포함되면 브라우저 간 세션 공유, 세션 하이재킹(Session Hijacking) 같은 보안 문제가 발생할 수 있음.
  • 적용 효과:
    • false: URL에 세션 ID를 포함하지 않음. 쿠키 기반 세션 유지만 허용.
    • 보안 강화를 위해 보통 false로 설정.

3. sessionFixation().changeSessionId()

  • 역할:
    • 인증 과정에서 세션 고정(Session Fixation) 공격을 방지하기 위해 기존 세션 ID를 변경.
  • 배경:
    • 공격자가 사용자의 세션 ID를 미리 예측하거나 가로채서 악의적인 작업을 수행할 수 있음.
  • 적용 효과:
    • changeSessionId(): 기존 세션의 속성을 유지하면서 새로운 세션 ID를 생성.
    • newSession(): 기존 세션을 무효화하고 새로운 세션 생성.
    • none(): 세션 ID를 변경하지 않음 (비권장).
  • 권장:
    • changeSessionId()는 보안성과 성능의 균형이 맞아 자주 사용됨.

4. maximumSessions(1)

  • 역할:
    • 한 사용자당 동시에 유지 가능한 세션 수를 제한.
  • 적용 효과:
    • 여기서는 1로 설정되어, 한 사용자가 한 번에 하나의 세션만 가질 수 있음.
    • 다른 브라우저나 기기에서 동일한 계정으로 로그인하면 이전 세션이 만료됨.

5. expiredUrl("/login?invalid-session=true")

  • 역할:
    • 사용자의 세션이 만료되었을 때 리디렉션될 URL을 설정.
  • 적용 효과:
    • 예를 들어, 동일한 계정으로 다른 브라우저에서 로그인하면 기존 세션은 만료되고 /login?invalid-session=true로 리디렉션됩니다.
    • 사용자 경험을 개선하기 위해 "세션 만료됨" 메시지를 이 URL에서 처리할 수 있음.

6. maxSessionsPreventsLogin(false)

  • 역할:
    • 세션 제한을 초과했을 때 새로운 로그인을 허용할지 결정.
  • 적용 효과:
    • false: 새로운 로그인 요청을 허용. 기존 세션은 만료됨.
    • true: 새로운 로그인 요청을 차단. "이미 최대 세션 수에 도달했습니다" 같은 에러를 반환.
  • 권장 사용:
    • 일반적으로 false로 설정하여 사용자가 다른 기기에서 로그인할 수 있도록 함.

7. sessionRegistry(sessionRegistry())

  • 역할:
    • SessionRegistry는 활성 세션을 추적하고 관리하는 데 사용됩니다.
    • 이 설정은 Spring Security의 동시 세션 제어(Concurrent Session Control)에 필요.
  • 적용 효과:
    • 사용자당 활성 세션 목록을 유지하며, 특정 사용자의 모든 세션을 만료시키거나, 만료된 세션 정보를 추적할 수 있음.
    • Spring Security의 SessionRegistryImpl을 기본 구현으로 사용.

적용 예

  1. 사용자가 이미 로그인된 상태에서 다른 브라우저에서 로그인:
    • 기존 세션은 만료되고 /login?invalid-session=true로 리디렉션.
    • 새로운 세션이 생성되어 인증이 유지됨.
  2. 사용자가 최대 세션 제한을 초과:
    • maxSessionsPreventsLogin(false)로 인해 새 로그인이 허용되고, 이전 세션은 종료됨.

 

필터를 사용 하게 되면 필터에 session registry를 별도로 설정을 해줘야 한다.

 

1. SecurityConfig에 ConcurrentSessionControlAuthenticationStrategy Bean 추가

@Bean
public ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlStrategy(SessionRegistry sessionRegistry) {
        ConcurrentSessionControlAuthenticationStrategy strategy =
new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry);
        
        
strategy.setMaximumSessions(1);

         strategy.setExceptionIfMaximumExceeded(false);
         return strategy;
}

 

2. SecurityConfig에 filterChain에 ConcurrentSessionControlAuthenticationStrategy sessionStrategy 추가하고 Filter에 전달

@Bean
public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationManager authenticationManager
,ConcurrentSessionControlAuthenticationStrategy sessionStrategy) throws Exception {

.....생략

// 세션 관리 설정
       http.sessionManagement(sessionManagement -> sessionManagement
             .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
              .enableSessionUrlRewriting(false)
              .sessionFixation().changeSessionId()
              .maximumSessions(1)
              .expiredUrl("/login?invalid-session=true")
              .maxSessionsPreventsLogin(false)
              .sessionRegistry(sessionRegistry())
       );

     
        http.addFilterBefore(CustomAuthenticationFilter(authenticationManager, sessionStrategy), UsernamePasswordAuthenticationFilter.class);
      

       return http.build();
}


@Bean
public CustomAuthenticationFilter CustomAuthenticationFilter(AuthenticationManager authenticationManager
,ConcurrentSessionControlAuthenticationStrategy sessionStrategy) {
        KsignGpkiCertAuthenticationFilter filter = new KsignGpkiCertAuthenticationFilter();
        filter.setAuthenticationManager(authenticationManager); // AuthenticationManager 설정
        filter.setFilterProcessesUrl("/member/ksign-gpki-login-processing"); // 필터가 처리할 URL 설정
        filter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler()); // 인증 성공 핸들러 설정
        filter.setAuthenticationFailureHandler(customAuthenticationFailureHandler()); // 인증 실패 핸들러 설정
        filter.setSessionAuthenticationStrategy(sessionStrategy); // 세션 관리 전략 설정
        return filter;
}

 

필터 등록 시 세션 관리 전략을 설정하지 않으면 중복 로그인 방지가 작동하지 않는다.

+ Recent posts