Spring/Spring Security 6.x

초기화 과정 이해

s_y_130 2025. 6. 25. 16:27

프로젝트 생성 / 의존성 추가


프로젝트 구성

  • Spring Boot 3.x 버전 (현재 3.2.0)
  • JDK 17
  • Gradle 빌드

의존성

  • implementation 'org.springframework.boot:spring-boot-starter-security'
  • implementation 'org.springframework.boot:spring-boot-starter-web'
  • testImplementation 'org.springframework.boot:spring-boot-starter-test'
  • testImplementation 'org.springframewor

 

자동 설정(Auto Configuration) 에 의한 기본 보안 작동

  • 서버가 기동되면 스프링 시큐리티의 초기화 작업 및 보안 설정이 이루어진다
  • 별도의 설정이나 코드를 작성하지 않아도 기본적인 웹 보안 기능이 현재 시스템에 연동되어 작동한다
    1. 기본적으로 모든 요청에 대하여 인증여부를 검증하고 인증이 승인되어야 자원에 접근이 가능하다
    2. 인증 방식은 폼 로그인 방식과 httpBasic 로그인 방식을 제공한다
    3. 인증을 시도할 수 있는 로그인 페이지가 자동적으로 생성되어 렌더링 된다
    4. 인증 승인이 이루어질 수 있도록 한 개의 계정이 기본적으로 제공된다
      • SecurityProperties 설정 클래스에서 생성
      • username : user
      • password : 랜덤 문자열

 

SpringBootWebSecurityConfiguration

자동 설정에 의한 기본 보안 설정 클래스 생성

@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
static class SecurityFilterChainConfiguration {

    @Bean
    @Order(SecurityProperties.BASIC_AUTH_ORDER)
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
        http.formLogin(withDefaults());
        http.httpBasic(withDefaults());
        return http.build();
    }

}
  • http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated())
    • 기본적으로 모든 요청에 대하여 인증여부를 검증하고 인증이 승인되어야 자원에 접근이 가능하다
  • http.formLogin(withDefaults()); http.httpBasic(withDefaults());
    • 인증 방식은 폼 로그인 방식과 httpBasic 로그인 방식을 제공한다

 

문제점

위 기본 보안 설정을 통한 문제점은 다음과 같다.

  • 기본 계정을 제공해 주긴 하지만 계정 추가나 권한 추가 시 문제
  • 시스템에서 필요로 하는 더 세부적이고 추가적인 보안기능이 필요할 때 문제

 

실습

IndexController

package com.example.springsecuritymaster;

@RestController
public class IndexController {

    @GetMapping("/")
    public String index() {
        return "index";
    }
}
  • "/" 경로로 문자열을 반환해주는 간단한 컨트롤러이다.

 

프로젝트 실행

  • http://localhost:8080

프로젝트 실행 시 본래 우리가 기대했던 결과는 "index"가 출력되는 것이다. 하지만 아래와 같은 화면이 나타났다.

  • "자동 설정에 의한 기본 보안 작동" 에서 설명 했던 내용이 그대로 나타났다.
  • 즉,  기본적으로 모든 요청에 대하여 인증여부를 검증하기 위해 폼 로그인 방식으로 인증을 시도할 수 있는 로그인 페이지가 자동적으로 생성되어 렌더링 되어 제공된 것이다.

그렇다면 기본으로 제공해주는 계정을 통해 로그인 해보자. 우리가 만든 IndexController 가 출력된다.

 

 

 

그렇다면 시큐리티는 어떻게 기본 계정을 제공하는 것일까?

 

기본 계정 제공

SecurityProperties 

SecurityProperties의 User 클래스를 살펴보면 익숙한 필드가 보인다. 

그런데 보통 우리가 User 정보를 어플리케이션에 제공하면 이 정보는 DB 에 저장되어 있는 User 정보와 비교를 통해 인증이 이뤄지는 것이 일반적이다.

 

하지만 현재 (새로 생성한) 어플리케이션에는 DB를 사용하고 있지 않는다. 즉, 스프링 시큐리티는 기본 User 정보를 어딘가에 저장해두고 요청이 오면 비교를 해야 한다.

