theme: smartblue
highlight: a11y-dark
一、前言及准备
1.1 SpringSecurity过滤器链简单介绍
在Spring Security中,过滤器链(Filter Chain)是由多个过滤器(Filter)组成的,这些过滤器按照一定的顺序对进入应用的请求进行处理。每个过滤器可以执行不同的安全操作,如身份验证、授权、安全上下文的建立等。
过滤器是一种典型的AOP思想,我们将通过源码分析这些过滤器如何被加载以及组成过滤器链。
1.2 SpringSecurity配置类
该类主要对SpringSecurity进行一系列配置,后续过滤器链的初始化和加载也是基于这个配置类。当前配置类仅供参考。注意里面两个很重要的方法 #configure(WebSecurity web)以及#configure(WebSecurity web),他们对过滤器链的初始化很重要。
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { // 自定义登录成功或失败处理器,退出登录处理器 @Autowired private MyAuthenticationService myAuthenticationService; // 自定义验证码过滤器 @Autowired private ValidateCodeFilter validateCodeFilter; // 自定义权限不足处理 @Autowired private MyAccessDeniedHandler myAccessDeniedHandler; // 权限相关Service @Autowired private PermissionService permissionService; @Override public void configure(WebSecurity web) throws Exception { //解决静态资源被拦截的问题 web.ignoring().antMatchers("/css/**", "/images/**", "/js/**", "/code/**"); } /** * http请求方法 * * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { // 加入用户名密码验证过滤器的前面 http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class); // 查询数据库所有权限列表 Listlist = permissionService.list(); for (Permission permission : list) { // 添加请求权限 http.authorizeRequests().antMatchers(permission.getPermissionUrl()) .hasAuthority(permission.getPermissionTag()); } // 设置权限不足的信息 http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler); http.formLogin()// 开启表单认证 .loginPage("/toLoginPage")// 自定义登录页面 .loginProcessingUrl("/login")//表单提交的路径 .usernameParameter("username") .passwordParameter("password")//自定义input的name值 .successForwardUrl("/")//登录成功之后跳转的路径 .successHandler(myAuthenticationService) .failureHandler(myAuthenticationService)//登录成功或者失败的处理 .and().logout().logoutUrl("/logout") .logoutSuccessHandler(myAuthenticationService) .and().rememberMe()//开启记住我功能 .tokenValiditySeconds(1209600)//token失效时间 默认是2周 .rememberMeParameter("remember-me")//自定义表单input值 .tokenRepository(getPersistentTokenRepository()) .and().authorizeRequests().antMatchers("/toLoginPage").permitAll()//放行登录页面 .anyRequest().authenticated(); //关闭csrf防护 http.csrf().disable(); //加载同源域名下iframe页面 http.headers().frameOptions().sameOrigin(); // 开启跨域支持 http.cors().configurationSource(corsConfigurationSource()); } }
1.3 过滤器链加载方法入口
Spring boot启动中会加载spring.factories文件, 在文件中有对应针对Spring Security的过滤器链
的配置信息,所以spring.factories来找过滤器链加载方法入口
所以我们的过滤器链加载方法入口位于org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration#springSecurityFilterChain()
二、 源码导读
SpringSecurity的过滤器链创建本质就是读取SecurityConfig配置类,并且通过核心配置方法:#configure(WebSecurity web)以及#configure(WebSecurity web)生成一个个配置对象,最终每个配置对象会转换成一个过滤器对象,这些过滤器对象组成过滤器链。
前面已经提到了,配置类中的 #configure(WebSecurity web)以及#configure(WebSecurity web)非常重要。他们将分别加载到一个HttpSecurity对象和一个名为ignoredRequests的ArrayList之中,
并且过滤器链也是由这两部分转换后组成。
2.1 SpringSecurity配置信息的加载
由前面的内容我们已经定位到过滤器链加载的方法入口位于:
org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration#springSecurityFilterChain()
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME) public Filter springSecurityFilterChain() throws Exception { boolean hasConfigurers = webSecurityConfigurers != null && !webSecurityConfigurers.isEmpty(); if (!hasConfigurers) { WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor .postProcess(new WebSecurityConfigurerAdapter() { }); webSecurity.apply(adapter); } // 加载方法 return webSecurity.build(); }
我们从该方法的webSecurity.build()按照下图一路点击,最终会进入到org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#doBuild()
方法,该方法是加载配置和过滤器链的主要方法。
org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#doBuild()如下
@Override protected final O doBuild() throws Exception { synchronized (configurers) { buildState = BuildState.INITIALIZING; beforeInit(); // 【1】.初始化,主要构建HttpSecurity对象以及内部的配置对象 init(); buildState = BuildState.CONFIGURING; beforeConfigure(); // 【2】ignoredRequests集合的构建 configure(); buildState = BuildState.BUILDING; // 【3】组装过滤器链 O result = performBuild(); buildState = BuildState.BUILT; return result; }
可以看到,当前的configurers加载的就是我们自定义的配置类
2.1.1 构建带有配置信息的HttpSecurity对象
我们从上述的doBuild()方法体中点击init()方法进入其内部
org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#init()
private void init() throws Exception { Collection> configurers = getConfigurers(); for (SecurityConfigurer configurer : configurers) { // 构建HttpSecurity对象 configurer.init((B) this); } for (SecurityConfigurer configurer : configurersAddedInInitializing) { configurer.init((B) this); } }
接着点击构建HttpSecurity的对象的方法configurer.init()方法
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#init(),发现真正构建HttpSecurity的方法其实是 getHttp()
public void init(final WebSecurity web) throws Exception { // 真正构建HttpSecurity的方法 final HttpSecurity http = getHttp(); // 将HttpSecurity对象作为SecurityFilterChainBuilder对象返回 web.addSecurityFilterChainBuilder(http).postBuildAction(() -> { FilterSecurityInterceptor securityInterceptor = http .getSharedObject(FilterSecurityInterceptor.class); web.securityInterceptor(securityInterceptor); }); }
点击进入 getHttp()
protected final HttpSecurity getHttp() throws Exception { /** 省略其他 **/ // 【1】.新建一个HttpSecurity对象 http = new HttpSecurity(objectPostProcessor, authenticationBuilder, sharedObjects); if (!disableDefaults) { // 【2】.设置HttpSecurity对象中configurers配置对象以及filter过滤器对象 http .csrf().and() .addFilter(new WebAsyncManagerIntegrationFilter()) .exceptionHandling().and() .headers().and() .sessionManagement().and() .securityContext().and() .requestCache().and() .anonymous().and() .servletApi().and() .apply(new DefaultLoginPageConfigurer<>()).and() .logout(); /** 省略 **/ } // 【3】.执行SecurityConfig中的configure(HttpSecurity http)方法 configure(http); return http; }
观察上述代码我们发现,首先通过new HttpSecurity()新建了一个HttpSecurity对象,并且在下方设置HttpSecurity对象中configurers属性以及filter属性。
当执行完下面代码时,即【2】处的代码
http .csrf().and() //【添加CsrfConfigurer】 .addFilter(new WebAsyncManagerIntegrationFilter()) // 【添加 WebAsyncManagerIntegrationFilter】 .exceptionHandling().and() // 【添加 ExceptionHandlingConfigurer】 .headers().and() // 【添加 HeadersConfigurer】 .sessionManagement().and()// 【添加SessionManagementConfigurer】 .securityContext().and()// 【添加SecurityContextConfigurer】 .requestCache().and()// 【添加RequestCacheConfigurer】 .anonymous().and()// 【添加AnonymousConfigurer】 .servletApi().and()// 【添加ServletApiConfigurer】 .apply(new DefaultLoginPageConfigurer<>()).and()//【添加DefaultLoginPageConfigurer】 .logout(); // 【添加LogoutConfigure】r
HttpSecurity对象中configurers属性以及filter如下,此时size分别为 10 和 1
执行了【2】的代码后会执行【3】的代码:
configure(http);
该方法会调用SecurityConfig配置类文件的configure()方法,由于此时传入的HttpSecurity对象,所以调用的
SecurityConfig配置类文件中的configure(HttpSecurity http),代码如下(注意该部分为自定义的配置文件中方法,需要伙伴自己去编写,根据自己需求进行配置。当前仅供参考!):
@Override protected void configure(HttpSecurity http) throws Exception { // 加入用户名密码验证过滤器的前 【添加ValidateCodeFilter】 http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class); // 查询数据库所有权限列表 Listlist = permissionService.list(); 【添加ExpressionUrlAuthorizationConfigurer】 for (Permission permission : list) { // 添加请求权限 http.authorizeRequests().antMatchers(permission.getPermissionUrl()) .hasAuthority(permission.getPermissionTag()); } // 设置权限不足的信息 http.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler); http.formLogin()// 开启表单认证 【添加FormLoginConfigurer】 .loginPage("/toLoginPage")// 自定义登录页面 .loginProcessingUrl("/login")//表单提交的路径 .usernameParameter("username") .passwordParameter("password")//自定义input的name值 .successForwardUrl("/")//登录成功之后跳转的路径 .successHandler(myAuthenticationService) .failureHandler(myAuthenticationService)//登录成功或者失败的处理 .and().logout().logoutUrl("/logout") .logoutSuccessHandler(myAuthenticationService) .and().rememberMe()//开启记住我功能 【添加 RememberMeConfigurer】 .tokenValiditySeconds(1209600)//token失效时间 默认是2周 .rememberMeParameter("remember-me")//自定义表单input值 .tokenRepository(getPersistentTokenRepository()) .and().authorizeRequests().antMatchers("/toLoginPage").permitAll()//放行登录页面 .anyRequest().authenticated(); //关闭csrf防护 // 【移除CsrfConfigurer】 http.csrf().disable(); //加载同源域名下iframe页面 http.headers().frameOptions().sameOrigin(); // 开启跨域支持 // 【添加CorsConfigurer】 http.cors().configurationSource(corsConfigurationSource()); }
执行完该方法后,configurers属性以及filter属性如下,size变为13和2
HttpSecurity对象构建完成!,之后configurers中每个Configurer对象最终都会转换为Filter对象
2.1.2 列表集合 ignoredRequests 的创建
当执行完init()后,HttpSecurity对象构建完成,并且存放在一个叫做securityFilterChainBuilders的集合对象中。
此时我们回到org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#doBuild(),在执行init()方法之后会有一个configure()方法,点击进去可以看到:
private void configure() throws Exception { Collection> configurers = getConfigurers(); for (SecurityConfigurer configurer : configurers) { // 【加载配置类SecurityConfig中的configure(WebSecurity web)方法】 configurer.configure((B) this); } }
注意看
configurer.configure((B) this);
这句代码,此时的this是一个WebSecurity对象
仔细想想,在配置类文件中,不正是有一个#configure(WebSecurity web)配置方法吗?没错,这句代码正是执行了配置文件中的该方法。
#configure(WebSecurity web)方法体:
执行完这一行代码之后,ignoredRequests变量已经被赋值,而且变量中的值是和上面配置方法中配置对应的。
OK,此时SpringSecurity配置信息的加载就已经完成,接下来就是将这些配置信息转换成过滤器并组成过滤器链
2.2 过滤器链的加载
加载完配置文件后,我们的配置信息分别被存放在两部分ignoredRequests和securityFilterBulders之中,下面再来看看这两部分是如何构建成过滤器链的。
2.2.1 ignoredRequests 转换成过滤器链
此时我们再次回到此时我们回到org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#doBuild(),
找到构造过滤器链的方法performBuild()
org.springframework.security.config.annotation.web.builders.WebSecurity#performBuild()
@Override protected Filter performBuild() throws Exception { /** 忽略部分代码 **/ 【1】计算过滤器链长度 int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size(); ListsecurityFilterChains = new ArrayList<>( chainSize); 【2】将ignoredRequests集合中的对象转为过滤器加入到过滤器链中 for (RequestMatcher ignoredRequest : ignoredRequests) { securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest)); } 【3】将securityFilterChainBuilder转为过滤器加入到过滤器链中 for (SecurityBuilder extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) { securityFilterChains.add(securityFilterChainBuilder.build()); } /** 忽略部分代码 **/ } /** 忽略部分代码 **/ return result; }
【1】处代码计算过滤器链长度chainSize,Debug中计算结果如下
【2】处代码将ignoredRequests集合中的对象转为过滤器加入到过滤器链securityFilterChains
中,执行结果如下
ignoredRequests转换成过滤器链完成
【3】处的代码就是 securityFilterBulders的的处理。下面接着讲。
2.2.2 securityFilterBulders 转换成过滤器链
在上面的org.springframework.security.config.annotation.web.builders.WebSecurity#performBuild() 方法中通过观察看到,处理securityFilterBulders代码就一句话
securityFilterChains.add(securityFilterChainBuilder.build());
我们从securityFilterChainBuilder.build(),按照下面顺序一路点击:
我们将会定位到
org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#configure()
private void configure() throws Exception { 【1】获取配置对象 Collection> configurers = getConfigurers(); for (SecurityConfigurer configurer : configurers) { 【2】将配置对象加入到过滤器链 configurer.configure((B) this); } }
在【1】处,我们将得到从securityFilterChainBuilder中得到configurers配置列表
在【2】处,会循环配置列表对每个配置对象处理并且最终加入到过滤器链中。
比如当前是ExceptionHandlingConfigurer,
那么就会执行
org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer#configure()
@Override public void configure(H http) { AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http); // 【1】创建 ExceptionTranslationFilter ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter( entryPoint, getRequestCache(http)); AccessDeniedHandler deniedHandler = getAccessDeniedHandler(http); exceptionTranslationFilter.setAccessDeniedHandler(deniedHandler); exceptionTranslationFilter = postProcess(exceptionTranslationFilter); //【2】 ExceptionTranslationFilter加入到过滤器链 http.addFilter(exceptionTranslationFilter); }
再比如当前是HeaderConfigurer,那么会执行org.springframework.security.config.annotation.web.configurers.HeadersConfigurer#configure():
@Override public void configure(H http) { 【1】创建 HeaderWriterFilter HeaderWriterFilter headersFilter = createHeaderWriterFilter(); 【2】加入到过滤器链 http.addFilter(headersFilter); }
也就是说,具体是什么类型的Filter,是根据当前Configurer而决定的。
当我们执行完所有的configure()方法,再次回到org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#doBuild()
可以看到filter已经被加载出来
但是,过滤器是有一定顺序的,此时加载出来的过滤器并没有处理顺序。
当方法执行到 performBuild()内部时,会进行排序
org.springframework.security.config.annotation.web.builders.HttpSecurity#performBuild()
@Override protected DefaultSecurityFilterChain performBuild() { 【对过滤器进行排序】 filters.sort(comparator); return new DefaultSecurityFilterChain(requestMatcher, filters); }
排序后的过滤器链:
到这里,拦截器链加载结束!
还没有评论,来说两句吧...