欢迎访问悦橙教程(wld5.com),关注java教程。悦橙教程  java问答|  每日更新
页面导航 : > > 文章正文

详解HttpSecurity是如何组装过滤器链的,

来源: javaer 分享于  点击 32872 次 点评:36

详解HttpSecurity是如何组装过滤器链的,


目录
  • 一 SecurityFilterChain
  • 二 SecurityConfigurer
  • 三 SecurityBuilder
    • 核心特点
  • 四 HttpSecurity

    一 SecurityFilterChain

    首先大伙都知道,Spring Security 里边的一堆功能都是通过 Filter 来实现的,无论是认证、RememberMe Login、会话管理、CSRF 处理等等,各种功能都是通过 Filter 来实现的。

    所以,我们配置 Spring Security,说白了其实就是配置这些 Filter。

    以前旧版继承自 WebSecurityConfigurerAdapter 类,然后重写 configure 方法,利用 HttpSecurity 去配置过滤器链,这种写法其实不太好理解,特别对于新手来说,可能半天整不明白到底配置了啥。

    现在新版写法我觉得更加合理,因为直接就是让开发者自己去配置过滤器链,类似下面这样:

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http.build();
    }
    

    这样开发者更容易理解,自己是在配置配置 SecurityFilter 过滤器链,因为就是要配置这样一个 Bean。

    SecurityFilterChain 是一个接口,这个接口只有一个实现类 DefaultSecurityFilterChain。

    DefaultSecurityFilterChain 中有一个 requestMatcher,通过 requestMatcher 可以识别出来哪些请求需要拦截,拦截下来之后,经由该类的另外一个属性 filters 进行处理,这个 filters 中保存了我们配置的所有 Filter。

    public final class DefaultSecurityFilterChain implements SecurityFilterChain {
    
    	private final RequestMatcher requestMatcher;
    
    	private final List<Filter> filters;
    
    }
    

    因此,在配置 SecurityFilterChain 这个 Bean 的时候,我们甚至可以按照如下方式来写:

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return new DefaultSecurityFilterChain(new AntPathRequestMatcher("/**"));
    }
    

    这个配置就表示拦截所有请求,但是拦截下来之后,这些请求所经过的过滤器为空,即拦截所有请求但是不做任何处理。

    我们也可以为 DefaultSecurityFilterChain 对象去配置过滤器链,大家看到,它的构造器可以传入 Filter:

    但是,Spring Security 过滤器数量有 30+,我们平日开发使用的也在 15 个左右,这样一个一个去配置太麻烦了,每个过滤器并非 new 出来就能用了,还要配置各种属性,所以我们一般不会自己一个一个去配置这些过滤器。

    二 SecurityConfigurer

    为了简化 Spring Security 中各个组件的配置,官方推出了 SecurityConfigurer,SecurityConfigurer 在 Spring Security 中扮演着非常重要的角色,其主要作用可以归纳为以下几点:

    • 配置过滤器:在 Spring Security 中,过滤器链中的每一个过滤器都是通过 xxxConfigurer 来进行配置的,而这些 xxxConfigurer 实际上都是 SecurityConfigurer 的实现。这意味着 SecurityConfigurer 负责配置和管理安全过滤器链中的各个组件。
    • 初始化和配置安全构建器:SecurityConfigurer 接口中定义了两个主要方法:init 和 configure。init 方法用于初始化安全构建器(SecurityBuilder),而 configure 方法则用于配置这个构建器。这两个方法共同确保了安全组件的正确设置和初始化。
    • 提供扩展点:SecurityConfigurer 的三个主要实现类(SecurityConfigurerAdapter、GlobalAuthenticationConfigurerAdapter、WebSecurityConfigurer)为开发者提供了扩展 Spring Security 功能的点。特别是,大部分的 xxxConfigurer 都是 SecurityConfigurerAdapter 的子类,这使得开发者能够轻松地定制和扩展安全配置。
    • 构建安全上下文:通过配置和组合不同的 SecurityConfigurer 实现,可以构建一个完整的安全上下文,包括身份验证、授权、加密等各个方面,从而确保应用程序的安全性。

    换句话说,Spring Security 中的各种 Filter,其实都有各自对应的 xxxConfigurer,通过这些 xxxConfigurer 完成了对 Filter 的配置。

    举几个简答的例子,如:

    • CorsConfigurer 负责配置 CorsFilter
    • RememberMeConfigurer 负责配置 RememberMeAuthenticationFilter
    • FormLoginConfigurer 负责配置 UsernamePasswordAuthenticationFilter

    以上是前置知识。

    三 SecurityBuilder

    SecurityBuilder 是 Spring Security 框架中的一个核心接口,它体现了建造者设计模式,用于构建和配置安全组件。这个接口并不直接与安全功能如认证或授权的具体实现绑定,而是提供了一种灵活的方式来组织和装配这些功能组件,特别是在构建安全上下文和过滤器链过程中。

    核心特点

    • 灵活性与模块化:通过 SecurityBuilder,开发者可以以模块化的方式添加、移除或替换安全配置中的各个部分,而不需要了解整个安全架构的细节。这促进了代码的复用性和可维护性。
    • 层次结构:在 Spring Security 中,SecurityBuilder及其子类形成了一个层次结构,允许配置像嵌套娃娃一样层层深入,每一个层级都可以贡献自己的配置逻辑,最终形成一个完整的安全配置。
    • 安全上下文构建:在认证过程中,SecurityBuilder 用于构造 SecurityContext,该上下文中包含了当前认证主体(通常是用户)的详细信息,以及主体所关联的权限和角色。
    • 过滤器链构建:在 Web 应用中,SecurityBuilder 还用于构建 FilterChainProxy,这是一个关键组件,负责组织和执行一系列的过滤器,这些过滤器负责处理 HTTP 请求的安全性,比如检查用户是否已经登录、是否有权限访问某个资源等。

    简而言之,在 SecurityBuilder 的子类 AbstractConfiguredSecurityBuilder 中,有一个名为 configurers 的集合,这个集合中保存的就是我们前面所说的各种 xxxConfigure 对象。

    AbstractConfiguredSecurityBuilder 收集到所有的 xxxConfigure 之后,将来会调用每个 xxxConfigure 的 configure 方法完成过滤器的构建。

    另外很重要的一点,HttpSecurity 也是一个 SecurityBuilder。因此,HttpSecurity 其实就是帮我们收集各种各样的 xxxConfigure,并存入到 configurers 集合中,以备将来构建过滤器使用。

    四 HttpSecurity

    根据前面的介绍,我们知道,无论我们怎么配置,最终拿到手的一定是一个 DefaultSecurityFilterChain 对象,因为这是 SecurityFilterChain 的唯一实现类。

    所以,HttpSecurity 其实就是在帮我们配置 DefaultSecurityFilterChain,我们看到 HttpSecurity 里边就有两个非常关键的属性:

    private List<OrderedFilter> filters = new ArrayList<>();
    private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;
    

    这两个恰恰就是构建 DefaultSecurityFilterChain 所需的关键参数。

    事实确实如此,我们看到,在 HttpSecurity#performBuild 方法中,就是利用这两个参数构建出来了 DefaultSecurityFilterChain:

    @Override
    protected DefaultSecurityFilterChain performBuild() {
    	ExpressionUrlAuthorizationConfigurer<?> expressionConfigurer = getConfigurer(
    			ExpressionUrlAuthorizationConfigurer.class);
    	AuthorizeHttpRequestsConfigurer<?> httpConfigurer = getConfigurer(AuthorizeHttpRequestsConfigurer.class);
    	boolean oneConfigurerPresent = expressionConfigurer == null ^ httpConfigurer == null;
    	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 进行排序,然后据此创建出来 DefaultSecurityFilterChain 对象。

    那么问题来了,filters 中的过滤器从何而来呢?

    前面我们提到,HttpSecurity 本质上也是一个 SecurityBuilder,我们平时在 HttpSecurity 配置的各种东西,本质上其实就是一个 xxxConfigure,这些 xxxConfigure 被 HttpSecurity 收集起来,最后会遍历收集起来的 xxxConfigure,调用其 configure 方法,最终获取过滤器,并将获取到的过滤器存入到 filters 集合中。

    这里松哥以我们最为常见的登录配置为例来和大家捋一捋这个流程。

    新版的登录配置我们一般按照如下方式来配置:

    http.formLogin(f -> f.permitAll());
    

    这个方法如下:

    public HttpSecurity formLogin(Customizer<FormLoginConfigurer<HttpSecurity>> formLoginCustomizer) throws Exception {
    	formLoginCustomizer.customize(getOrApply(new FormLoginConfigurer<>()));
    	return HttpSecurity.this;
    }
    

    从这段源码可以看到,我们配置里边写的 lambda,其实就是在配置 FormLoginConfigurer 对象。

    getOrApply 方法主要主要有两方面的作用:

    • 确保这个 Bean 不会重复配置。
    • 如果该 Bean 是第一次配置,那么将该对象添加到 SecurityBuilder 中(其实就是 HttpSecurity 对象自身),这个 SecurityBuilder 里边保存了所有配置好的 xxxConfigure 对象,将来在过滤器链构建的时候,会去遍历这些 xxxConfigure 对象并调用其 configure 方法,完成过滤器的构建。

    在 FormLoginConfigurer#init 方法中,完成了和登录相关过滤器的配置,如:

    • 登录请求处理过滤器(构造方法中完成的)
    • 注销过滤器的配置
    • 登录失败端点配置
    • 登录页面的配置

    这里涉及到的属性都会在对应的 xxxConfigurer 中完成配置。

    当过滤器链开始构建的时候,会调用到所有 xxxConfigurer 的 configure 方法,在这个方法中,最终完成相关过滤器的创建,并将之添加到 HttpSecurity 的 filters 属性这个集合中。

    FormLoginConfigurer 的 configure 方法在其父类中,如下:

    @Override
    public void configure(B http) throws Exception {
    	PortMapper portMapper = http.getSharedObject(PortMapper.class);
    	if (portMapper != null) {
    		this.authenticationEntryPoint.setPortMapper(portMapper);
    	}
    	RequestCache requestCache = http.getSharedObject(RequestCache.class);
    	if (requestCache != null) {
    		this.defaultSuccessHandler.setRequestCache(requestCache);
    	}
    	this.authFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
    	this.authFilter.setAuthenticationSuccessHandler(this.successHandler);
    	this.authFilter.setAuthenticationFailureHandler(this.failureHandler);
    	if (this.authenticationDetailsSource != null) {
    		this.authFilter.setAuthenticationDetailsSource(this.authenticationDetailsSource);
    	}
    	SessionAuthenticationStrategy sessionAuthenticationStrategy = http
    		.getSharedObject(SessionAuthenticationStrategy.class);
    	if (sessionAuthenticationStrategy != null) {
    		this.authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
    	}
    	RememberMeServices rememberMeServices = http.getSharedObject(RememberMeServices.class);
    	if (rememberMeServices != null) {
    		this.authFilter.setRememberMeServices(rememberMeServices);
    	}
    	SecurityContextConfigurer securityContextConfigurer = http.getConfigurer(SecurityContextConfigurer.class);
    	if (securityContextConfigurer != null && securityContextConfigurer.isRequireExplicitSave()) {
    		SecurityContextRepository securityContextRepository = securityContextConfigurer
    			.getSecurityContextRepository();
    		this.authFilter.setSecurityContextRepository(securityContextRepository);
    	}
    	this.authFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
    	F filter = postProcess(this.authFilter);
    	http.addFilter(filter);
    }
    

    关键在最后这一句 http.addFilter(filter);,这句代码将配置好的过滤器放到了 HttpSecurity 的 filters 集合中。

    其他的也都类似,例如配置 CorsFilter 的 CorsConfigurer#configure 方法,如下:

    @Override
    public void configure(H http) {
    	ApplicationContext context = http.getSharedObject(ApplicationContext.class);
    	CorsFilter corsFilter = getCorsFilter(context);
    	http.addFilter(corsFilter);
    }
    

    这些被添加到 HttpSecurity 的 filters 属性上的 Filter,最终就成为了创建 DefaultSecurityFilterChain 的原材料。

    类似的道理,我们也可以分析出 disable 方法的原理,例如我们要关闭 csrf,一般配置如下:

    .csrf(c -> c.disable());
    

    那么很明显,这段代码其实就是调用了 CsrfConfigurer 的 disable 方法:

    public B disable() {
    	getBuilder().removeConfigurer(getClass());
    	return getBuilder();
    }
    

    该方法直接从 SecurityBuilder 的 configurers 集合中移除了 CsrfConfigurer,所以导致最终调用各个 xxxConfigure 的 configure 方法的时候,没有 CsrfConfigurer#configure 了,就导致 csrf 过滤器没有配置上,进而 CSRF filter 失效。

    以上就是详解HttpSecurity是如何组装过滤器链的的详细内容,更多关于HttpSecurity组装过滤器链的资料请关注3672js教程其它相关文章!

    您可能感兴趣的文章:
    • SpringSecurity如何实现配置单个HttpSecurity
    • 详解spring security之httpSecurity使用示例
    • Spring Security基于HttpRequest配置权限示例详解
    • Spring-Security对HTTP相应头的安全支持方式
    相关栏目:

    用户点评