User 클래스에 있는 getName() 과 getPassword() 가 스프링 시큐리티 초기화 시점에 어디서 호출되는지 디버깅를 해보자

 

UserDetailsServiceAutoConfiguration

디버깅 해본 결과 InMemoryUserDetailsManager라는 대충 메모리로 UserDetails 정보를 관리하는 Manager가 생성되는 것을 확인할 수 있다.

 

바로 이 InMemoryUserDetailsManager가 우리에게 기본 계정을 생성해서 제공해 주는 것이다.

 

 


초기화 과정 살펴보기

어쨌든 이렇게 기본 계정을 통해 인증을 유지하기 전에 스프링 시큐리티는 초기화 과정을 통해 웹 보안 기능이 동작해야 한다. 그렇게 동작한 보안 기능은 우리에게 로그인 절차를 제공하며 인증을 해야하는 상황을 제공해줄 것이다.

 

위에서 살펴봤다시피 SpringBootWebSecurityConfiguration 이 기본 보안 설정에 관한 Bean을 생성해준다.

SpringBootWebSecurityConfiguration

그런데 SecurityFilterChainConfiguration은 무조건 실행되는 것이 아니다.

 

@ConditionalOnDefaultWebSecurity을 살펴보자.

@ConditionalOnDefaultWebSecurity의 Conditional은 DefaultWebSecurityCondition 이다.

 

DefaultWebSecurityCondition 

  • @ConditionalOnClass({ SecurityFilterChain.class, HttpSecurity.class })
    • SecurityFilterChain, HttpSecurity 이 ClassPath에 존재하는가? Yes
    • 스프링 시큐리티 의존성을 추가했기 때문에 이 두 클래스가 ClassPath에 존재한다.
  • @ConditionalOnMissingBean({ SecurityFilterChain.class })
    • SecurityFilterChain 빈이 없을 때 동작한다.
    • 우리는 SecurityFilterChain 타입의 빈을 직접적으로 생성한 적이 없다. 

결과적으로 두 조건이 참(true)이기 때문에  SecurityFilterChainConfiguration의 SecurityFilterChain 빈이 생성된다.

 

위에서 찍은 breakpoint에 진입하면 위 두 조건이 정상적으로 동작한 것을 확인할 수 있다. (두 조건 중 하나라도 false 일 경우 breakpoint에 진입을 못한다.)

 

 

결국 이러한 스프링 시큐리티 자동 구성도 스프링 부트가 제공해주는 것이다.

 

그리고 https://docs.spring.io/spring-boot/docs/current/reference/html/auto-configuration-classes.html 을 살펴보면 스프링 부트가 자동 구성해주는 configuration 중에 SecurityAutoConfiguration 이 있다.

 

바로 이 SecurityAutoConfiguration @Import 에 있는 SpringBootWebSecurityConfiguration 이 추가적으로 자동 설정된다.

SecurityAutoConfiguration 

 

5. 자동 구성(Auto Configuration)

 

s-y-130.tistory.com

 

 

 

 

 

SecurityBuilder / SecurityConfigurer


스프링 시큐리티는 초기화 때 인증/인가와 관련된 작업들을 진행한다. 그리고 이러한 작업들을 종합적으로 처리하는 두 클래스가 바로 SecurityBuilder 와 SecurityConfigurer 이다.

  • SecurityBuilder 는 빌더 클래스로서 웹 보안을 구성하는 빈 객체와 설정클래스들을 생성하는 역할을 하며 대표적으로  WebSecurity, HttpSecurity(중요) 가 있다
  • SecurityConfigurer 는 Http 요청과 관련된 보안처리를 담당하는 필터들을 생성하고 여러 초기화 설정에 관여한다.
    • 스프링 시큐리티는 모든 인증/ 인가 요청을 필터를 통해 처리한다. 즉, 스프링 시큐리티는 터 기반 보안 프레임워크라고 해도 과언이 아니다.
  • SecurityBuilder 는 SecurityConfigurer 를 참조하고 있으며 인증 및 인가 초기화 작업은 SecurityConfigurer 에 의해 진행된다.

 

