[컴] Spring 의 securityWebFilterChain 에서 filter 가 추가되는 과정

스프링 / springboot / 스프링부트 / security

Spring 의 securityWebFilterChain 에서 filter 가 추가되는 과정

  • WebFilterChainProxy.filter()에서 WebFluxSecurityConfiguration.securityWebFilterChains 을 돌면서 실행한다.
  • WebFilterChainProxy@Bean WebFluxSecurityConfiguration.springSecurityWebFilterChainFilter 에 의해서 new 된다.
  • securityWebFilterChains@Autowire setSecurityWebFilterChains(List<SecurityWebFilterChain> securityWebFilterChains) 에 의해 new 된다.
  • WebFluxSecurityConfiguration@EnableWebFluxSecurity 에 의해 불려진다.
  • @EnableWebFluxSecuritySecurityConfig 에 설정돼 있다.
  • @Bean SecurityConfig.securityWebFilterChain 에서 filter를 정하고, ServerHttpSecurity.build() 를 호출하는데, 이 때 filter들이 실제로 추가된다.
    • formLogin 인 경우 login url 인 ‘/login’ 도 설정된다.

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig
    @Bean
    SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        http
            ...
            .authorizeExchange(exchange -> exchange
                    ...
                    .and()
                    .formLogin(formLogin -> formLogin
                            .authenticationSuccessHandler(authenticationSuccessHandler)
                            .authenticationFailureHandler(authenticationFailureHandler)
                    )
            );
        return http.build();    // ServerHttpSecurity.build
    }

public class ServerHttpSecurity
    private FormLoginSpec formLogin;
    public SecurityWebFilterChain build() {
        if (this.formLogin != null) {
            if (this.formLogin.authenticationManager == null) {
                this.formLogin.authenticationManager(this.authenticationManager);
            }
            if (this.formLogin.securityContextRepository != null) {
                this.formLogin.securityContextRepository(this.formLogin.securityContextRepository);
            }
            else if (this.securityContextRepository != null) {
                this.formLogin.securityContextRepository(this.securityContextRepository);
            }
            else {
                this.formLogin.securityContextRepository(new WebSessionServerSecurityContextRepository());
            }
            // add filter to the this.webFilters
            this.formLogin.configure(this);
        }
        ...
        AnnotationAwareOrderComparator.sort(this.webFilters);
        List<WebFilter> sortedWebFilters = new ArrayList<>();
        this.webFilters.forEach((f) -> {
            if (f instanceof OrderedWebFilter) {
                f = ((OrderedWebFilter) f).webFilter;
            }
            sortedWebFilters.add(f);
        });
        sortedWebFilters.add(0, new ServerWebExchangeReactorContextWebFilter());

        return new MatcherSecurityWebFilterChain(getSecurityMatcher(), sortedWebFilters);
    }

    public final class FormLoginSpec {
        protected void configure(ServerHttpSecurity http) {
            if (this.authenticationEntryPoint == null) {
                this.isEntryPointExplicit = false;
                loginPage("/login");
            }
            ...
        }
    }


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ ServerHttpSecurityConfiguration.class, WebFluxSecurityConfiguration.class,
        ReactiveOAuth2ClientImportSelector.class })
@Configuration
public @interface EnableWebFluxSecurity {

}

@Configuration(proxyBeanMethods = false)
class WebFluxSecurityConfiguration {

    private List<SecurityWebFilterChain> securityWebFilterChains;

    ...

    @Autowired(required = false)
    void setSecurityWebFilterChains(List<SecurityWebFilterChain> securityWebFilterChains) {
        // SecurityConfig.securityWebFilterChain()
        this.securityWebFilterChains = securityWebFilterChains;
    }

    @Bean(SPRING_SECURITY_WEBFILTERCHAINFILTER_BEAN_NAME)
    @Order(WEB_FILTER_CHAIN_FILTER_ORDER)
    WebFilterChainProxy springSecurityWebFilterChainFilter() {
        return new WebFilterChainProxy(getSecurityWebFilterChains());
    }

    private List<SecurityWebFilterChain> getSecurityWebFilterChains() {
        List<SecurityWebFilterChain> result = this.securityWebFilterChains;
        if (ObjectUtils.isEmpty(result)) {
            return Arrays.asList(springSecurityFilterChain());
        }
        return result;
    }


public class WebFilterChainProxy implements WebFilter {
    private final List<SecurityWebFilterChain> filters;

