0. 概述
经过基于注解的Spring Security原理解析分析,Spring Security本身所做的事情就是在Spring容器中注册了一系列的Filter,这些Filters在检测到满足条件的URL请求时,会执行其定义的处理过程; Security本身默认提供了一些Filter来完成其各种功能; 本文主要分析以下问题:
- 默认Filter的作用及配置
- 默认Filter的配置及生效示例分析
由于水平受限,如有分析不正确处敬指出。
1. 默认Filter分析
1.1 概述
Security默认的Filter入口在HttpSecurity对象中;关于该对象的加载过程,请参考基于注解的Spring Security原理解析;
在HttpSecurity对象中,实际提供的是各默认Filter的配置类,通过配置类来控制对应Filter的各个属性配置;在配置完成将Filter加载到HttpSecurity中的FilterChain中去。
在HttpSecurity中提供了以下默认Filter及其配置类:
Configurer | Filter | 功能说明 |
---|---|---|
OpenIDLoginConfigurer | OpenIDAuthenticationFilter | 处理OpenID授权请求 |
HeaderWriterFilter | HeadersConfigurer | 在返回报文头中添加Security相关信息 |
CorsConfigurer | CorsFilter | 提供跨域访问配置支持的Filter |
SessionManagementConfigurer | SessionManagementFilter | 会话管理Filter |
PortMapperConfigurer | 无 | 用于在Http及Https请求之间重定向时的端口判定 |
JeeConfigurer | J2eePreAuthenticatedProcessingFilter | 添加J2EE预授权处理机制支持 |
X509Configurer | X509AuthenticationFilter | 添加X509预授权处理机制支持 |
RememberMeConfigurer | RememberMeAuthenticationFilter | 记住用户名及密码功能支持 |
ExpressionUrlAuthorizationConfigurer | FilterSecurityInterceptor | Security的主要Filter,通过调用权限管理器等进行Http访问的权限判断 |
RequestCacheConfigurer | RequestCacheAwareFilter | 缓存请求并在必要的时候使用缓存的请求 |
ExceptionHandlingConfigurer | ExceptionTranslationFilter | 处理AccessDeniedException及AuthenticationException异常 |
SecurityContextConfigurer | SecurityContextPersistenceFilter | SecurityContext对象持久化Filter,用于在请求开始阶段初始化并持久化该对象,在后续的Filter中可以使用该对象来获取信息 |
ServletApiConfigurer | SecurityContextHolderAwareRequestFilter | 在原始请求基础上包装一些方法供后续调用 |
CsrfConfigurer | CsrfFilter | 跨站请求伪造保护Filter; |
LogoutConfigurer | LogoutFilter | 退出登录请求处理Filter |
AnonymousConfigurer | AnonymousAuthenticationFilter | 匿名请求控制Filter |
FormLoginConfigurer | UsernamePasswordAuthenticationFilter | 表单登录请求处理Filter |
OAuth2LoginConfigurer | OAuth2AuthorizationRequestRedirectFilter | OAuth2请求权限控制处理Filter,为其它网站提供本网站Oauth2方式登录,即其它网站通过本网站的账户密码进行登录授权 |
ChannelSecurityConfigurer | ChannelProcessingFilter | 通道选择Filter,确保请求是通过正确的通道过来的,如Http或者Https |
HttpBasicConfigurer | BasicAuthenticationFilter | Security基础登录授权Filter,将其结果保存在SecurityContextHolder中 |
默认的Filter并不是在HttpSecurity对象初始化的时候就全部加载,而是根据用户定制情况进行加载,具体加载情况见后文;
1.2 默认Filter默认配置
在WebSecurityConfigurerAdapter类中,存在默认的configure方法,它会提供一些默认的权限控制配置,默认方法实现哪下:
protected void configure(HttpSecurity http) throws Exception {
logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}
同时,在初始化HttpSecurity的方法init中,也会提供一些默认的配置:
protected final HttpSecurity getHttp() throws Exception {
if (http != null) {
return http;
}
DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
.postProcess(new DefaultAuthenticationEventPublisher());
localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
AuthenticationManager authenticationManager = authenticationManager();
authenticationBuilder.parentAuthenticationManager(authenticationManager);
Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
sharedObjects);
if (!disableDefaults) {
// @formatter:off
http
.csrf().and()
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling().and()
.headers().and()
.sessionManagement().and()
.securityContext().and()
.requestCache().and()
.anonymous().and()
.servletApi().and()
.apply(new DefaultLoginPageConfigurer<HttpSecurity>()).and()
.logout();
// @formatter:on
ClassLoader classLoader = this.context.getClassLoader();
List<AbstractHttpConfigurer> defaultHttpConfigurers =
SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
for(AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
http.apply(configurer);
}
}
configure(http);
return http;
}
通过分析这两个配置处,Security默认加载的Filter清单如下:
Configurer | Filter |
---|---|
CsrfConfigurer | CsrfFilter |
空 | WebAsyncManagerIntegrationFilter |
ExceptionHandlingConfigurer | ExceptionTranslationFilter |
HeadersConfigurer | HeaderWriterFilter |
SessionManagementConfigurer | SessionManagementFilter |
SecurityContextConfigurer | SecurityContextPersistenceFilter |
RequestCacheConfigurer | RequestCacheAwareFilter |
AnonymousConfigurer | AnonymousAuthenticationFilter |
ServletApiConfigurer | SecurityContextHolderAwareRequestFilter |
DefaultLoginPageConfigurer | DefaultLoginPageGeneratingFilter |
LogoutConfigurer | LogoutFilter |
FormLoginConfigurer | UsernamePasswordAuthenticationFilter |
HttpBasicConfigurer | BasicAuthenticationFilter |
1.3 默认Filter用户自定义加载
当开发的时候使用@EnableWebSecurity注解加载Security的配置类时,如果该类继承了WebSecurityConfigurerAdapter类,则可以覆盖其configure方法来配置权限控制参数:
@EnableWebSecurity(debug = true)
public class SercurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private TestAuthenticationProvider authenticationProvider;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/*", "/home", "/manager/dbManager.html", "/js/*", "/db/*", "/js/**/*").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/")
.permitAll()
.and()
.sessionManagement().invalidSessionUrl("/timeout");
http.headers().frameOptions().sameOrigin().httpStrictTransportSecurity().disable();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
@Bean
public UserDetailsService getUserDetailservice() {
return new TestUserDetailService();
}
}
此时即可配置登录链接、退出登录链接、各个URL的访问权限配置等。
1.4 Filter执行顺序分析
Security在初始化的时候会初始化一系列的Filter,这些Filter之间实际上是有先后关系的,其先后关系是如何控制的?
通过前面的分析,Security的Filters是加载到HttpSecurity对象的Filters属性中去的,在HttpSecurity对象Build的时候生成FilterChain对象,此时会将所有添加的Filters添加到FilterChain对象中并返回。其PerformBuild方法实现如下:
@Override
protected DefaultSecurityFilterChain performBuild() throws Exception {
Collections.sort(filters, comparator);
return new DefaultSecurityFilterChain(requestMatcher, filters);
}
可以看到在PerformBuild方法中,第一步是对所有的Fiilters进行排序;其排序规则使用HttpSecurity的属性comparator;我们看下comparator的初始化:
private FilterComparator comparator = new FilterComparator();
再分析下该Comparator的实现:
final class FilterComparator implements Comparator<Filter>, Serializable {
private static final int STEP = 100;
private Map<String, Integer> filterToOrder = new HashMap<String, Integer>();
FilterComparator() {
int order = 100;
put(ChannelProcessingFilter.class, order);
order += STEP;
put(ConcurrentSessionFilter.class, order);
order += STEP;
put(WebAsyncManagerIntegrationFilter.class, order);
order += STEP;
put(SecurityContextPersistenceFilter.class, order);
order += STEP;
put(HeaderWriterFilter.class, order);
order += STEP;
put(CorsFilter.class, order);
order += STEP;
put(CsrfFilter.class, order);
order += STEP;
put(LogoutFilter.class, order);
order += STEP;
put(X509AuthenticationFilter.class, order);
order += STEP;
put(AbstractPreAuthenticatedProcessingFilter.class, order);
order += STEP;
filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter",
order);
order += STEP;
put(UsernamePasswordAuthenticationFilter.class, order);
order += STEP;
put(ConcurrentSessionFilter.class, order);
order += STEP;
filterToOrder.put(
"org.springframework.security.openid.OpenIDAuthenticationFilter", order);
order += STEP;
put(DefaultLoginPageGeneratingFilter.class, order);
order += STEP;
put(ConcurrentSessionFilter.class, order);
order += STEP;
put(DigestAuthenticationFilter.class, order);
order += STEP;
put(BasicAuthenticationFilter.class, order);
order += STEP;
put(RequestCacheAwareFilter.class, order);
order += STEP;
put(SecurityContextHolderAwareRequestFilter.class, order);
order += STEP;
put(JaasApiIntegrationFilter.class, order);
order += STEP;
put(RememberMeAuthenticationFilter.class, order);
order += STEP;
put(AnonymousAuthenticationFilter.class, order);
order += STEP;
put(SessionManagementFilter.class, order);
order += STEP;
put(ExceptionTranslationFilter.class, order);
order += STEP;
put(FilterSecurityInterceptor.class, order);
order += STEP;
put(SwitchUserFilter.class, order);
}
很明显执行顺序是在这个地方控制的。
2. 默认Filter配置分析举例
2.1 FormLoginConfigurer
2.1.1 概述
表单登录Filter配置类;用于基于表单的权限控制配置;所有配置项均存在默认值,因此所有参数均可使用默认值; 需要特别说明的是,如果没有指定loginPage配置项,Security将会提供一个默认的登录页面。 该配置项配置的实际上是UsernamePasswordAuthenticationFilter这个Filter。
2.1.2 配置详解
如要配置FormLoginConfigurer,在继承自WebSecurityConfigurerAdapter的类的Configure方法中进行:
http.formLogin()
.loginPage("/login")
.permitAll();
该配置类所包含的配置项清单及其作用见下表:
配置项 | 配置项说明 |
---|---|
loginPage | 登录页面,如果用户未指定,Security将提供默认的登录页面; 默认登录页面请求链接:/login |
usernameParameter | 用户名属性的名称;默认是username |
passwordParameter | 密码属性名称,默认是password |
failureForwardUrl | 授权失败时的跳转链接 |
failureUrl | 登录失败时的跳转链接,默认是/login?error |
failureHandler | 登录失败后的处理器 |
successForwardUrl | 授权成功时的跳转链接 |
successHandler | 登录成功时的处理器 |
defaultSuccessUrl | 指定如果用户登录前未访问需要授权访问的页面,登录成功后的跳转链接 |
loginProcessingUrl | 登录请求处理链接 |
authenticationDetailsSource | 保存登录请求地址及其SessionID的对象 |
2.1.3 Filter配置过程分析
上方已经提到过,Configurer是用来配置Filter的各个属性的;实际上最终添加到HttpSecurity对象的Filters属性中去的还是各个Filter;那Configurer与Filter分别是如何发挥作用的?
在此我们将以FormLoginConfigurer为例来分析其生效过程。
先来看下配置加载过程:
其中HttpSecurity继承自AbstractConfiguredSecurityBuilder类,其配置入口是其方法formLogin;实际最终调用的是其父类的add方法,将FormLoginConfigurer对象添加进去。
add方法源码如下:
private <C extends SecurityConfigurer<O, B>> void add(C configurer) throws Exception {
Assert.notNull(configurer, "configurer cannot be null");
Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
.getClass();
synchronized (configurers) {
if (buildState.isConfigured()) {
throw new IllegalStateException("Cannot apply " + configurer
+ " to already built object");
}
List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers
.get(clazz) : null;
if (configs == null) {
configs = new ArrayList<SecurityConfigurer<O, B>>(1);
}
configs.add(configurer);
this.configurers.put(clazz, configs);
if (buildState.isInitializing()) {
this.configurersAddedInInitializing.add(configurer);
}
}
}
该段代码实际所做的事情就是: 将FormLoginConfigurer对象添加到HttpSecurity的configurers属性中去;
在基于注解的Spring Security原理解析一文中已经说明,HttpSecurity最终会被加载到WebSecurity的securityFilterChainBuilders属性中去; 添加进去后,在WebSecurity对象的build方法中,会调用HttpSecurity的build方法生成FilterChain对象。
HttpSecurity对象的其Build方法实现如下(AbstractConfiguredSecurityBuilder父类中实现):
@Override
protected final O doBuild() throws Exception {
synchronized (configurers) {
buildState = BuildState.INITIALIZING;
beforeInit();
init();
buildState = BuildState.CONFIGURING;
beforeConfigure();
configure();
buildState = BuildState.BUILDING;
O result = performBuild();
buildState = BuildState.BUILT;
return result;
}
}
可以看到在performBuild前会调用configure方法; 跟踪HttpSecurity的该方法,其实现如下:
private void configure() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.configure((B) this);
}
}
可以看到实际调用的是每个FilterConfigurer的configure方法,此处也就是FormLoginConfigurer的configure方法(实现在其父类中):
@Override
public void configure(B http) throws Exception {
PortMapper portMapper = http.getSharedObject(PortMapper.class);
if (portMapper != null) {
authenticationEntryPoint.setPortMapper(portMapper);
}
authFilter.setAuthenticationManager(http
.getSharedObject(AuthenticationManager.class));
authFilter.setAuthenticationSuccessHandler(successHandler);
authFilter.setAuthenticationFailureHandler(failureHandler);
if (authenticationDetailsSource != null) {
authFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
}
SessionAuthenticationStrategy sessionAuthenticationStrategy = http
.getSharedObject(SessionAuthenticationStrategy.class);
if (sessionAuthenticationStrategy != null) {
authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
}
RememberMeServices rememberMeServices = http
.getSharedObject(RememberMeServices.class);
if (rememberMeServices != null) {
authFilter.setRememberMeServices(rememberMeServices);
}
F filter = postProcess(authFilter);
http.addFilter(filter);
}
该方法是重点了,可以看到执行过程实际就是根据Configurer对象中的属性更新authFilter对象的属性,最后调用HttpSecurity的addFilter方法将生成的Filter添加到了HttpSecurity对象中去。authFilter对象实现类型为: DefaultLoginPageGeneratingFilter。
在Configure方法完成后,HttpSecurity的build方法最终调用的是其performBuild方法,该方法实现如下:
@Override
protected DefaultSecurityFilterChain performBuild() throws Exception {
Collections.sort(filters, comparator);
return new DefaultSecurityFilterChain(requestMatcher, filters);
}
实际上就是组装了一个包含已配置的Filter的一个FilterChain对象。此时
至此,Filter的配置加载过程已经分析完成。
2.1.4 Filter执行过程分析
经过2.1.3中分析,FormLoginConfigurer最终是向HttpSecurity中添加了DefaultLoginPageGeneratingFilter这样一个Filter。其doFilter方法如下:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
boolean loginError = isErrorPage(request);
boolean logoutSuccess = isLogoutSuccess(request);
if (isLoginUrlRequest(request) || loginError || logoutSuccess) {
String loginPageHtml = generateLoginPageHtml(request, loginError,
logoutSuccess);
response.setContentType("text/html;charset=UTF-8");
response.setContentLength(loginPageHtml.length());
response.getWriter().write(loginPageHtml);
return;
}
chain.doFilter(request, response);
}
实际也就是将判断是否是登录、登录失败、退出登录请求,如果是的话则跳转到登录页面;此时将不会再执行后续的Filters了。如果不是上述请求,则继续执行FilterChain中的其它Filters。