동작 과정

  1. 자동 설정에 의해 SecurityBuilder 클래스를 생성한다.
  2. SecurityBuilder 는 웹 보안을 구성하는 빈 객체와 설정 클래스들을 생성하는 역할을 하므로 SecurityConfigurer 타입의 설정 클래스들을 생성한다. 
  3. SecurityConfigurer 는 내부적으로 init(B builder), configure(B builder) 메소드를 통해 초기화 작업을 진행한다.
    • 이때 매개변수로 받는 B builder가 SecurityBuilder 이다. 
    • 이때 filter 생성 및 인증/ 인가와 관련된 설정들을 한다.

 

구체적인 동작 과정

 

  1. SecurityBuilder 의 구현체 중에서 가장 핵심적인 역할을 하는 것이 HttpSecurity 이다. 그리고 이 HttpSecurity 이 자동 설정에 의해 빈으로 등록된다.
  2. HttpSecurity 는 SecurityConfigurer 타입의 설정 클래스들을 생성한다. 
  3. SecurityConfigurer 의 init(B builder), configure(B builder)는 모든 초기화 작업들이 이뤄진다. 

 

코드 흐름 살펴보기

SecurityBuilder 

SecurityConfigurer 

 

SecurityBuilder 의 구현체 중에서 가장 핵심적인 역할을 하는 것이 HttpSecurity 이므로 이를 중심으로 살펴보자.

 

 

HttpSecurityConfiguration

HttpSecurityConfiguration에서 HttpSecurity 빈을 생성하고 있으며 스코프가 prototype 이므로 각 HttpSecurity 객체를 생성할 때마다 빈으로 등록된다.

 

두 번째 breakpoint 부분이 HttpSecurity 초기 설정 부분이다. 

우선 csrf() 부터 따라가보자.

 

HttpSecurity

HttpSecurity 의 csrf() 를 보면 new CsrfConfigurer() 을 생성해주고 있으며 이를 getOrApply() 를 통해 어딘가에 적용해주고 있다. 

또한 CsrfConfigurer의 상속 구조를 따라가보면 SecurityConfigurer 을 상속받고 있는 것을 확인할 수 있다.

 

exceptionHandling() 도 마찬가지로 new ExceptionHandlingConfigurer<>()) 을 생성해주면서 이를 getOrApply() 를 통해 어딘가에 적용해주고 있다. 

 

나머지 메소드들도 동일하게 동작한다.

 

그럼 이렇게 여러 초기 설정을 하고 생성한 HttpSecurity 빈을 어딘가에 주입해줘야 할것이다.

 

SpringBootWebSecurityConfiguration

HttpSecurity 빈을 주입 받는 곳이 바로 이전 주제에서 살펴봤던 SecurityFilterChainConfiguration의 SecurityFilterChain 빈 생성 부분이다.

 

그리고 주입된 HttpSecurity 의 configurers 를 살펴보면 초기 설정한 11개의 configurer들이 있는 것을 확인할 수 있다.

formLogin과 httpBasic 도 각각의 configurer을 HttpSecurity에 등록한다. 또한 DefaultLoginPage를 위한 configurer도 추가되었다.

 

자, 이렇게 SecurityConfigurer 설정 클래스들을 생성했지만 아직  init(B builder), configure(B builder) 두 메소드는 실행하지 않았다. 이제 두 메소드의 실행 흐름을 따라가보자.

 

위에서 http.build() 메소드에 들어가보면 doBuild() 메소드가 실행되고 있다. 그리고 doBuild()에서 init(), configure() 메소드가 실행되는 것을 확인할 수 있다. 

init()

init() 의 configurers 보면 아까 설정된 14개의 configure들을 확인할 수 있다. 그리고 각각의 configure의 init()을 호출하면서 초기화 작업이 이뤄지고 있다. 

 

configure() 

configure() 에서도 마찬가지로 14개의 configure들의 configure() 을 호출하면서 각각의 설정이 진행되고 있다.

 

그리고 configure() 안에서 filter 를 생성하는 등의 설정 작업이 이뤄지고 있다.

 

