Spring Security
개인 프로젝트에 Spring Security를 적용하기 위해 스프링 문서를 통해서 공부한 내용 정리를 위한 글
이 글은 Spring Security 6.3.3 버전을 기준으로 작성되었습니다.
Spring Security는 인증, 인가 및 일반적인 공격에 대한 보호를 제공하는 프레임워크입니다.
- 인증(Authentication): 사용자가 누구인지 확인하는 과정입니다. 즉, 사용자가 입력한 자격 증명(예: 사용자 이름과 비밀번호)이 유효한지 검증합니다.
- 인가(Authorization): 인증된 사용자가 특정 리소스나 기능에 접근할 수 있는 권한이 있는지를 결정하는 과정입니다. 인증 후 사용자가 어떤 작업을 수행할 수 있는지 정의합니다.
- 보호(Protection): 일반적인 공격(예: CSRF, 세션 고정 공격, XSS 등)으로부터 애플리케이션을 보호하는 기능을 포함합니다.
전제조건
Spring Security는 Java 17 이상의 런타임 환경을 필요로 하며, Spring Security는 자체적으로 동작할 수 있도록 설계되었기 때문에, Java 런타임 환경에 특별한 설정 파일을 배치할 필요가 없습니다. 필요한 모든 파일은 애플리케이션 내에 포함되어 있습니다.
이러한 설계는 배포 시 유연성을 극대화하며, 목표 아티팩트(JAR, WAR, 또는 EAR)를 한 시스템에서 다른 시스템으로 복사하면 바로 작동할 수 있도록 해줍니다.
Spring Security는 Spring Framework 없이도 동작할 수 있지만, Spring과 함께 사용되도록 설계된 보안 프레임워크입니다.
즉, Spring Security는 Java EE와 같은 다른 자바 애플리케이션 환경에서도 사용할 수 있지만, Spring Boot나 Spring Framework와 함께 사용할 때 가장 쉽게 통합되고 효율적으로 작동합니다.
Spring Security의 서블릿 아키텍처
Spring Security의 서블릿 아키텍처에 기반을 두고 있기 때문에 필터의 역할을 이해하는 것이 중요합니다.
필터는 클라이언트의 요청이 애플리케이션에 도달하는 동안 여러 단계에서 작동합니다.
FilterChain
Spring Security는 FilterChain을 통해 다수의 필터를 처리하며, 각 필터는 요청과 응답을 변형하거나 흐름을 차단할 수 있습니다.
각각의 필터는 요청이 특정 조건을 만족하는지 확인하고, 필요에 따라 필터 체인의 다음 단계로 넘길지 여부를 결정합니다.
주요 흐름:
- 클라이언트 요청: 사용자가 애플리케이션에 요청을 보냅니다.
- FilterChain 생성: 서블릿 컨테이너는 요청 URI에 따라 처리할 필터와 서블릿을 포함한 FilterChain을 생성합니다.
- 필터 처리: 각 필터가 순차적으로 요청을 처리하고, 필요에 따라 요청을 변형하거나 인증, 인가와 관련된 작업을 수행합니다.
- 서블릿 처리: 모든 필터가 완료된 후 DispatcherServlet 같은 서블릿이 최종적으로 요청을 처리합니다.
DelegatingFilterProxy
DelegatingFilterProxy는 Spring에서 서블릿 필터와 Spring의 Bean 간의 연결을 위해 사용하는 특별한 필터입니다.
(사진의 Bean Filter는 아래에서 설명할 FilterChainProxy입니다.)
- 서블릿 필터와 Spring Bean의 통합
서블릿 컨테이너는 표준을 사용해 필터 인스턴스를 등록할 수 있지만, Spring에서 정의한 빈(Bean)은 인식하지 못합니다. DelegatingFilterProxy는 서블릿 컨테이너를 통해 표준적인 방법으로 등록되어서 이름처럼 필터 작업을 Spring Bean에게 위임(Delegating)합니다.
이를 통해 Spring이 관리하는 빈도 서블릿 필터로 동작할 수 있습니다.
- 지연로딩
서블릿 컨테이너는 애플리케이션이 시작될 때 필터를 즉시 등록하고 실행하려고 합니다. 하지만 Spring의 컨텍스트는 ContextLoaderListener를 통해 나중에 초기화됩니다
DelegatingFilterProxy는 Spring 컨텍스트가 초기화된 후에야 실제 필터 Bean을 로드하고 실행하도록 설정되어 있습니다.
이러한 지연 로딩을 이용해 해당 문제를 해결합니다.
- DelegatingFilterProxy의 doFilter()
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// Lazily initialize the delegate if necessary.
Filter delegateToUse = this.delegate; // 1
if (delegateToUse == null) { // 2
synchronized (this.delegateMonitor) { // 3
delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = findWebApplicationContext(); // 4
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: " +
"no ContextLoaderListener or DispatcherServlet registered?");
}
delegateToUse = initDelegate(wac); // 5
}
this.delegate = delegateToUse;
}
}
// Let the delegate perform the actual doFilter operation.
invokeDelegate(delegateToUse, request, response, filterChain); // 6
}
- 실제로 요청을 처리할 필터인 this.delegate를 delegateToUse로 지정
- null이면(아직 초기화되지 않았다면), 필터를 초기화하기 위해 아래 단계를 진행
- 쓰레드 안전성을 위해 synchronized 블록을 사용하여 한 번에 하나의 쓰레드만 필터를 초기화하도록 함.
- findWebApplicationContext() 메서드를 호출하여 Spring의 WebApplicationContext를 찾음.
(WebApplicationContext는 Spring의 설정과 bean을 포함하고 있음. 만약 이 컨텍스트가 없다면 예외 발생) - initDelegate(wac) 메서드를 통해 딜리게이트 필터를 초기화
initDelegate(wac) 메서드를 보면 targetBeanName을 통해 wac에서 빈을 찾아서 초기화함) - invokeDelegate(delegateToUse, request, response, filterChain) 메서드를 호출
(invokeDelegate 메서드를 보면 필터의 doFilter를 호출함)
위와 같은 동작으로 특정 필터(delegate)를 찾고 초기화한 다음, 그 필터에 요청을 전달하여 처리를 맡기는 역할을 합니다.
FilterChainProxy, SecurityFilterChain
FilterChainProxy
FilterChainProxy는 Spring Security에서 제공하는 특수한 필터로, HTTP 요청을 처리하기 위해 여러 보안 필터를 관리하고, 해당 필터들이 요청을 처리하도록 위임하는 역할을 합니다.
FilterChainProxy는 하나 이상의 SecurityFilterChain 인스턴스를 포함하고 있으며, Spring 빈으로 등록되어 일반적으로 DelegatingFilterProxy로 감싸져 Spring 애플리케이션 컨텍스트와 원활하게 작동합니다.
SecurityFilterChain
SecurityFilterChain은 FilterChainProxy가 현재 요청에 대해 호출해야 할 보안 필터를 결정할 때 사용됩니다.
인증 및 권한 부여 필터와 같은 보안 필터 인스턴스가 Spring 빈으로 구성되어 있고, 요청이 들어오면 FilterChainProxy는 등록된 순서에 따라 각 SecurityFilterChain을 확인하여 요청이 기준과 일치하는지 확인합니다.
호출과정
- FilterChain 선택:
- FilterChainProxy는 들어오는 요청을 SecurityFilterChain 인스턴스에 정의된 URL 패턴과 비교합니다.
- 일치하는 첫 번째 SecurityFilterChain만 호출되며, 즉 여러 체인이 일치하더라도 첫 번째 것만 처리됩니다.
- 일치 예제:
- 예를 들어 /api/messages/에 요청을 보내면, /api/**를 일치시키는 SecurityFilterChain0이 있으면, 해당 체인의 필터만 실행됩니다.
- 반면 /messages/에 요청하면, SecurityFilterChain0와 일치하지 않으므로, FilterChainProxy는 다른 SecurityFilterChain을 계속 확인하여 일치하는 것을 찾습니다.
스프링 시큐리티를 이용해 보안필터를 구성하는 자세한 내용은 프로젝트를 진행하면서 블로그에 적겠습니다.
https://docs.spring.io/spring-security/reference/index.html
Spring Security :: Spring Security
If you are ready to start securing an application see the Getting Started sections for servlet and reactive. These sections will walk you through creating your first Spring Security applications. If you want to understand how Spring Security works, you can
docs.spring.io
https://docs.spring.io/spring-security/reference/servlet/architecture.html
Architecture :: Spring Security
The Security Filters are inserted into the FilterChainProxy with the SecurityFilterChain API. Those filters can be used for a number of different purposes, like authentication, authorization, exploit protection, and more. The filters are executed in a spec
docs.spring.io