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 framework는 HttpSessionEventPulisher를 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
'Spring' 카테고리의 다른 글
[Spring Framework] intellij에서 Spring legacy project 구성하기 (1) | 2024.09.07 |
---|---|
[multipart/form-data] - MultipartFile과 JSON 함께 받기 (HttpMediaTypeNotSupportedException가 발생하는 이유) (0) | 2023.11.04 |
[환경 설정] 데이터 베이스 (DB) (H2) (0) | 2023.01.29 |
스프링 웹프로젝트 간편 생성 - spring initializr 사용하기 (0) | 2023.01.25 |
[JPA] JPA를 통한 데이터 조회 (Entity, DTO) (0) | 2022.04.24 |