이렇게 각각의 configure 설정이 마무리 되면 그제서야 SecurityBuilder 를 통한 HttpSecurity 설정이 마무리되며  SecurityFilterChain빈이 생성된다.

 

 

 

 

WebSecurity / HttpSecurity


HttpSecurity 

  • HttpSecurityConfiguration 에서 HttpSecurity 를 생성하고 초기화를 진행한다
  • HttpSecurity 는 보안에 필요한 각 설정 클래스와 필터들을 생성하고 최종적으로 SecurityFilterChain 빈 생성

  • HttpSecurityConfiguration 에서 HttpSecurity 를 생성하고 이 객체는 내부적으로 각 SecurityConfigurer 구현체들을 생성한다. 
  • 이 설정 클래스들을 통해서 초기화 작업이 진행된다. 
  • build() 메소드가 호출되면 각 SecurityCongifurer 구현체들의 init(), configure()이 호출되면서 인증/인가와 관련된 여러 객체들 (필터 등...) 을 생성한다.
  • 그리고 이렇게 생성된 필터 목록들은 SecurityFilterChain 빈 안에 저장된다. 즉, SecurityFilterChain 이 각 인증/인가에 필요한 필터들을 갖고 있다는 말이 된다. (그래서 FilterChain 이라고 하는 것이다.)

 

SecurityFilterChain

SecurityFilterChain은 인터페이스이면 구현체는 DefaultSecurityFilterChain이다.

SecurityFilterChain는 matches(), getFilters() 두 메소드를 갖고 있다. 

 

 

 

WebSecurity 

  • WebSecurityConfiguration 에서 WebSecurity 를 생성하고 초기화를 진행한다
  • WebSecurity 는 HttpSecurity 에서 생성한 SecurityFilterChain 빈을 SecurityBuilder 에 저장한다.
    • (다중 보안 설정) SecurityFilterChain 는 여러 개 생성될 수 있으며 이 SecurityFilterChain 들이 모두 SecurityBuilder 에 저장된다.
  • WebSecurity 의 build() 를 실행하면 SecurityBuilder 에서 SecurityFilterChain 을 꺼내어 FilterChainProxy 생성자에게 전달한다.
  • 즉, 최종적으로 FilterChainProxy 이 설정된 모든 보안 설정되 필터들을 다 갖고 있는 형태가 된다.

 

 

코드 흐름 살펴보기

우선 이전 챕터에서 HttpSecurity 빈 생성 부분부터 상기해보자. 이전 시간에 배운 흐름처럼 아래 코드 흐름으로 HttpSecurity 빈이 생성된다. 

 

그리고 이와 동시에 WebSecurityConfiguration의 setFilterChainProxySecurityConfigurer() 메소드를 살펴보면 우선 WebSecurity 객체를 만들고 있다. WebSecurity 는 HttpSecurity 에서 만든 빈들을 참조해야 하기 때문에 HttpSecurity 빈 생성 이후에 만들어 지는 것이다. 

WebSecurityConfiguration -&nbsp; setFilterChainProxySecurityConfigurer()

 

그리고 이렇게 HttpSecurity 이 생성되면 최종적으로 HttpSecurity 이 주입되면서 SecurityFilterChain 빈이 생성되어야 한다. 이때 모든 설정들은 앞 과정을 통해 완료된 상태이다. 

 

 

SecurityFilterChain 생성 과정

http.build() 를 호출하면 SecurityFilterChain 이 생성될텐데 이 과정을 살펴보자.

 

HttpSecurity 의 performBuild()는 최종적으로 이 build를 완수해라~라는 의미의 메소드다.

 

현재 HttpSecurity 에서는 앞 과정을 통해 모든 설정이 끝났다. 그 말인 즉슨, 필터까지 모두 생성됐다는 의미가 된다. 

총 16개의 필터가 생성됐으며 이 필터들이 사용자 요청에 대해서 체인으로 연결되어 모든 요청들을 처리하는 필터가 될것이다. 

 

그러면 이제 이 필터들을 활용해야 한다.

 

이 단계 중 첫번째 단계로 HttpSecurity가 DefaultSecurityFilterChain 을 생성하는 것부터 시작한다. 그리고 이 DefaultSecurityFilterChain 의 생성자로 requestMatcher와 Filters 를 넘겨주고 있다. 

 

