본문 바로가기
Web/Spring-Security

[Security] 시큐리티 파헤치기 (3) : 시큐리티 초기화, 다중 필터 체인

by 희조당 2023. 8. 11.
728x90

🙋 들어가며

이번 글에서는 시큐리티가 어떻게 초기화되고 어떤 값들이 생성되는지 알아보겠습니다.

시큐리티는 여러 개의 필터 체인을 관리할 수 있는데 어떻게 설정하는지 알아보겠습니다.

그리고 요청이 어떻게 알맞은 필터 체인에 타고 흐르는지도 알아보겠습니다.


📁 설정 파일 작성 (feat. Security 6)

시큐리티를 사용하기 위해서는 우선 설정 파일을 작성해야 합니다.

다음과 같이 단 어노테이션 하나만 달아주면 기본적인 시큐리티 설정이 완료됩니다.

@Configuration
@EnableWebSecurity // 기본 시큐리티 설정
public class SecurityConfig {
}

 

바로 @EnableWebSecurity 입니다. 전혀 복잡하지 않죠? 😋

단순히 달아주기만 하면 애플리케이션이 메모리에 올라올 때 다양한 설정을 해줍니다.

그러면 어떻게 초기화되고 어떤 게 만들어질까요?

📈 초기화 과정

스프링 부트 환경에서 애플리케이션을 시작하면 가장 먼저 내장 서버(Tomcat)가 올라옵니다.

그 뒤로 내부적으로 필요한 Bean들을 생산하는데 그 중에 securityFilterChain이 존재합니다.

 

다음과 같이 BeanFactory에서 이름으로 빈을 등록하는 빈을 생성합니다.

그림 1 : 시큐리티 필터 체인 등록 요청

 

이후 ConstructorResolver에서 SecurityFilterAutoConfiguration을 생성합니다.

그림 2 : 자동 설정 빈 생성

 

이후 AutoConfiguration에서 DelegatingFilterProxy를 생성해줍니다.

여기서 우리가 설정한 내용에 따라 추가적으로 빈이 생성됩니다. (@ConditionalOnClass)

그림 3 : 같이 추가되는 특정 빈

 

AbstractSecurityWebApplicationInitializer는 DelegatingFilterProxy를 등록합니다.

고로 AutoConfiguration에서 빈을 생성하고, Initializer에서 DelegatingFilterProxy가 등록시켜 다른 필터보다

먼저 springSecurityFilterChain을 사용할 수 있게 합니다.

 

마지막으로 WebSecurityConfiguration 에서 우리가 설정한 필터를 등록됩니다.

여기서 이렇게 반복문을 돌면서 등록하기 때문에 다중 필터 체인을 사용할 수 있습니다.

그림 4 : Configration을 기준으로 등록되는 필터 체인들

🛡️ 다양한 보안기능

@EnableWebSecurity로 간단하지만 다양한 것들을 지원합니다.

지원하는 것들은 다양한데 대표적인 몇 가지만 적어보겠습니다.

  • URL에 대한 인증 처리
  • 기본적인 로그인/로그아웃 지원
  • 다양한 웹 공격 방어 : CRSF 공격, 세션 고정 공격 등

 

사실 이 어노테이션은 서블릿 필터를 만드는 역할입니다.

바로 이전 글에서 봤던 springSecurityFilterChain입니다.

여기서 시큐리티가 제공하는 모든 보안은 필터를 통해서 이루어진다는 것을 알 수 있습니다.

 

따로 설정하지 않으면 어떤 것들이 생성될까요? FilterChainProxy에 디버그를 걸고 동작시켜 보겠습니다.

다음과 같이 15개의 필터들이 기본적으로 생성됩니다. 필요에 따라 필터를 더 추가할 수도, 없앨 수도 있습니다.

그림 5 : 생성된 필터들

사실 @EnableWebSecurity이 없더라도 Security Dependency가 추가되어 있다면 자동으로 생성합니다.

