HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
🤩
개발
/
🔐
Spring Security
/
📖
Spring Security Architecture & Configure
📖

Spring Security Architecture & Configure

Spring Security ArchitectureConceptual Architecture실제 구현전체 흐름 구조도 & Filter 목록FilterChainProxy (springSecurityFilterChain — BeanName)웹 요청은 어떻게 FilterChainProxy로 전달될까? ← DelegatingFilterProxy코드로 살펴보는 흐름Spring Security Configuration — 필터 등록 설정configure(HttpSecurity http)configure(WebSecurity web)configure (AuthenticationManagerBuilder auth)user 정보 만들기 (InMemoryUserDetailsManager → UserDetailsService) using DelegatingPasswordEncoderrequestMachers 와 antMachers

Spring Security Architecture

  • 스프링 시큐리티는 간단히 말하면 서블릿 필터의 집합
  • 서블릿 필터는 서블릿(디스패처 서블릿) 실행 전에 실행되는 클래스들
  • 스프링이 구현하는 서블릿의 이름 : 디스패처 서블릿
    • notion image

Conceptual Architecture

  • 거시적인 관점에서 Spring Security는 웹 요청을 가로챈 후 사용자를 인증하고, 인증된 사용자가 적절한 권한을 지니고 있는 확인함
    • https://www.slideshare.net/analizator/spring-security-framework
    • [인증] AuthenticationManager : 사용자 인증 관련 처리
    • [인가] AccessDecisionManager : 사용자가 보호받는 리소스에 접근할 수 있는 적절한 권한이 있는지 확인
    • AuthenticationManager에서 request를 가로채서 credential을 확인함. valid하면 넘김
    • AccessDeicisonManager에서 request를 또 가로채서 이번엔 Authority를 확인함. 권한이 있는 유저면 접근 허용, 아니면 403 Forbidden

실제 구현

전체 흐름 구조도 & Filter 목록

https://docs.spring.io/spring-security/site/docs/current/reference/html5/#servlet-architecture
  • 왼쪽의 Filter chain 목록은 spring security와는 상관없는 Filter chain임
    • DelegatingFilterProxy : springSecurityFilterChain(FilterChainProxy) 자동으로 등록 & 해당 SecurityFilterChain으로 요청 전달
    • 여러 Security 정책이 공존할 수 있기에(url 패턴에 따라 FilterChain이 달라질 수 있음) DelegatingFilterProxy를 통하여 특정 FilterChain에 요청을 전달할 수 있게 구성됨
  • 각각의 필터는 단일책임원칙(SRP) 처럼, 각기 서로 다른 관심사를 해결함
FilterChainProxy를 구성하는 Filter 목록
  • 정말 다양한 필터 구현을 제공함
  • 결국 Spring Security를 잘 이해하고 활용한다는 것은 이들 Filter 를 이해하고, 적절하게 사용한다는 것을 의미함
Spring Security Reference
Imagine you're designing an application for a pet clinic. There will be two main groups of users of your Spring-based application: staff of the pet clinic, as well as the pet clinic's customers. The staff will have access to all of the data, whilst your customers will only be able to see their own customer records.
Spring Security Reference
https://docs.spring.io/spring-security/site/docs/current/reference/html5/#servlet-security-filters

FilterChainProxy (springSecurityFilterChain — BeanName)

  • Spring Security의 실제적인 구현은 서블릿 필터 (javax.servlet.Filter 인터페이스 구현체) 를 통해 이루어짐. Spring Security는 필터 구현체의 집합임
    • 서블릿 필터는 웹 요청을 가로챈 후 전처리 또는 후처리를 수행하거나, 요청 자체를 리다이렉트 하기도 함
  • FilterChainProxy 세부 내용은 WebSecurityConfigurerAdapter 추상 클래스를 상속하는 구현체에서 설정함 (보통 @EnableWebSecurity 어노테이션도 함께 사용)
    • 웹 요청은 이러한 필터 체인을 차례로 통과하게 됨
      • 웹 요청은 모든 필터를 통과하게 되지만, 모든 필터가 동작하는 것은 아님
      • 각 필터는 웹 요청에 따라 동작 여부를 결정할 수 있고, 동작할 필요가 없다면 다음 필터로 웹 요청을 즉시 넘김
    • 요청을 처리하고 응답을 반환하면 필터 체인 호출 스택은 모든 필터에 대해 역순으로 진행
    • 보통 springSecurityFilterChain 이라는 이름으로 Bean 등록됨