requestMatcher은 앞서 설명했다시피 현재 요청이 해당 SecurityFilterChain 에 의해 처리되어야 하는지 여부를 결정한다. 

 

그런데 디폴트 requestMatch 는 어떤 요청에 대해 검증을 진행할까?

requestMatcher 필드를 보면 AnyRequestMatcher 가 되어 있다. 즉, 특정한 URL 요청이 아니라 모든 요청에 대해 검증을 진행하겠다는 것이 디폴트 requestMatch 타입이다. 

 

이 모든 과정이 HttpSecurity 최종 완성에 해당하는 부분이며 SecurityFilterChain 이 생성되면서 HttpSecurity 의 과정이 끝나게 된다.

 


그럼 이제 WebSecurity로 가서 WebSecurity가 HttpSecurity이 만든 SecurityFilterChain 을 어떻게 활용해서 최종적으로 모든 설정을 완료하는 지 그 과정을 살펴보자.

 

WebSecurityConfiguration의 setFilterChains 메소드이다. 

WebSecurityConfiguration - setFilterChains()

  • @Authwired 가 붙은 것을 보니 DI 에 의해 SecurityFilterChain 을 주입받고 있는 것을 알 수 있다.
  • 파라미터가 List 인 것을 보니 여러 SecurityFilterChain 을 주입 받을 수 있다. 
  • 우리는 자동 설정에 의해 생성된 하나의 SecurityFilterChain 빈만 주입받았을 것이다.

그 다음에 위 메소드를 살펴보면 현재 DefaultSecurityFilterChain을 WebSecurity 어떤 필드에 저장하고 있다. 왜냐하면 WebSecurity 가 SecurityFilterChain을 활용해야 하기 때문이다. 

 

여기서 집고 넘어가야 할 부분이 SecurityFilterChain이 클라이언트 요청을 처리하는 주체가 아니라는 것이다. 그 주체는 바로 WebSecurity가 최종적으로 만드는 FilterChainProxy이다.  그리고 우리는 이 과정을 살펴보고 있는 중이다.

WebSecurity의 addSecurityFilterChainBuilder() 에서는 securityFilterChainBuilders라는 속성에 하나씩 add() 하고 있는 것을 볼 수 있다. add 라는 것을 보니 이 역시도 여러 securityFilterChainBuilder들을 저장할 수 있어 보인다.

 


여기가지가 아래 그림의 build() 전 과정이다. 이제 다음 과정을 살펴보자.

 

WebSecurity의 performBuild() 메소드이다. 복잡한 부분은 건너 뛰고 파란색 부분을 보면 securityFilterChainBuilders를 반복하면서 각각의 securityFilterChainBuilder들을 build() 하여 securityFilterChain 을 얻고 있다. 

그리고 이를 다시 securityFilterChains 에 add 하고 있다. 

 

그러고 나서 FilterChainProxy를 생성하고 있으며 위에서 얻은 securityFilterChains 를 생성자에 전달해주고 있다.

 

위 절차로 생성된 FilterChainProxy이 바로 this.webSecurity.build() 의 결과이며 최종적으로 FilterChainProxy 빈이 생성된다. 

 

 

참고

FilterChainProxy 빈 생성 코드를 보면 리턴 타입이 Filter 다??

예상했다시피 FilterChainProxy 의 상속 구조를 타고 올라가다보면 Filter 인터페이스가 있는 것을 확인할 수 있다. (다형성)

 

 

 

 

 

 

 

 

DelegatingFilterProxy / FilterChainProxy


Filter

  • 서블릿 필터는 웹 애플리케이션에서 클라이언트의 요청과 서버의 응답을 가공하거나 검사하는데 사용되는 구성 요소이다
  • 서블릿 필터는 클라이언트의 요청이 서블릿에 도달하기 전이나 서블릿의 응답을 클라이언트에게 보내기 전에 특정 작업을 수행할 수 있다
  • 서블릿 필터는 서블릿 컨테이너(WAS)에서 생성되고 실행되고 종료된다

 

 

 

