[Spring Security] 초기화 과정

심현민·2025년 9월 9일
2

Spring

목록 보기
13/18
post-thumbnail

Spring Security를 사용하면서 SecurityFilterChain을 설정할 때 항상 사용하는 HttpSecurity 객체의 동작 방식이 궁금했습니다.

어떻게 메서드를 연쇄적으로 호출하는 것만으로 복잡한 보안 설정이 적용되는지, 그 내부 원리를 파악하기 위해 코드를 분석하고 학습한 내용을 기록했습니다.

  • HttpSecurity 객체의 생성 방식과 생명 주기 파악
  • .authorizeHttpRequests(), .csrf() 등 설정 메서드의 내부 동작 원리 이해
  • SecurityBuilderSecurityConfigurer의 관계 분석
  • 최종적으로 SecurityFilterChain이 생성되는 과정 추적

핵심 인터페이스

본격적인 분석에 앞서, Spring Security의 설정을 이해하는 데 핵심적인 두 가지 인터페이스의 역할을 먼저 정리했습니다.

  • SecurityBuilder: 이름 그대로 보안 설정을 구성하는 빌더(Builder) 인터페이스입니다. HttpSecurity는 이 인터페이스의 가장 중요한 구현체 중 하나로, 최종 결과물인 SecurityFilterChain을 생성하는 역할을 담당합니다.

  • SecurityConfigurer: 특정 보안 기능을 설정하고 관련 필터를 생성하는 역할을 합니다. 예를 들어, CsrfConfigurer는 CSRF 보호 설정을, SessionManagementConfigurer는 세션 관리 설정을 담당합니다. HttpSecurity는 내부에 이러한 Configurer들을 목록 형태로 유지하고 관리합니다.

즉, HttpSecurity가 다수의 SecurityConfigurer를 활용하여 SecurityFilterChain을 만드는 구조로 파악할 수 있습니다.


HttpSecurity 빈은 어떻게 생성될까?

가장 먼저 SecurityFilterChain 메서드의 파라미터로 주입되는 HttpSecurity 객체가 어디서 오는지 추적했습니다. Spring Boot의 자동 설정 클래스인 SpringBootWebSecurityConfiguration 내부에서 다음과 같은 코드를 발견할 수 있었습니다.

// SpringBootWebSecurityConfiguration.java 의 일부
@Bean
@Scope("prototype") // <-- 핵심 부분
HttpSecurity httpSecurity() throws Exception {
    // ...
    HttpSecurity http = new HttpSecurity(...);
    http
        .csrf(withDefaults())
        .headers(withDefaults())
        // ... 기타 기본 설정 적용
    return http;
}

여기서 가장 중요한 부분은 @Scope("prototype") 어노테이션입니다. 이 어노테이션은 HttpSecurity 빈이 애플리케이션 전체에서 단 하나만 존재하는 싱글톤(Singleton)이 아님을 의미합니다.

즉, SecurityFilterChain 빈이 생성될 때마다 매번 새로운 HttpSecurity 인스턴스가 생성되어 주입됩니다. 이 덕분에 여러 개의 SecurityFilterChain을 정의하더라도 설정이 서로에게 영향을 주지 않고 독립성을 유지할 수 있습니다.


설정 메서드는 어떻게 동작하는지

다음으로 .csrf()와 같은 설정 메서드가 내부적으로 어떻게 동작하는지 확인했습니다. HttpSecurity 클래스의 소스 코드를 살펴보니 다음과 같은 패턴을 발견했습니다.

// HttpSecurity.java 의 일부
public HttpSecurity csrf(Customizer<CsrfConfigurer<HttpSecurity>> csrfCustomizer) throws Exception {
    ApplicationContext context = getContext();
    csrfCustomizer.customize(getOrApply(new CsrfConfigurer<>(context)));
    return HttpSecurity.this;
}

private <C extends SecurityConfigurer<O, B>> C getOrApply(C configurer) throws Exception {
    C existingConfig = (C) getConfigurer(configurer.getClass());
    if (existingConfig != null) {
        return existingConfig;
    }
    return apply(configurer);
}

핵심은 getOrApply라는 private 메서드에 있었습니다. 이 메서드의 로직은 다음과 같습니다.

  1. 현재 HttpSecurity 인스턴스에 인자로 받은 Configurer와 동일한 타입의 Configurer가 이미 등록되어 있는지 확인합니다 (getConfigurer).
  2. 만약 이미 존재한다면, 기존 Configurer 객체를 반환합니다.
  3. 존재하지 않는다면, 새로운 ConfigurerHttpSecurityapply하고 반환합니다.

이를 통해 .csrf()와 같은 메서드를 호출하는 것은 단순히 값을 설정하는 것이 아니라, CsrfConfigurer라는 설정 객체를 HttpSecurity에 등록하는 과정임을 알 수 있었습니다. 다른 모든 설정 메서드 역시 유사한 패턴으로 동작합니다.


http.build()의 역할

모든 체이닝 메서드를 통해 설정이 완료된 후, 마지막으로 http.build()가 호출됩니다. 이 메서드가 호출되는 시점에 HttpSecurity는 자신이 관리하고 있던 모든 Configurer들을 사용하여 각각의 보안 필터와 관련 객체들을 생성합니다.

그리고 이 필터들을 정해진 순서에 따라 엮어 최종 결과물인 DefaultSecurityFilterChain 객체를 생성하여 반환합니다. 이렇게 생성된 SecurityFilterChain은 불변 객체로, 애플리케이션의 보안 처리에 실제로 사용됩니다.

결론

이번 학습을 통해 Spring Security의 설정 과정 매우 체계적인 빌더 패턴에 기반하고 있음을 이해할 수 있었습니다.

  • HttpSecuritySecurityFilterChain을 만들기 위한 프로토타입 스코프의 빌더이다.
  • .csrf(), .headers() 등의 설정 메서드는 각각의 기능에 맞는 SecurityConfigurerHttpSecurity에 등록하는 역할을 한다.
  • 마지막에 호출되는 build() 메서드는 등록된 Configurer들을 종합하여 최종 결과물인 SecurityFilterChain을 생성한다.

다음 포스팅에서는 이렇게 생성된 필터들이 어떤 순서로 동작하는지 필터 체인의 동작 방식에 대해 더 깊이 분석하도록 하겠습니다.

profile
혼자 성장하는 것보다 함께 성장하는 것을 선호합니다.

0개의 댓글