[Spring] Spring Security 내부 동작 과정

[Spring] Spring Security 내부 동작 과정

#목차

Spring Security 내부 동작 과정

Spring Security 내부 동작 과정을 요약한 이미지

  • Spring Security 내부 동작 과정을 알기 위해선 Spring MVC의 기본 동작 원리를 알고 있어야 한다.
  • 우선, 아래 Spring MVC 내부 동작 과정에 대해 간략하게 알아보자.

Spring MVC 요청 과정

Spring MVC 요약한 이미지

  1. 사용자는 웹 브라우저를 통해 특정 URL로 요청을 보낸다. 그 요청에는 HTTP 메서드, 헤더 정보, 본문 데이터 등이 포함될 수 있다.
  2. 모든 요청은 Spring의 웹 서버에 도착하고, FrontController 역할을 하는 DispatcherServlet에 의해 처리가 되는데, 요청 URL, HTTP 메서드, 요청 헤더 정보 등을 분석하여 요청을 처리할 수 있는 핸들러를 HandlerMapping에 조회한다.
  3. HandlerAdapter 목록에서 요청에 맞는 핸들러를 조회한다.
  4. DispatcherServlet은 조회한 핸들러의 타입을 기반으로 HandlerAdapter를 통해서 핸들러를 실행한다.
  5. 핸들러는 일반적으로 @Controller@RestController와 같은 애노테이션으로 정의되며, @RequestMapping, @GetMapping, @PostMapping 등의 애노테이션을 통해 비즈니스 로직을 거쳐 Model 객체를 생성하여 DispatcherServlet에 반환한다.
  6. DispatcherServletViewResolver에게 Model 객체를 전달하고, 뷰 이름을 실제 뷰 페이지 경로로 변환하고, View Template Engine(Thymeleaf, JSP 등) 을 이용하여 Model 객체의 데이터를 HTML로 렌더링 하는 View 객체를 생성한다.
  7. 그렇게 렌더링 되어 생성된 HTML 코드를, DispatcherServlet에 응답해 준다.
  8. DispatcherServletViewResolver에서 렌더링 된 HTML 코드를 사용자(Client)에게 HTTP 응답으로 전송한다.

위 내용이 기본적인 Spring MVC의 요청 흐름이다.

그렇다면, 요청에 문제가 있거나, 권한이 없는 요청 등이 중요한 데이터에 접근하려 한다면 어떻게 처리해야 할까? 이 문제를 Srping Security가 해결할 수 있게 되었다.

Spring Securit 의 기본 동작 원리와 흐름

Spring Security 내부 동작 과정을 요약한 이미지

위 그림은 Spring MVC에서 Spring Security를 적용했을 때의 그림이다.

  1. 사용자는 웹 브라우저 또는 기타 애플리케이션을 통해 요청을 보낸다. 요청은 웹 서버(예: Nginx, Apache)에 도달하고, 웹 서버는 요청을 Servlet Container(예: Tomcat)로 전달한다.
  2. Servlet Container는 전달 받은 요청을 FilterChain (FilterChainServlet Container에 등록된 필터들의 목록)에 전달하고, 필터들은 순서대로 실행되며, 요청을 처리하고 다음 필터로 전달할지 여부를 결정한다.
  3. FilterChainDelegatingFilterProxySpring Context에서 SecurityFilterChain@Bean을 조회한다.

    Spring Security less than 5.7 version

     @Configuration
     public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
        
         @Override
         protected void configure(HttpSecurity http) throws Exception {
             http
                 .authorizeHttpRequests((authz) -> authz
                     .anyRequest().authenticated()
                 )
                 .httpBasic(withDefaults());
         }
     }
    

    Spring Security 5.7 version or more

     @Configuration
     public class SecurityConfiguration {
        
         @Bean
         public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
             // 각종 설정 들을 구성한다.
             http
                 .authorizeHttpRequests((authz) -> authz
                     .anyRequest().authenticated()
                 )
                 .httpBasic(withDefaults());
             return http.build();
         }
     }
    

    참고
    Spring Security 5.4에서는 SecurityFilterChain 빈(@Bean)을 생성하여 HttpSecurity구성하는 기능을 도입하였고, Spring Security 5.7.0-M2 부터 WebSecurityConfigurerAdapter의 기능이 Deprecated 되었다.

    • 여기서 문제가 발생한다. 필터는 WAS내에서 Spring SecuritySpring Context에서 각각 독립적인 구성요소로 운영 된다. 즉, 필터에서는 직접적으로 스프링 기능을 활용하기 어렵다.
    • 그렇다면, 어떻게 Spring Security를 사용할 수 있게 해결했을까? 바로, DelegatingFilterProxySpring SecurityFilterChainProxy위임(delegation)하는 전략을 취하게 되었다. 즉, 위임(delegating)을 통해 문제를 해결하였다.
  4. 그렇게 조회한 @BeanSecurityFilterChain에 설정된 보안(CSRF, XSS 등), 인증(Authentication), 인가(Authorization) 등의 작업을 통해 원하는 접근 제어를 할 수 있다.
  5. SecurityFilterChain에서 설정한 설정 값들이 모두 통과하게 되면 다음 필터의 단계로 넘어가게 되며, 모든 FilterChain의 필터들이 정상적으로 통과하게 되면 클라이언트의 요청은 DispatcherServlet으로 넘어가게 된다.