DelegatingFilterProxy 

  • DelegatingFilterProxy 는 스프링에서 사용되는 특별한 서블릿 필터로, 서블릿 컨테이너와 스프링 애플리케이션 컨텍스트 간의 연결고리 역할을 하는 필터이다
  • DelegatingFilterProxy 는 서블릿 필터의 기능을 수행하는 동시에 스프링의 의존성 주입 및 빈 관리 기능과 연동되도록 설계된 필터라 할 수 있다
  • DelegatingFilterProxy 는 “springSecurityFilterChain” 이름으로 생성된 빈을 ApplicationContext 에서 찾아 요청을 위임한다
  • 실제 보안 처리를 수행하지 않는다

앞서 스프링 시큐리티는 필터 기반으로 요청을 처리하고 수행한다고 했다.

그리고 본래 필터는 서블릿 컨테이너에서 생성, 실행, 종료 된다. 즉, 스프링 컨테이너하고는 전혀 상관없는 것이다. 

이 말인 즉슨, 필터에서는 스프링 기능을 사용할 수 없다는 얘기가 된다. (ex. DI, AOP 와 같은 기능들을 일반적인 필터에서는 사용할 수 없다는 뜻이다.) 

 

그럼에도 불구하고 스프링 시큐리티는 이 필터 기반으로 설계를 원했고 또 이 설계를 통해 스프링 시큐리티 기능을 동작시키고자 했다.

 

그래서 이를 해결하고자 스프링에서도 필터라는 타입의 클래스들을 빈으로 생성하고 필터의 기능도 갖지만 DI, AOP 같은 스프링 기능들도 가능하게 해서 어떤 요청을 처리하게끔 하고자 등장한 것이 바로 DelegatingFilterProxy 인것이다. 

 

스프링의 DelegatingFilterProxy  활용 방법

DelegatingFilterProxy 은 서블릿 컨테이너에서 생성되는 필터이다. 즉, 스프링하고는 관련이 없다.

DelegatingFilterProxy 를 스프링이 활용한다는 것인데 어떻게 활용하는 것일까?

 

클라이언트 요청이 오면 일단 일반적인 필터들을 거치게 된다. 그러고 만약에 DelegatingFilterProxy 을 만나게 되면  DelegatingFilterProxy 는 “springSecurityFilterChain” 이름으로 생성된 빈을 ApplicationContext 에서 찾아 클라이언트의 요청을 위임한다. 

 

사실상 DelegatingFilterProxy 이 구체적으로 하는 일은 없다. 단지 요청을 받아 위임하는 것이 전부다. 

 

 

 

 

 

FilterChainProxy

  • springSecurityFilterChain 의 이름으로 생성되는 필터 빈으로서 DelegatingFilterProxy 으로 부터 요청을 위임 받고 실질적인 보안 처리 역할을 한다
  • 내부적으로 하나 이상의 SecurityFilterChain 객체들을 가지고 있으며 요청 URL 정보를 기준으로 적절한 SecurityFilterChain 을 선택하여 필터들을 호출한다
  • HttpSecurity 를 통해 API 추가 시 관련 필터들이 추가된다
  • 사용자의 요청을 필터 순서대로 호출함으로 보안 기능을 동작시키고 필요 시 직접 필터를 생성해서 기존의 필터 전.후로 추가 가능하다

 

 

 

코드 흐름 살펴보기

DelegatingFilterProxy 생성 과정

SecurityFilterAutoConfiguration

 

현재 이 클래스는 SecurityFilterAutoConfiguration라는 설정 클래스로 자동 설정으로 실행되고 있다.

여기서 DelegatingFilterProxyRegistrationBean 빈을 생성해주는 부분이 있다. 여기가 바로 DelegatingFilterProxy 객체를 생성해주는 부분이라고 할수 있다.

 

그런데 이 빈이 생성될 때 DEFAULT_FILTER_NAME 이라는 상수 값을 생성자로 전달하고 있다.

DEFAULT_FILTER_NAME 값을 보면 "springSecurityFilterChain" 이라는 값이다. 앞서 말했다시피 DelegatingFilterProxy 는 “springSecurityFilterChain” 이름으로 생성된 빈을 ApplicationContext 에서 찾기 때문에 빈 생성 과정에서부터 전달하고 있는 것이다. 

 

