본문 바로가기

Backend/Spring

[Spring Security] session 동시 접속자 수 제한

 

Spring Security

 

 스프링시큐리티는 스프링 기반 웹 어플리케이션에서 '보안'(security)구현을 위한 구조를 제공해주는 프레임워크이며  인증(Authentication)과 인가(Authorization)을 포함한 다양한 기능을 제공한다.

 

 공개되지 않은 어플리케이션 정보에 접근하고자 하는 사용자는 인증이 필요하다. 웹어플리케이션에서 인증된 사용자의 정보는 세션(Session)이나 , JWT(JSON Web token)을 통해 관리할 수 있으며 스프링 시큐리티에서는 이와 관련한 여러 인터페이스를 지원한다.

 

 개발중인 웹 어플리케이션에서 인증 / 인가 기능을 구현하기 위해 스프링 시큐리티를 사용하였는데

로그아웃 후 재 로그인을 하였을 때 로그인이 안되는 현상이 발생하여 스프링 시큐리티 설정하는 방법을 정리하게 되었다.

(원래는 중복로그인 방지 기능이 안되었으나 중복 로그인 방지를 위한 설정을 차례대로 적용하니 해결되었다.)

 

 

 

Problem - 로그아웃 후 재 로그인 시 로그인 실패

 

 최대 세션 허용값을 1로 설정하여 중복로그인을 정상적으로 방지하였으나 로그 아웃 후 재 로그인을 할 때 세션 허용범위를 초과한다는 에러메시지를 받게 되었다.

 

Spring Security를 적용한 테스트 프로젝트를 생성하고 해당 문제의 원인과 해결방법을 찾아보자.

 

0. project 생성 - https://start.spring.io 

   (의존성 : Spring Web, Spring Security, lombok, SpringBoot Devtools 추가)

 

1. Security 환경설정

    

[참고]

- 간단한 페이지 ("/", "/home")들은 ViewControllerRegistry에 등록

- Spring security version 5.4 부터 WebSecurityConfigurerAdapter 를 상속받아 configure() 메서드를 오버라이드하는 방식은 deprecated 되고 SecurityFilterChain 빈을 생성하는 설정 방식이 권장된다.

- 간단한 테스트를 위해 인증 과정에서 사용자 정보를 검증하는 프로세스는 인메모리 방식을 사용

  * 만약 UserDetails 인터페이스를 상속하여 커스텀한 사용자 정보 객체를 사용할 경우 사용자를 구분하기 위해 equals()와 hashCode() 메서드를 오버라이드해서 구현해주어야한다.

- Spring framework의 경우 스프링시큐리티 기능의 적용을 위해서 DelegatingFilterProxy를 web.xml에 <filter>로 등록해주어야 한다. 

 

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                .formLogin()
                    //.loginPage("/login")
                    .permitAll()
                    .defaultSuccessUrl("/hello")
                    .and()
                .logout()
                    .permitAll()
                    .and()
                .sessionManagement()
                    .maximumSessions(1)
                    .maxSessionsPreventsLogin(true);

        return http.build();
    }


    @Bean
    protected UserDetailsService userDetailsService() {
        UserDetails user = User.withDefaultPasswordEncoder()
                .username("admin")
                .password("123")
                .roles("USER")
                .build();

        return new InMemoryUserDetailsManager(user);
    }
}

 

 

Cause & Solution - HttpSessionEventPulisher 의 등록

 SessionRegistry 에 등록된 세션정보가 정상적으로 제거되지 않았기 때문에 해당 문제가 발생한다. HttpSessionEventPublisher를 스프링 빈 및 리스너로 등록하여 세션연관 이벤트에 대한 로직들이 정상적으로 처리되도록 설정하면 해당 문제는 해결할 수 있다.

 

[참고]

- Springboot는 리스너 인터페이스 구현체를 빈으로 등록하면 자동으로 어플리케이션의 리스너로 등록하기 때문에 listener를 등록하기 위한 별도의 설정을 할 필요가 없다. HttpSessionEventPublisher를 빈으로 등록하면 HttpSession event listener로 자동 등록된다.

- Spring frameworkHttpSessionEventPulisher를 bean으로 등록하고 리스너 설정(web.xml 등) 해주어야 정상 적용된다.

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

...
    /*
    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }    
    */

    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }
...

}

 

 

 

Summary

- 진행 중이던 프로젝트의 '사용자 동시접속 제한' 기능이 제대로 적용되지 않아 시작했던 정리였지만 스프링 프레임워크와 스프링 부트의 차이점에 대해서 알게 된 기회였다. (스프링 부트는 많은 부분에 대해서 자동설정 기능을 지원한다.)

- 세션 등록과 관련하여 문제가 발생했다고 판단하여 엉뚱한 곳(SessionRegistry)을 살펴보았지만 공식문서와 chatGPT를 활용하여 원인을 파악할 수 있었다.

- SessionRegistry는 현재 어플리케이션에 접속중인 사용자들의 세션정보를 유지하기 위해 사용하고 SecurityContextRegistry 특정 유저에 관한 SecurityContext(Authentication처리된 인증정보)를 관리하기 위해 사용된다.

 

 

 

 

References

스프링 시큐리티 설정  - https://docs.spring.io/spring-security/site/docs/5.2.15.RELEASE/reference/html5/#hello-web-security-java-configuration

스프링 시큐리티 설정(최신) - https://docs.spring.io/spring-security/reference/servlet/authentication/session-management.html

스프링 학습 가이드: 웹 보안 - https://spring.io/guides/gs/securing-web/

사용한 샘플코드 - https://github.com/JaewookMun/spring-exercise/tree/main/max-session