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);
// 查询数据库所有权限列表
List<Permission> list = 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<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
// 构建HttpSecurity对象
configurer.init((B) this);
}
for (SecurityConfigurer<O, B> 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);
// 查询数据库所有权限列表
List<Permission> list = 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<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> 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();
List<SecurityFilterChain> securityFilterChains = 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<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> 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);
}
排序后的过滤器链:
到这里,拦截器链加载结束!