스프링부트 해부학 : Security(1) - @EnableWebSecurity

정윤성·2022년 6월 14일
5

스프링부트 해부학

목록 보기
14/20
post-custom-banner

역할

스프링 AutoConfiguration이며 우리가 쉽게 MVC Security세팅을 할 수 있게 도와준다

@EnableWebSecurity

Selector

클래스 이름에서 부터 알 수 있듯 특정 클래스들을 import하는 Selector이다

SpringWebMvcImportSelector.class

@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
	...
	return new String[] {
    	"org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration" };
	}    
}

OAuth2ImportSelector.class

@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
	if (oauth2ClientPresent) {
		imports.add("org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration");
	}
	if (webfluxPresent && oauth2ClientPresent) {
		imports.add("org.springframework.security.config.annotation.web.configuration.SecurityReactorContextConfiguration");
	}
	if (webfluxPresent && oauth2ResourceServerPresent) {
		imports.add("org.springframework.security.config.annotation.web.configuration.SecurityReactorContextConfiguration");
	}
}

현재는 Deprecated가된 WebMvcSecurityConfiguration으로부터 정보를 읽어들어와 적용시키거나 OAuth2와 관련된 ClientRegistration을 가져오는걸 볼 수 있다

HttpSecurityConfiguration

@Bean
...(HttpSecurity http) {
	...
}

위와같은 코드를 정말 많이 봤을 것 이다
이 때 HttpSecurity Bean을 주입해주는 Class가 HttpSecurityConfiguration이다

private static final String HTTPSECURITY_BEAN_NAME = BEAN_NAME_PREFIX + "httpSecurity";

실제 해당 클래스에 등록된 Bean의 이름이다

@Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype")
HttpSecurity httpSecurity() throws Exception {
	...
    HttpSecurity http = ...
    http
    	.csrf(withDefaults())
        .addFilter(new WebAsyncManagerIntegrationFilter())
        .exceptionHandling(withDefaults())
    ...
    return http;
}

httpSecurity라는 Bean을 생성하는데 이때 주의깊게 봐야하는건 Scope이
HttpSecurity는 prototype으로 빈을 주입받을 때 마다 새로 생성한다는걸 알 수 있다 이외에 초기값은 전부 withDefault로 지정된다

WebSecurityConfiguration

스프링 시큐리티 자동설정중 가장 중요한 부분으로 우리가 등록한 SecurityFilterChain들과 WebSecurityCustomizer를 WebSecurity객체로 만들어준다

public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";

@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
    boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
    boolean hasFilterChain = !this.securityFilterChains.isEmpty();
    if (!hasConfigurers && !hasFilterChain) {
    	...
        this.webSecurity.apply(adapter);
    }
    ...
    for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
    	for (Filter filter : securityFilterChain.getFilters()) {
        	if (filter instanceof FilterSecurityInterceptor) {
            	this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
                break;
			}
        }
    }
	for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
    	customizer.customize(this.webSecurity);
	}
	return this.webSecurity.build();
}

Adapater도 등록을하고 우리가 등록한 SecurityFilterChain Bean들을 받아와 FilterInterceptor, WebSecurityCustomizer.customize와 같은 동작들을 수행한 뒤 WebSecurity객체를 생성한다

실제 우리가 등록한 requestMatcher와 기본Filter에 대해서 확인할 수 있다 ( SecurityFilter는 직접만든 필터이다 ) 정상적으로 ignore WebSecurityCustomizer가 등록된것도 확인할 수 있다
@Autowired(required = false)
void setFilterChains(List<SecurityFilterChain> securityFilterChains) {
	this.securityFilterChains = securityFilterChains;
}

@Autowired(required = false)
void setWebSecurityCustomizers(List<WebSecurityCustomizer> webSecurityCustomizers) {
	this.webSecurityCustomizers = webSecurityCustomizers;
}

이렇게 직접만든 Bean들을 주입받게되는데 이는 스프링 IoC컨테이너에 의해 해당 Component가 초기화 되기전 필드주입을 통해 먼저 받게된다

WebSecurity

Security의 가장 핵심적인 클래스로 springSecurityFilterChain이라는 네임벨류 Bean이다
얘의 가장큰 역할은 FilterChainProxy를 만들어 DelegatingFilterProxy의 실제 처리를 담당한다

@Override
protected Filter performBuild() throws Exception {
	...
    List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
    ...
	for (RequestMatcher ignoredRequest : this.ignoredRequests) {
    	...
        SecurityFilterChain securityFilterChain = new DefaultSecurityFilterChain(ignoredRequest);
        securityFilterChains.add(securityFilterChain);
        ...
    }
    for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
    	securityFilterChains.add(securityFilterChain);
        ...
    }
    ...
    FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
    ...
    filterChainProxy.afterPropertiesSet();
    Filter result = filterChainProxy;
    ...
    return result;
}

우리가 등록한 ignoreRequest, requestMatcher를 SecurityFilterChain으로 만든뒤 이를 하나로 엮어 FilterChainProxy로 만들어주는 걸 확인할 수 있다

Bean으로 등록된 SecurityFilterChain목록들이다

결국 해당 RootFilter(FilterChainProxy)는 DelegatingFilterProxy에 의해 최종적으로 실행된다

DelegatingFilterProxy.class

protected void invokeDelegate(
	Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
    throws ServletException, IOException {
 
 	delegate.doFilter(request, response, filterChain);
}

FilterChainProxy.class

private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
	throws IOException, ServletException {
 	...
    chain.doFilter(firewallRequest, firewallResponse);
    ...
}

위처럼 Filter가 Chain을타고 순차적으로 실행된다

최종적으로 다음과 같은 그림이 된다

출처 : https://uchupura.tistory.com/24

정리

  1. Security의 AutoConfiguration전략에 대해 알 수 있었다
  2. DelegatingFilterProxy -> FilterChainProxy -> SecurityFilterChain순으로 Filter가 진행되는 걸 알 수 있었다
profile
게으른 개발자
post-custom-banner

0개의 댓글