참고
Spring SecurityServlet Filter 기반으로 동작하며, Servlet과 Srping Context는 다르다.
  - Servlet Filter : 웹의 모든 요청가로채어 먼저 처리하는 역할을 수행한다, 톰캣과 같은 WAS에서 작동한다.
  - Spring Context : 스프링 IoC 컨테이너를 기반으로 구축되며, DI, AOP 등 다양한 기능을 제공한다.

package org.springframework.security.web

public interface SecurityFilterChain {
    boolean matches(HttpServletRequest request);

    List<Filter> getFilters();
}
  • SecurityFilterChain의 구현체는 matches()를 통해 자신에게 온 요청인지 확인하고 매칭된다면 getFilters()를 통해 FilterChainProxy에게 필터 체인을 제공한다. 즉, 여러 개의 필터 체인을 가지고 있을 수 있으며 요청에 따라 다른 보안 로직을 처리할 수 있다는 뜻이다.

그렇다면 왜 서블릿 필터와 스프링 컨텍스트로 나누는 전략을 취했을까?

  1. 분리된 책임 : 서블릿 필터(Servlet Filter) 는 주로 웹 요청과 응답에 대한 사전처리 및 사후 처리를 담당한다. 반면, 스프링 컨텍스트(Spring Context)애플리케이션의 비즈니스 로직, 빈 관리, 데이터 접근, 서비스 계층 등을 관리하게 된다. 이러한 분리는 관심사의 분리(Separation of concerns, SoC) 원칙을 따르며, 각각의 영역이 자신의 역할에 집중할 수 있도록 설계되었다. 그래서 개발자는 각 영역에 맞는 코드 작업과 테스트할 수 있다.
  2. 유연성과 확장성 : 서블릿 필터(Servlet Filter)스프링 컨텍스트(Spring Context) 를 분리함으로써, 각각 독립적으로 발전하고 확장할 수 있는 유연성을 제공한다. 예를 들어, 보안, 로깅, 인증 같은 기능을 필터에서 처리하고, 비즈니스 로직은 스프링 컨텍스트에서 처리할 수 있다. 이는 코드의 재사용 성과 유지보수 성을 향상시키게 된다.
  3. 기술적인 호환성 : 서블릿 API는 JavaEE 사양의 일부로, 다양한 웹 애플리케이션 서버(WAS)에서 지원된다. 스프링 프레임워크는 이러한 서블릿 API 위에서 동작하며, 스프링 기능을 제공한다. 이 구조는 스프링 기반 애플리케이션을 다양한 환경에서 실행할 수 있는 기술적 호환성을 보장한다.
  4. 보안의 강화 : 보안 관련 처리를 서블릿 필터(Servlet Filter) 단계에서 먼저 처리함으로써, 악의적인 요청이 스프링 컨텍스트(Spring Context) 내부의 비즈니스 로직에 도달하기 전에 차단될 수 있다. 이는 애플리케이션의 전반적인 보안 수준을 향상시키게 된다.

이러한 이유로, 서블릿 필터(Servlet Filter) 와 스프링 컨텍스트(Spring Context) 를 분리하여 애플리케이션의 구조를 더욱 견고하고 안전하게 만들며, 개발자가 유연하고 효율적인 애플리케이션을 개발하고 관리할 수 있도록 돕는다.
물론, 이러한 장점만 있는 것은 아니다.

가장 큰 단점복잡성이 증가하여 두 시스템을 별도로 관리해야 하기 때문에 개발 및 유지 관리에 더 많은 노력과 시간이 투자가 된다.
하지만, 앞서 언급한 장점들을 고려했을 때, 일반적으로 웹 애플리케이션 개발에서는 서블릿 필터(Servlet Filter)스프링 컨텍스트(Spring Context) 를 나누는 전략을 사용하는 것이 유리하며, 대규모 서비스나 복잡한 웹 애플리케이션 개발 시에는 이러한 전략의 장점들이 뚜렷이 나타나게 된다.

Reference