SpringSecurity 是如何使用Filter的

SpringSecurity 是如何优雅地使用大量Filter实现的

有了前文 SpringWeb Filter演变基础,
我们再来分析SpringSecurity的实现。

1、SpringSecurity 中用到了 DelegatingFilterProxy 吗?

在比较低版本的 Spring 开发中使用 SpringSecurity 肯定见到过 web.xml 中的这一段配置

  <filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

Servlet 3.0 虽然看不到上面这段配置,但是它是通过web容器启动时动态配置的,主要代码摘录:

package org.springframework.security.web.context;

public abstract class AbstractSecurityWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public final void onStartup(ServletContext servletContext) {
        beforeSpringSecurityFilterChain(servletContext);
        if (this.configurationClasses != null) {
        AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
            rootAppContext.register(this.configurationClasses);
            servletContext.addListener(new ContextLoaderListener(rootAppContext));
        }
        if (enableHttpSessionEventPublisher()) {
            servletContext.addListener("org.springframework.security.web.session.HttpSessionEventPublisher");
        }
        servletContext.setSessionTrackingModes(getSessionTrackingModes());
        insertSpringSecurityFilterChain(servletContext);
        afterSpringSecurityFilterChain(servletContext);
    }

    private void insertSpringSecurityFilterChain(ServletContext servletContext) {
        String filterName = DEFAULT_FILTER_NAME;
        DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(filterName);
        String contextAttribute = getWebApplicationContextAttribute();
        if (contextAttribute != null) {
            springSecurityFilterChain.setContextAttribute(contextAttribute);
        }
        registerFilter(servletContext, true, filterName, springSecurityFilterChain);
    }

    private void registerFilters(ServletContext servletContext, boolean insertBeforeOtherFilters, Filter... filters) {
        Assert.notEmpty(filters, "filters cannot be null or empty");
        for (Filter filter : filters) {
            Assert.notNull(filter, () -> "filters cannot contain null values. Got " + Arrays.asList(filters));
            String filterName = Conventions.getVariableName(filter);
            registerFilter(servletContext, insertBeforeOtherFilters, filterName, filter);
        }
    }
}

onStartup 在容器启动时被调用,然后调用 insertSpringSecurityFilterChain 方法,在调用 registerFilters 方法实现了与xml配置相同的效果。

2、DelegatingFilterProxy 代理的哪个Bean?

DelegatingFilterProxy 代码比较简单清晰,它所代理的类要么就是 initParam 中指定的 targetBeanName(如果未指定则 targetBeanName = filterName)

@Override
protected void initFilterBean() throws ServletException {
    synchronized (this.delegateMonitor) {
        if (this.delegate == null) {
            // If no target bean name specified, use filter name.
            if (this.targetBeanName == null) {
                this.targetBeanName = getFilterName();
            }
            // Fetch Spring root application context and initialize the delegate early,
            // if possible. If the root application context will be started after this
            // filter proxy, we'll have to resort to lazy initialization.
            WebApplicationContext wac = findWebApplicationContext();
            if (wac != null) {
                this.delegate = initDelegate(wac);
            }
        }
    }
}

从这里可以看出代理的就是 springSecurityFilterChain。

3、springSecurityFilterChain 是一个 FilterChainProxy 吗?

结合 SpringWeb Filter演变 这篇文章,我们分析最可能得结果就是 FilterChainProxy。
接下来我们一步一步来验证猜想:

1、springSecurityFilterChain 这个 Bean 在哪里注册的?

首先想到的办法就是搜索整个工程,有没有名称为 SpringSecurityFilterChain SecurityFilterChain 的文件或者类,很失望。
开始 Debug 查看注册在Spring容器中的名称为 springSecurityFilterChain 的 Bean 结果:
在这里插入图片描述

通过Debug 找到了注册 springSecurityFilterChain 是在 org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration
的 springSecurityFilterChain() 方法。

@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
	boolean hasFilterChain = !this.securityFilterChains.isEmpty();
	if (!hasFilterChain) {
		this.webSecurity.addSecurityFilterChainBuilder(() -> {
			this.httpSecurity.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated());
			this.httpSecurity.formLogin(Customizer.withDefaults());
			this.httpSecurity.httpBasic(Customizer.withDefaults());
			return this.httpSecurity.build();
		});
	}
	for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
		this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
	}
	for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
		customizer.customize(this.webSecurity);
	}
	return this.webSecurity.build();
}

继续跟源码可以发现 FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);