그리고 DelegatingFilterProxyRegistrationBean 안에서 DelegatingFilterProxy를 생성하고 있는데 this.targetBeanName 으로 "springSecurityFilterChain" 을 전달하고 있다. 

 

그리고 위에서 생성한 DelegatingFilterProxy는 서블릿 컨텍스트에  필터로 추가된다. 

그래서 DelegatingFilterProxy는 스프링에서 활용은 하지만 실질적으로는 서블릿 컨텍스트에 추가되는 필터라고 볼 수 있다.

AbstractFilterRegistrationBean - addRegistration()

 

 

요청에 따른 흐름 살펴보기

http://localhost:8080/으로 요청을 보내보자.

 

DelegatingFilterProxy는 생성 과정 때 생성자를 통해 targetBeanName으로 "springSecurityFilterChain"을 저장했다.

그리고 요청이 와서 doFilter()가 실행되면 우선 delegateToUse 에서 위임할 필터를 찾는다.

 

위 코드를 보면 WebApplicationContext 즉, 스프링 컨테이너가 보인다. 

그리고 initDelegate() 메소드를 살펴보면 스프링 컨테이너에서 빈 이름이"springSecurityFilterChain"이고 타입이 Filter.class인 빈을 얻는다. 

 

그 이후에는 invokeDelegate() 가 호출되며 여기서 delegate.doFilter()가 호출된다.

 

물론 delegate.doFilter()는 CompositeFilter 의 doFilter()를 호출하겠지만 결국 FilterChainProxy 의 doFilter() 호출할 것이다.

 

그리고 FilterChainProxy 의 doFilter() 에서는 doFilterInternal()이 실행될 것이고 여기서 기본 설정된 필터들이 순서대로 실행되는 것이다.

 

 

 

 

참고

FilterChainProxy 는 내부 클래스로 VirtualFilterChain을 갖고 있는데 이는 필터를 체인으로 연결한 가상의 필터들을 내부적으로 갖고 있는 것이다. 

본래 필터는 WAS에서 사용되는 것이므로 스프링에서는 스프링 전용 필터들을 가상으로 갖고 있겠다는 의미이다.

 

 

 

 

 

사용자 정의 보안 설정하기


사용자 정의 보안 기능 구현

한 개 이상의 SecurityFilteChain 타입의 빈을 정의한 후 인증 API 및 인가 API 를 설정한다

 

기본 구현 코드

SecurityConfig

package com.example.springsecuritymaster;

import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@EnableWebSecurity
@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            .formLogin(Customizer.withDefaults());

        return http.build();
    }
}
  • @EnableWebSecurity 을 클래스에 정의한다
  • 모든 설정 코드는 람다 형식으로 작성해야 한다 (스프링 시큐리티 7 버전부터는 람다 형식만 지원 할 예정)
  • SecurityFilterChain 을 빈으로 정의하게 되면 자동설정에 의한 SecurityFilterChain 빈은 생성되지 않는다

 

http://localhost:8080/으로 요청을 보내면 우리가 설정했던 formLogin 방식을 통해 인증을 받는 것을 확인할 수 있다.

 

이때 우리가 직접 SecutiryFilterChain을 빈으로 등록했기 때문에 위에서의 "초기화 과정 살펴보기"에서 살펴봤던 conditional 조건에 중 @ConditionalOnMissingBean({ SecurityFilterChain.class })에 부합하지 않아 SpringBootWebSecurityConfiguration에서의 빈 등록이 동작하지 않는다.

 

사용자 추가 설정

1. application.properties 혹은 application.yml 파일에 설정한다

spring:
  security:
    user:
      name: user
      password: 1111
      roles: USER

 

 

2. 자바 설정 클래스에 직접 정의한다

@Bean
public UserDetailsService userDetailsService() {

    UserDetails user = User.withUsername("user")
        .password("{noop}1111")
        .roles("USER")
        .build();

    return new InMemoryUserDetailsManager(user);
}

 

yml 보다 설정 클래스가 우선 시 된다.