주석 처리를 하고 애플리케이션을 동작시켜 보겠습니다.

그림 6 : 주석 처리된 @EnableWebSecurity

 

그래도 다음과 같이 정상적으로 애플리케이션에 올라오는 것을 알 수 있습니다.

그림 7 : 정상적으로 올라오는 SecurityFilterChain


⛓️ 다중 필터 체인

이렇게 보안을 관리하는 필터 체인을 여러 개 등록해서 사용할 수 있습니다.

우선 예시를 위해서 다음과 같이 필터 체인을 등록해 보겠습니다.

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // Web attack
    http.csrf().disable()
            .headers().frameOptions().disable();

        // Session management
        htt.sessionManagement()
            .maximumSessions(1)
            .maxSessionsPreventsLogin(true);

        // Login
    http.formLogin()
            .permitAll();

        // Logout
    http.logout()
            .deleteCookies("remember-me", "JSESSIONID")
            .permitAll();

        // Remember Me
    http.rememberMe()
            .key("deep-dive-security")
            .rememberMeParameter("remember-me")
            .tokenValiditySeconds(TOKEN_VALID_TIME);

        // Authentication 
    http.authorizeHttpRequests()
            .requestMatchers("/user").hasRole("USER")
            .requestMatchers("/sys").hasRole("SYS")
            .requestMatchers("/admin").hasRole("ADMIN");

    // Authorization
    http.authorizeHttpRequests()
            .requestMatchers("/join", "/exception", "/denied")
            .permitAll();

    return http.build();
}

 

그리고 같은 설정 파일 안에서 다른 필터 체인을 설정해 보겠습니다.

@Bean
public SecurityFilterChain adminFilter(HttpSecurity http) throws Exception {
    http
            .securityMatcher("/admin/**")
            .authorizeHttpRequests(authorize -> authorize
                    .anyRequest().hasRole("ADMIN")
            );
    http.formLogin(withDefaults());
    return http.build();
}

 

그리고 아까와 같이 FilterChainProxy 디버그를 걸고 동작시켜 보겠습니다.

FilterChainProxy는 내부적으로 필터 체인에 대한 리스트를 가지고 있으므로 this를 확인하면 될 것 같습니다.

public class FilterChainProxy extends GenericFilterBean {
    // ...
    private List<SecurityFilterChain> filterChains;
    // ...
}

 

아무 곳에 디버그를 걸고 가지고 있는 값을 확인해 주면 다음과 같이 잘 등록된 것을 확인할 수 있습니다.

첫 번째는 2번째로 등록한 필터 체인으로 보입니다.

그림 8 : 등록된 2개의 필터 체인

🤔 어떻게 사용되죠?

앞서 언급했듯이 FilterChainProxy는 모든 필터 체인을 가지고 있습니다.

간단하게 반복문을 사용해서 요청이 들어왔을 때 일치하는 필터 체인을 찾습니다.

이후 요청을 해당 필터에 흘려보냅니다.

그림 9 : FilterChainProxy 내부 요청과 일치하는 필터 체인을 찾는 getFilters()

 

그림으로 표현하면 다음과 같습니다. 😋

그림 10 : FilterChainProxy 동작 구현도


😋 정리

  1. 시큐리티는 다양한 보안을 지원한다.
  2. @EnableWebSecurity로 등록할 수 있다.
  3. 사실 없어도 애플리케이션에 dependency가 존재하면 기본 설정이 된다.
  4. 초기화 과정은 복잡하다.
  5. 필터 체인을 여러 개 등록할 수 있다.
  6. FilterChainProxy에서 내부적으로 반복문으로 요청에 맞는 필터 체인을 찾는다.

-Reference:

https://docs.spring.io/spring-security/reference/servlet/architecture.html

 

😋 지극히 개인적인 블로그지만 댓글과 조언은 제 성장에 도움이 됩니다 😋

댓글