@Override
protected Filter performBuild() throws Exception {
	Assert.state(!this.securityFilterChainBuilders.isEmpty(),
			() -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
					+ "Typically this is done by exposing a SecurityFilterChain bean. "
					+ "More advanced users can invoke " + WebSecurity.class.getSimpleName()
					+ ".addSecurityFilterChainBuilder directly");
	int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
	List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
	List<RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>>> requestMatcherPrivilegeEvaluatorsEntries = new ArrayList<>();
	for (RequestMatcher ignoredRequest : this.ignoredRequests) {
		WebSecurity.this.logger.warn("You are asking Spring Security to ignore " + ignoredRequest
				+ ". This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.");
		SecurityFilterChain securityFilterChain = new DefaultSecurityFilterChain(ignoredRequest);
		securityFilterChains.add(securityFilterChain);
		requestMatcherPrivilegeEvaluatorsEntries
				.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
	}
	for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
		SecurityFilterChain securityFilterChain = securityFilterChainBuilder.build();
		securityFilterChains.add(securityFilterChain);
		requestMatcherPrivilegeEvaluatorsEntries
				.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
	}
	if (this.privilegeEvaluator == null) {
		this.privilegeEvaluator = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
				requestMatcherPrivilegeEvaluatorsEntries);
	}
	FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
	if (this.httpFirewall != null) {
		filterChainProxy.setFirewall(this.httpFirewall);
	}
	if (this.requestRejectedHandler != null) {
		filterChainProxy.setRequestRejectedHandler(this.requestRejectedHandler);
	}
	else if (!this.observationRegistry.isNoop()) {
		CompositeRequestRejectedHandler requestRejectedHandler = new CompositeRequestRejectedHandler(
				new ObservationMarkingRequestRejectedHandler(this.observationRegistry),
				new HttpStatusRequestRejectedHandler());
		filterChainProxy.setRequestRejectedHandler(requestRejectedHandler);
	}
	filterChainProxy.setFilterChainDecorator(getFilterChainDecorator());
	filterChainProxy.afterPropertiesSet();

	Filter result = filterChainProxy;
	if (this.debugEnabled) {
		this.logger.warn("\n\n" + "********************************************************************\n"
				+ "**********        Security debugging is enabled.       *************\n"
				+ "**********    This may include sensitive information.  *************\n"
				+ "**********      Do not use in a production system!     *************\n"
				+ "********************************************************************\n\n");
		result = new DebugFilter(filterChainProxy);
	}

	this.postBuildAction.run();
	return result;
}

最终验证我们猜想。按照 SpringWeb Filter演变 的思路 FilterChainProxy 应该代理了一系列 FilterChain
仔细阅读 springSecurityFilterChain() 会发现 new FilterChainProxy(securityFilterChains) 需要依赖 securityFilterChains,securityFilterChains 又是注入的。

@Autowired(required = false)
void setFilterChains(List<SecurityFilterChain> securityFilterChains) {
	this.securityFilterChains = securityFilterChains;
}

到这里整个过滤器逻辑结构基本清晰。 那么都注入了哪些 SecurityFilterChain,分别在什么地方产生的 ?
通过 Debug 能发现,这里注入的Bean 都是 DefaultSecurityFilterChain 类型,再搜索 DefaultSecurityFilterChain
发现其实来自我们自己的代码(一处是对 SpringSecurity 的配置,一处是 Oauth2 的配置)
com.example.resource.config.DefaultSecurityConfig

@Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    http
    .authorizeHttpRequests(authorize ->
        authorize
        .requestMatchers("/assets/**", "/webjars/**", "/login","/h2-console/**").permitAll()
        .anyRequest().authenticated()
    )
    .formLogin(formLogin ->
        formLogin.loginPage("/login")
    )
    .oauth2Login(oauth2Login ->
        oauth2Login.loginPage("/login")
        .successHandler(authenticationSuccessHandler())
    );
	return http.build();
}

