스프링 시큐리티를 처음으로 공부하고 프로젝트에 적용하게 되면서 배운 내용들과 생각을 정리한 글이다.😊
공식문서에서는 아래와 같이 정의하고 있다.
Spring Security는 인증(Authentication), 권한 부여(Authorization), 그리고 일반적인 공격으로부터의 보호를 제공하는 프레임워크이다.
명령형(imperative)과 리액티브(reactive) 애플리케이션 보안을 모두 완벽히 지원하며, Spring 기반 애플리케이션 보안을 위한 사실상의 표준(de-facto standard)으로 자리 잡고 있다.
먼저 공식 문서를 통해 스프링 시큐리티 아키텍쳐 에 대해 공부를 하였다. 공식문서에서 서블릿에 대한 얘기가 나오기에 예전에 공부한 서블릿을 복습하였다. 글에서는 단순히 공식 문서 이해를 위해서 단순 요약하여 정리하였다.
1. 서블릿 이란?
서블릿은 웹 애플리케이션에서 HTTP 요청을 처리하는 표준 방식으로, Java EE(Enterprise Edition) 의 일부로 등장했다. 서블릿은 HTTP 요청을 받아 요청 데이터를 처리하고, 동적으로 생성한 응답 데이터를 클라이언트에 전달하는 역할을 수행한다. 주로 웹 서버와 클라이언트 간의 데이터 교환을 중재하는 핵심 요소로 사용된다.
2.스프링과 서블릿의 관계
스프링은 Java EE의 복잡성을 해결하고, 더 효율적인 애플리케이션 개발을 지원하기 위해 개발된 프레임워크다. 스프링은 웹 애플리케이션을 처리하기 위해 서블릿을 기반으로 하는 스프링 MVC라는 웹 프레임워크를 제공하게 되었고, 이 과정에서 서블릿은 중요한 역할을 한다. 그 중 DispatcherServlet은 스프링 MVC 애플리케이션의 핵심 서블릿으로, 모든 HTTP 요청을 처리하는 주요 역할을 담당한다.
3. 스프링 시큐리티와 서블릿의 연관성
스프링 시큐리티는 서블릿 컨테이너를 기반으로 동작하며, 서블릿과 밀접하게 연관되어 있다.
스프링 시큐리티는 서블릿 필터(Servlet Filter) 를 활용하여 요청과 응답에 대한 보안 처리를 한다. 서블릿 필터는 서블릿 요청과 응답을 가로채어 중간에서 작업을 수행할 수 있게 해주는 기술이다. 스프링 시큐리티는 서블릿 필터를 통해 인증 및 권한 부여 로직을 처리하며, 웹 애플리케이션에서 발생하는 보안 관련 작업을 제어한다.
이제 공식 문서를 번역하며 이해를 해보려고한다.
클라이언트가 요청을 보내면 컨테이너는 요청 URI 경로에 따라 처리해야 할 필터(Filter)
와 서블릿(Servlet)
을 포함하는 FilterChain
을 생성한다.
스프링 MVC 애플리케이션에서는 이 서블릿이 주로 DispatcherServlet
이다.
하나의 요청(HttpServletRequest, HttpServletResponse)에 대해 하나의 Servlet만이 직접 처리할 수 있다는 제약이 있지만, 여러 개의 Filter
는 함께 사용될 수 있다.
FilterChain
은 요청 흐름에서 다음 필터나 서블릿을 호출하며, 요청 처리의 순서를 제어한다. 이를 통해 필터는 요청이 처리되기 전 또는 후에 원하는 작업을 삽입할 수 있다.
스프링은 필터를 구현한 DelegatingFilterProxy
를 제공하고,
DelegatingFilterProxy
는 Spring과 서블릿 컨테이너를 연결해준다.
서블릿 컨테이너가 직접 Spring Bean
을 알지 못하므로, DelegatingFilterProxy
가 대신 Spring Bean
으로 등록된 필터(Filter)
를 찾아 실행한다.
위의 그림은 DelegatingFilterProxy가 필터와 FilterChain에서 어떻게 작동하는지 보여주는 그림이다.
DelegatingFilterProxy는 ApplicationContext에서 Bean Filter0을 찾아 실행한다.
FilterChainProxy
는 Spring Security에서 중요한 역할을 하는 필터로, 여러 보안 필터를 관리하고 순차적으로 실행하는 데 사용된다. SecurityFilterChain
을 통해 필터를 처리하고, 이를 DelegatingFilterProxy
가 위임하여 실행하도록 한다.
SecurityFilterChain
은 FilterChainProxy
에 의해 사용되어, 요청에 대해 호출해야 할 Spring Security 필터를 결정한다.
요청되는 URL에 따라 알맞는 SecurityFilterChain(0~n) 이 실행된다.
ExceptionTranslationFilter
는 FilterChainProxy
내의 보안 필터(Security Filters)
중 하나이며, 요청 중 발생하는 인증 및 권한 문제를 처리하고 적절한 응답을 반환하는 역할을 하는 필터이다.
요청 처리 시작
인증되지 않았거나 AuthenticationException이 발생한 경우
AuthenticationEntryPoint
호출하여 로그인 페이지로 리디렉션 하여 인증 정보가 필 요하다는 것을 클라이언트에게 알린다.AccessDeniedException이 발생한 경우
AccessDeniedHandler
호출하여 접근 권한이 없다는 메시지를 클라이언트에게 보냅니 다.예외가 없는 경우
AccessDeniedException
이나 AuthenticationException
이 발생하지 않으면, ExceptionTranslationFilter는 아무 작업도 하지 않고 요청 처리를 정상적으로 마친다..쉽게 말하자면
ExceptionTranslationFilter
를 보안 수문장이라고 생각할 수 있다:
- 수문장은 요청을 그대로 전달한다.(정상적인 경우).
- 만약 인증되지 않았거나 잘못된 인증 정보가 있다면, "로그인하세요!"라고 알려준다.
- 인증은 되었지만 권한이 부족한 경우, "여기는 들어오면 안 돼요!"라고 거절한다.
- 문제가 없으면 그냥 지나가게 한다.
스프링 시큐리티는 Gradle 기준으로 build.gradle에 아래와 같이 설정하면 스프링 부트가 자동으로 빈으로 등록하여 사용할 수 있다.
dependencies{
implementation 'org.springframework.boot:spring-boot-starter-security'
}
Spring Security 환경 설정을 위한 클래스이다.
SecurityConfig를 구성하기 위해서는 두 가지 애너테이션이 필요하다. 이를 통해 이 클래스가 Spring Security의 보안 설정을 정의한 클래스임을 나타낼 수 있다.
@EnableWebSecurity :
SpringConfig 클래스에 @EnableWebSecurity
애너테이션을 사용하면 해당 클래스가 Spring Security의 보안 설정을 담당하는 설정 클래스로 인식된다. 이 애너테이션을 사용함으로써 Spring Security의 보안 관련 설정을 활성화할 수 있다.
@Configruation
:
Spring 설정 클래스를 정의할 때 사용된다. 이 애너테이션을 붙인 클래스는 Spring 컨테이너의 빈(Bean) 으로 등록되며, 이 클래스에서 정의된 @Bean 메서드들을 Spring 컨테이너에 등록할 수 있다.
즉, 보안 설정 클래스의 필터 체인, 인증 매니저(AuthenticationManager), CORS 설정 등과 같은 모든 보안 관련 설정을 Spring 컨테이너가 관리할 수 있도록 등록한다.
@Configuration // Spring 설정 클래스를 정의
@EnableWebSecurity // Spring Security 설정 활성화
public class SecurityConfig
스프링 시큐리티에서는 보안을 구성하기 위한 두가지 주요 방법이 존재한다.바로
두 방식 모두 보안 설정을 정의하는 데 사용되지만, 방식과 구현 방법에 차이가 있다.
5.7 버전 이전에 스프링 시큐리티에서 사용되던 기존의 보안 설정 방식이다.
개발자는 WebSecurityConfigurerAdapter
를 상속받고 클래스에서 제공하는 여러 메서드 (예: configure(HttpSecurity http))를 @Override하는 방식으로 다양한 보안 설정을 커스텀 구성한다.
상속 구조에 의존하기 때문에 확장이 제한적일 수 있다.
예시 코드
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
Spring Security 5.7 이상에서는 WebSecurityConfigurerAdapter을 지양하고 SecurityFilterChain을 사용하는 방식이 권장된다.
이 방식은 @Bean
을 이용하여 보안 설정을 선언적으로 정의한다.
유연성 : 여러 구성 요소를 조합하거나 조건에 따라 설정할 수 있다.
상속의 종속성 제거 : 기존에 WebSecurityConfigurerAdapter
를 반드시 상속해야 했기 때문에 상당히 의존적이었다. 하지만 @Bean
을 통해 클래스를 독립적으로 관리할 수 있다.
Spring과 자연스럽게 통합 : @Bean으로 정의된 보안 구성 요소들은 스프링의 IoC 컨테이너에서 관리를 하기 때문에 스프링의 설정 관행에 맞게 자연스럽게 통합됩니다.
테스트 용이성 : 설정이 각 메서드에 분리되어 작성되므로, 특정 설정을 테스트하거나 수정하기가 간단하다.
예시 코드
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated())
.formLogin();
return http.build();
}
}
Spring은 의존성 주입(Dependency Injection) 과 컴포넌트 기반 개발을 핵심 철학으로 삼고 있기 때문에 Spring Security 또한 점점 컴포넌트 기반 아키텍처를 강조하며 발전했다.
기존 WebSecurityConfigurerAdapter
방식은 클래스 상속에 의존했기 때문에 보안 설정을 커스터마이징하기 위해 추상 메서드를 오버라이드해야 했다. 그리고 상속 구조 때문에 다중 상속이 불가능했다.
예를 들어 클래스가 이미 다른 클래스를 상속받고 있다면WebSecurityConfigurerAdapter
를 상속할 수 없는 한계가 있었다. 또한 보안 설정이 다른 컴포넌트와 강하게 결합되거나 재사용이 어려웠다.
반면, SecurityFilterChain
은 상속 대신 구성(Composition) 방식을 사용하여 보안 설정을 유연하게 정의한다.
컴포넌트 기반으로 보안 설정을 관리하고, 각 보안 설정이 독립적인 빈(Bean)으로 관리되므로 다중 필터 체인 정의나 재사용이 훨씬 용이하다.