    public WebFilterChainProxy(List<SecurityWebFilterChain> filters) {
        this.filters = filters; // WebFluxSecurityConfiguration.securityWebFilterChains
    }
    ...
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return Flux.fromIterable(this.filters)
                .filterWhen((securityWebFilterChain) -> securityWebFilterChain.matches(exchange)).next()
                .switchIfEmpty(chain.filter(exchange).then(Mono.empty()))
                .flatMap((securityWebFilterChain) -> securityWebFilterChain.getWebFilters().collectList())
                .map((filters) -> new FilteringWebHandler(chain::filter, filters)).map(DefaultWebFilterChain::new)
                .flatMap((securedChain) -> securedChain.filter(exchange));  // securedChain is `DefaultWebFilterChain`
    }
    ...
}
@AutoConfiguration(after = { WebFluxAutoConfiguration.class })
@ConditionalOnClass({ DispatcherHandler.class, HttpHandler.class })
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnMissingBean(HttpHandler.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class HttpHandlerAutoConfiguration {
    @Configuration(proxyBeanMethods = false)
    public static class AnnotationConfig {

        ...

        @Bean
        public HttpHandler httpHandler(ObjectProvider<WebFluxProperties> propsProvider) {
            HttpHandler httpHandler = WebHttpHandlerBuilder.applicationContext(this.applicationContext).build();
            ...
            return httpHandler;
        }

    }

}

public final class WebHttpHandlerBuilder{
    public static WebHttpHandlerBuilder applicationContext(ApplicationContext context) {
        ...
        // set this.filters
        List<WebFilter> webFilters = context
                .getBeanProvider(WebFilter.class)
                .orderedStream()
                .collect(Collectors.toList());
        builder.filters(filters -> filters.addAll(webFilters));
    }
    public HttpHandler build() {
        WebHandler decorated = new FilteringWebHandler(this.webHandler, this.filters);
        decorated = new ExceptionHandlingWebHandler(decorated,  this.exceptionHandlers);

        HttpWebHandlerAdapter adapted = new HttpWebHandlerAdapter(decorated);
        ...
        // return adapted
        return (this.httpHandlerDecorator != null ? this.httpHandlerDecorator.apply(adapted) : adapted);
    }
}

public class FilteringWebHandler extends WebHandlerDecorator 
    public FilteringWebHandler(WebHandler handler, List<WebFilter> filters) {
        super(handler);
        this.chain = new DefaultWebFilterChain(handler, filters);
    }

    @Override
    public Mono<Void> handle(ServerWebExchange exchange) {
        return this.chain.filter(exchange);
    }

public class DefaultWebFilterChain implements WebFilterChain
    public DefaultWebFilterChain(WebHandler handler, List<WebFilter> filters) {
        Assert.notNull(handler, "WebHandler is required");
        this.allFilters = Collections.unmodifiableList(filters);
        this.handler = handler;
        DefaultWebFilterChain chain = initChain(filters, handler);
        this.currentFilter = chain.currentFilter;
        this.chain = chain.chain;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange) {
        return Mono.defer(() ->
                this.currentFilter != null && this.chain != null ?
                        invokeFilter(this.currentFilter, this.chain, exchange) :
                        this.handler.handle(exchange));
    }
    private Mono<Void> invokeFilter(WebFilter current, DefaultWebFilterChain chain, ServerWebExchange exchange) {
        String currentName = current.getClass().getName();
        return current.filter(exchange, chain).checkpoint(currentName + " [DefaultWebFilterChain]");
    }

requst 를 처리시점에 호출되는 filter()

  1. HttpWebHandlerAdapter에서 getDelegate().handle(exchange)
  2. ExceptionHandlingWebHandler.handle() 에서 super.handle() 을 호출
  3. FilteringWebHandler.handle()을 호출하게 된다.
  4. FilteringWebHandler.handle()에서 DefaultWebFilterChain.filter(exchange) 를 호출
public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHandler 
    @Override
    public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
        ...
        ServerWebExchange exchange = createExchange(request, response);
        ...
        return getDelegate().handle(exchange)
                .doOnSuccess(aVoid -> logResponse(exchange))
                .onErrorResume(ex -> handleUnresolvedError(exchange, ex))
                .then(Mono.defer(response::setComplete));
    }
public class ExceptionHandlingWebHandler extends WebHandlerDecorator 
    ...
    @Override
    public Mono<Void> handle(ServerWebExchange exchange) {
        Mono<Void> completion;
        try {
            // WebHandlerDecorator.handle --> FilteringWebHandler.handle
            completion = super.handle(exchange);
        }
        catch (Throwable ex) {
            ...
        }

        for (WebExceptionHandler handler : this.exceptionHandlers) {
            completion = completion.onErrorResume(ex -> handler.handle(exchange, ex));
        }
        return completion;
    }

public class WebHandlerDecorator implements WebHandler

    private final WebHandler delegate;

    public WebHandlerDecorator(WebHandler delegate) {
        ...
        this.delegate = delegate;
    }
    ...

    @Override
    public Mono<Void> handle(ServerWebExchange exchange) {
        return this.delegate.handle(exchange);
    }

/login 호출

curl http://localhost:8888/login
  • DefaultWebFilterChain.filter
  • –> AuthenticationWebFilter.filter
  • --> AuthenticationWebFilter.authenticate()
  • --> authenticationManager.authenticate(token)
  • --> AbstractUserDetailsReactiveAuthenticationManager.authenticate

댓글 없음:

댓글 쓰기