com.example.resource.config.AuthorizationServerConfig

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(
		HttpSecurity http, RegisteredClientRepository registeredClientRepository,
		AuthorizationServerSettings authorizationServerSettings) throws Exception {

	OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
	DeviceClientAuthenticationConverter deviceClientAuthenticationConverter =
			new DeviceClientAuthenticationConverter(
					authorizationServerSettings.getDeviceAuthorizationEndpoint());
	DeviceClientAuthenticationProvider deviceClientAuthenticationProvider =
			new DeviceClientAuthenticationProvider(registeredClientRepository);

	// @formatter:off
	http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
		.deviceAuthorizationEndpoint(deviceAuthorizationEndpoint ->
			deviceAuthorizationEndpoint.verificationUri("/activate")
		)
		.deviceVerificationEndpoint(deviceVerificationEndpoint ->
			deviceVerificationEndpoint.consentPage(CUSTOM_CONSENT_PAGE_URI)
		)
		.clientAuthentication(clientAuthentication ->
			clientAuthentication
				.authenticationConverter(deviceClientAuthenticationConverter)
				.authenticationProvider(deviceClientAuthenticationProvider)
		)
		.authorizationEndpoint(authorizationEndpoint ->
			authorizationEndpoint.consentPage(CUSTOM_CONSENT_PAGE_URI))
		.oidc(Customizer.withDefaults());	// Enable OpenID Connect 1.0
	// @formatter:on

	// @formatter:off
	http
		.exceptionHandling((exceptions) -> exceptions
			.defaultAuthenticationEntryPointFor(
				new LoginUrlAuthenticationEntryPoint("/login"),
				new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
			)
		)
		.oauth2ResourceServer(oauth2ResourceServer ->
			oauth2ResourceServer.jwt(Customizer.withDefaults()));
	// @formatter:on
	return http.build();
}
2、这些 SecurityFilterChain 分别又包含了多少个 Filter ?

通过上述源码阅读以及结合上一篇文章分析,SecurityFilterChain 都是通过 HttpSecurity.build() 出来的实例,接下来分析 build 操作。
通过跟踪调用链,会发现以下代码:

@Override
protected DefaultSecurityFilterChain performBuild() {
	ExpressionUrlAuthorizationConfigurer<?> expressionConfigurer = getConfigurer(
			ExpressionUrlAuthorizationConfigurer.class);
	AuthorizeHttpRequestsConfigurer<?> httpConfigurer = getConfigurer(AuthorizeHttpRequestsConfigurer.class);
	boolean oneConfigurerPresent = expressionConfigurer == null ^ httpConfigurer == null;
	Assert.state((expressionConfigurer == null && httpConfigurer == null) || oneConfigurerPresent,
			"authorizeHttpRequests cannot be used in conjunction with authorizeRequests. Please select just one.");
	this.filters.sort(OrderComparator.INSTANCE);
	List<Filter> sortedFilters = new ArrayList<>(this.filters.size());
	for (Filter filter : this.filters) {
		sortedFilters.add(((OrderedFilter) filter).filter);
	}
	return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters);
}

发现创建实例时指定了 filters 但是这里的 filters 既没有实例化的操作,又没有注入的逻辑。但是有几个 addFilterXXX 方法,因此再次断点调试发现最终是通过
addFilter(Filter filter) 这个方法添加,现在只需关注那些地方调用了 addFilter(Filter filter) 方法即可,全局搜索:
在这里插入图片描述

发现 addFilter() 都是在configure 过程中添加,也正好对应 HttpSecurity 中的 cros()、securityContext()、exceptionHandling()…一系列配置。
HttpSecurity 实例创建操作部分代码摘录

@Configuration(proxyBeanMethods = false)
class HttpSecurityConfiguration { 
    @Bean(HTTPSECURITY_BEAN_NAME)
    @Scope("prototype")
    HttpSecurity httpSecurity() throws Exception {
        LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(this.context);
        AuthenticationManagerBuilder authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(
                this.objectPostProcessor, passwordEncoder);
        authenticationBuilder.parentAuthenticationManager(authenticationManager());
        authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
        HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
        WebAsyncManagerIntegrationFilter webAsyncManagerIntegrationFilter = new WebAsyncManagerIntegrationFilter();
        webAsyncManagerIntegrationFilter.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
        // @formatter:off
        http
            .csrf(withDefaults())
            .addFilter(webAsyncManagerIntegrationFilter)
            .exceptionHandling(withDefaults())
            .headers(withDefaults())
            .sessionManagement(withDefaults())
            .securityContext(withDefaults())
            .requestCache(withDefaults())
            .anonymous(withDefaults())
            .servletApi(withDefaults())
            .apply(new DefaultLoginPageConfigurer<>());
        http.logout(withDefaults());
        // @formatter:on
        applyDefaultConfigurers(http);
        return http;
    }
}

可以看到在这里 Configure 了相当一部分。同时在 com.example.resource.config.AuthorizationServerConfig 中
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class) 添加的 OAuth2AuthorizationServerConfigurer 也Configure了一部分Filter。
经过一系列的Filter 添加来完成了 SpringSecurity 的功能。

4、总结

1、首先产生一个 DelegatingFilterProxy 来代理 springSecurityFilterChain(FilterChainProxy)
2、springSecurityFilterChain 注入一系列 FilterChain,针对 SpringSecurity 主要是 DefaultSecurityFilterChain
3、给 FilterChain configure 一系列 Filter
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值