웹 요청은 어떻게 FilterChainProxy로 전달될까? ← DelegatingFilterProxy

  • 웹 요청을 수신한 서블릿 컨테이너는 해당 요청을 DelegatingFilterProxy (javax.servlet.Filter 인터페이스 구현체) 로 전달함
    • DelegatingFilterProxy Bean은 SecurityFilterAutoConfiguration 클래스에서 자동으로 등록됨
      • @Bean @ConditionalOnBean(name = DEFAULT_FILTER_NAME) public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(SecurityProperties securityProperties) { DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(DEFAULT_FILTER_NAME); registration.setOrder(securityProperties.getFilter().getOrder()); registration.setDispatcherTypes(getDispatcherTypes(securityProperties)); return registration; }
        SecurityFilterAutoConfiguration 구현 발췌 (DelegatingFilterProxyRegistrationBean 을 통해 DelegatingFilterProxy 인스턴스를 생성함)
      • DEFAULT_FILTER_NAME : springSecurityFilterChain
    • DelegatingFilterProxy는 실제적으로 웹 요청을 처리할 Target Filter Bean을 지정해야함
      • public class DelegatingFilterProxy extends GenericFilterBean { @Nullable private String targetBeanName; @Nullable private volatile Filter delegate;
      • Target Filter Bean은 바로 앞에서 알아본 FilterChainProxy
      • @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { // Lazily initialize the delegate if necessary. Filter delegateToUse = this.delegate; if (delegateToUse == null) { synchronized (this.delegateMonitor) { delegateToUse = this.delegate; if (delegateToUse == null) { WebApplicationContext wac = findWebApplicationContext(); if (wac == null) { throw new IllegalStateException("No WebApplicationContext found: " + "no ContextLoaderListener or DispatcherServlet registered?"); } delegateToUse = initDelegate(wac); } this.delegate = delegateToUse; } } // Let the delegate perform the actual doFilter operation. invokeDelegate(delegateToUse, request, response, filterChain); } protected Filter initDelegate(WebApplicationContext wac) throws ServletException { String targetBeanName = getTargetBeanName(); Assert.state(targetBeanName != null, "No target bean name set"); Filter delegate = wac.getBean(targetBeanName, Filter.class); if (isTargetFilterLifecycle()) { delegate.init(getFilterConfig()); } return delegate; } protected void invokeDelegate(Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { delegate.doFilter(request, response, filterChain); }
        DelegatingFilterProxy 구현 발췌 (실제적으로 요청을 처리할 delegate 로 요청을 전달함)

코드로 살펴보는 흐름

// DelegatingFilterProxy @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { // Lazily initialize the delegate if necessary. Filter delegateToUse = this.delegate; if (delegateToUse == null) { synchronized (this.delegateMonitor) { delegateToUse = this.delegate; if (delegateToUse == null) { WebApplicationContext wac = findWebApplicationContext(); if (wac == null) { throw new IllegalStateException("No WebApplicationContext found: " + "no ContextLoaderListener or DispatcherServlet registered?"); } delegateToUse = initDelegate(wac); } this.delegate = delegateToUse; } } // Let the delegate perform the actual doFilter operation. invokeDelegate(delegateToUse, request, response, filterChain); } protected void invokeDelegate( Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { delegate.doFilter(request, response, filterChain); }
DelegatingFilterProxy에서 들어온 request에 대해 doFilter를 처리 invokeDelgate( ) 호출을 통해 FilterChainProxy로 request 넘김

Spring Security Configuration — 필터 등록 설정

Spring Security without the WebSecurityConfigurerAdapter
  • @EnableWebSecurity 어노테이션 추가, WebSecurityConfigurerAdapter 클래스를 확장하는 Configuration 클래스를 생성
    • 기본적인 Spring Security 설정이 자동으로 추가되며, 개별 설정을 override 할수 있음
  • @EnableWebSecurity → Spring security의 configuration을 책임지는 클래스에 대해 붙이는 annotation임. 이 어노테이션이 붙으면 해당 필터를 필터체인에서 동작하도록 해줌
@@EnableWebSecurity(debug=true) @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { super.configure(auth); } @Override protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/api/**") // 해당 메서드를 통해 어떤 url에 대해 필터들이 적용될 것인지 정의 가능함 super.configure(http); } }
  • WebSecurityConfigurerAdatper 를 상속한 클래스를 통해 Security FilterChain을 구성할 수 있음
  • 필터체인에서 어떤 필터들이 적용되고 있는지를 보려면 @EnableWebSecurity(debug=true) 를 설정하면 됨
  • 필터체인을 여러개 만들고 싶을때는 WebSecurityConfigurerAdatper 를 상속한 클래스를 여러 개 만들기. 그 후 @Order(1), @Order(2)와 같은 식으로 숫자가 낮은 것 부터 적용되도록 해야함

configure(HttpSecurity http)

@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests(request->{ request .antMatchers("/**").permitAll() ; }) ; // 사용예시 http .authorizeRequests() .antMatchers("/me").hasAnyRole("USER", "ADMIN") .anyRequest().permitAll() .and() .formLogin() .defaultSuccessUrl("/") .permitAll() ; }
  • authorizeRequests 메서드를 활용하여 특정한 url에 대해 인가를 할 수 있음
  • .formLogin() 을 통해 폼 로그인
  • HttpSecurity 클래스는 세부적인 웹 보안기능 설정을 처리할 수 있는 API를 제공
  • 필터체인에 들어갈 필터를 configure할 수 있음
HttpSecurity 주요 메소드
메소드명
설명
authorizeRequests()
공개 리소스 또는 보호받는 리소스에 대한 세부 설정
formLogin()
로그인 폼 기능 세부설정
logout()
로그아웃 기능 세부설정
rememberMe()
자동 로그인 기능 세부설정

configure(WebSecurity web)

@Override public void configure(WebSecurity web) throws Exception { web.ignoring() .requestMatchers( PathRequest.toStaticResources().atCommonLocations() ); }
  • WebSecurity 클래스는 필터 체인 관련 전역 설정을 처리할 수 있는 API 제공
    • ignoring()
      • Spring Security 필터 체인을 적용하고 싶지 않은 리소스에 대해 설정
      • 일반적으로 정적 리소스(*.html, *.css, *.js 등)을 예외 대상으로 설정함
      • 불필요한 서버 자원 낭비를 방지함
  • atCommonLocations() 를 살펴보면, js, static 등등 디폴트 정적리소스 경로들에 대해 SpringSecurity접근을 안할 수 있도록 설정해주는 것임

configure (AuthenticationManagerBuilder auth)

  • 기본 로그인 계정 추가 가능함
  • 기본 user 정보는 UserDetailsServiceAutoConfiguration 클래스를 참고하면 어떻게 생성되는지 확인이 가능함

user 정보 만들기 (InMemoryUserDetailsManager → UserDetailsService) using DelegatingPasswordEncoder

@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { Map<String,PasswordEncoder> encoders = new HashMap<>(); encoders.put("noop", NoOpPasswordEncoder.getInstance()); auth.inMemoryAuthentication() .passwordEncoder(new DelegatingPasswordEncoder("noop", encoders)) .withUser("user").password("{noop}user123").roles("USER").and() .withUser("admin").password("{noop}admin123").roles("ADMIN"); }
  • DelegatingPasswordEncoder에 들어가는 map에 사용할 PasswordEncoder들을 다 등록함
  • DelegatingPasswordEncoder를 생성할 시, 어떤 PasswordEncoder를 쓸 것인지 key의 이름을 명시해주고 map을 파라미터로 넘겨줌
  • password( ) 파라미터에 어떠한 PasswordEncoder를 쓸 것인지 id를 { } 안에 명시해주어야 함.
 

requestMachers 와 antMachers

endpoint 인가& 인증 적용 방법
endpoint 인가& 인증 적용 방법