spring security给我们提供了功能非常强大的安全保护机制,在使用时的配置也极其简单,在和spring boot工程集成的时候,简单到只需要我们用一个注解@EnableWebSecurity就可以把需要的过滤器都配置好,可是这一切是怎么发生的呢?在本系列文章的第一篇就让我们结合源码来一探究竟。
环境:
spring boot 版本:1.5.4.RELEASE
1.@EnableWebSecurity注解
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) @Target(value = { java.lang.annotation.ElementType.TYPE }) @Documented @Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class }) @EnableGlobalAuthentication @Configuration public @interface EnableWebSecurity { /** * Controls debugging support for Spring Security. Default is false. * @return if true, enables debug support with Spring Security */ boolean debug() default false; }
在这个注解中主要是引入了WebSecurityConfiguration.class这个配置类,另一个就是加入了@EnableGlobalAuthentication 这个注解,本篇文章主要介绍WebSecurityConfiguration.class这个类,下一篇文章将重点介绍EnableGlobalAuthentication这个注解
2.WebSecurityConfiguration类
@Configuration public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware { private WebSecurity webSecurity; private Boolean debugEnabled; private List> webSecurityConfigurers; .... /** * Creates the Spring Security Filter Chain * @return * @throws Exception */ @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(); } .... /** * Sets the {@code } * instances used to create the web configuration. * * @param objectPostProcessor the {@link ObjectPostProcessor} used to create a * {@link WebSecurity} instance * @param webSecurityConfigurers the * {@code } instances used to * create the web configuration * @throws Exception */ @Autowired(required = false) public void setFilterChainProxySecurityConfigurer( ObjectPostProcessor
这个类中最主要就是上面的两个方法,
(1)setFilterChainProxySecurityConfigurer,这个方法有@Autowired注解,会先执行,执行时AutowiredWebSecurityConfigurersIgnoreParents通过这个类的getWebSecurityConfigurers()方法会获取所有实现WebSecurityConfigurer的类,如果我们有自己实现了
WebSecurityConfigurerAdapter的类就会被这个方法获取到对应的实例,排序后放到websecurity中。
在这个方法中还会创建我们第一个比较重要的对象webSecurity。
(2)接着会调用springSecurityFilterChain()方法,这个方法会判断我们上一个方法中有没有获取到webSecurityConfigurers,没有的话这边会创建一个WebSecurityConfigurerAdapter实例,并追加到websecurity中。接着调用websecurity的build方法。实际调用的是websecurity的父类AbstractSecurityBuilder的build方法
3. Websecurity类
Websecurity类主要的继承关系为自Websecurity->AbstractConfiguredSecurityBuilder->AbstractSecurityBuilder,其中上一步调用的build方法就在AbstractSecurityBuilder中
... public final O build() throws Exception { if (this.building.compareAndSet(false, true)) { this.object = doBuild(); return this.object; } throw new AlreadyBuiltException("This object has already been built"); } ... protected abstract O doBuild() throws Exception;
具体的doBuild()逻辑在子类AbstractConfiguredSecurityBuilder的doBuild方法中
... @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; } } ... @SuppressWarnings("unchecked") private void init() throws Exception { Collection> configurers = getConfigurers(); for (SecurityConfigurer configurer : configurers) { configurer.init((B) this); } for (SecurityConfigurer configurer : configurersAddedInInitializing) { configurer.init((B) this); } } ... @SuppressWarnings("unchecked") private void configure() throws Exception { Collection > configurers = getConfigurers(); for (SecurityConfigurer configurer : configurers) { configurer.configure((B) this); } } ... protected abstract O performBuild() throws Exception;
可以看到build过程主要分三步,init->configure->peformBuild
WebSecurityConfigurerAdapter类(最重要的一个类,我们定制化也是继承这个类进行设置的)
/** * Creates the {@link HttpSecurity} or returns the current instance * * ] * @return the {@link HttpSecurity} * @throws Exception */ @SuppressWarnings({ "rawtypes", "unchecked" }) 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, 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 ()).and() .logout(); // @formatter:on ClassLoader classLoader = this.context.getClassLoader(); List defaultHttpConfigurers = SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader); for(AbstractHttpConfigurer configurer : defaultHttpConfigurers) { http.apply(configurer); } } configure(http); return http; } .... public void init(final WebSecurity web) throws Exception { final HttpSecurity http = getHttp(); web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() { public void run() { FilterSecurityInterceptor securityInterceptor = http .getSharedObject(FilterSecurityInterceptor.class); web.securityInterceptor(securityInterceptor); } }); } /** * Override this method to configure {@link WebSecurity}. For example, if you wish to * ignore certain requests. */ public void configure(WebSecurity web) throws Exception { } /** * Override this method to configure the {@link HttpSecurity}. Typically subclasses * should not invoke this method by calling super as it may override their * configuration. The default configuration is: * * * http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic(); ** * @param http the {@link HttpSecurity} to modify * @throws Exception if an error occurs */ // @formatter:off 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(); } ...
这个类中主要的是上面的三个方法,
*1 init方法做了两件事,一个就是调用getHttp()方法获取一个http实例,并通过web.addSecurityFilterChainBuilder方法把获取到的实例赋值给WebSecurity的securityFilterChainBuilders属性,这个属性在我们执行build的时候会用到,第二个就是为WebSecurity追加了一个postBuildAction,在build都完成后从http中拿出FilterSecurityInterceptor对象并赋值给WebSecurity。
*2 getHttp()方法,这个方法在当我们使用默认配置时(大多数情况下)会为我们追加各种SecurityConfigurer的具体实现类到httpSecurity中,如exceptionHandling()方法会追加一个ExceptionHandlingConfigurer,sessionManagement()方法会追加一个SessionManagementConfigurer,securityContext()方法会追加一个SecurityContextConfigurer对象,这些SecurityConfigurer的具体实现类最终会为我们配置各种具体的filter,这些SecurityConfigurer类是怎么调用到的,下面会讲到
另外getHttp()方法的最后会调用configure(http),这个方法也是我们继承WebSecurityConfigurerAdapter类后最可能会重写的方法
*3 configure(HttpSecurity http)方法,默认的configure(HttpSecurity http)方法继续向httpSecurity类中追加SecurityConfigurer的具体实现类,如authorizeRequests()方法追加一个ExpressionUrlAuthorizationConfigurer,formLogin()方法追加一个FormLoginConfigurer。
其中ExpressionUrlAuthorizationConfigurer这个实现类后面会进一步探讨,因为他会给我们创建一个非常重要的对象FilterSecurityInterceptor对象,FormLoginConfigurer对象比较简单,但是也会为我们提供一个在安全认证过程中经常用到会用的一个Filter:UsernamePasswordAuthenticationFilter。
以上三个方法就是WebSecurityConfigurerAdapter类中init方法的主要逻辑,
... @Override protected Filter performBuild() throws Exception { Assert.state( !securityFilterChainBuilders.isEmpty(), "At least one SecurityBuilder extends SecurityFilterChain> needs to be specified. Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. More advanced users can invoke " + WebSecurity.class.getSimpleName() + ".addSecurityFilterChainBuilder directly"); int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size(); ListsecurityFilterChains = new ArrayList ( chainSize); for (RequestMatcher ignoredRequest : ignoredRequests) { securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest)); } for (SecurityBuilder extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) { securityFilterChains.add(securityFilterChainBuilder.build()); } FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains); if (httpFirewall != null) { filterChainProxy.setFirewall(httpFirewall); } filterChainProxy.afterPropertiesSet(); Filter result = filterChainProxy; if (debugEnabled) { 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); } postBuildAction.run(); return result; } ...
这个方法中最主要的任务就是遍历securityFilterChainBuilders属性中的SecurityBuilder对象,并调用他的build方法。
这个securityFilterChainBuilders属性我们前面也有提到过,就是在WebSecurityConfigurerAdapter类的init方法中获取http后赋值给了WebSecurity。因此这个地方就是调用httpSecurity的build方法。
3. HttpSecurity类
HttpSecurity和WebSecurity两个类继承关系都一样,所以httpSecurity的build也是分三步
init->configure->performBuild,只不过在webSecurity中SecurityConfigurer列表在默认实现下只有WebSecurityConfigurerAdapter一个,而httpSecurity中的SecurityConfigurer列表我们在WebSecurityConfigurerAdapter的init方法中设置了很多,这时就会依次调用我们设置的SecurityConfigurer的具体实现类。我在debug时看了,默认情况下springSecurity有下面的这些
[org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer@350a94ce, org.springframework.security.config.annotation.web.configurers.HeadersConfigurer@7e00ed0f, org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer@b0fc838, org.springframework.security.config.annotation.web.configurers.SecurityContextConfigurer@3964d79, org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer@62db0521, org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer@1b4ae4e0, org.springframework.security.config.annotation.web.configurers.ServletApiConfigurer@6ef1a1b9, org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer@5fbdc49b, org.springframework.security.config.annotation.web.configurers.LogoutConfigurer@65753040, org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer@2954b5ea, org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer@4acb2510, org.springframework.security.config.annotation.web.configurers.RememberMeConfigurer@7be3a9ce]
4.上面这些配置每个都完成一段逻辑,如RememberMeConfigurer会向HttpSecurity中追加一个RememberMeAuthenticationFilter实例,一个RememberMeAuthenticationProvider实例,用来做实际的认证。因为这些类做的事情都比较单一明确,从类名就可以猜测出来这些类做的工作,这里就不一个一个讲解,下面简单以ExpressionUrlAuthorizationConfigurer这个类为例来做个说明,因为这个类会为我们创建一个非常重要的对象FilterSecurityInterceptor
ExpressionUrlAuthorizationConfigurer的继承关系
ExpressionUrlAuthorizationConfigurer->AbstractInterceptUrlConfigurer->AbstractHttpConfigurer->SecurityConfigurerAdapter->SecurityConfigurer
对应的init方法在SecurityConfigurerAdapter类中,是个空实现,什么也没有做,configure方法在SecurityConfigurerAdapter类中也有一个空实现,在AbstractInterceptUrlConfigurer类中进行了重写
@Override public void configure(H http) throws Exception { FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource(http); if (metadataSource == null) { return; } FilterSecurityInterceptor securityInterceptor = createFilterSecurityInterceptor( http, metadataSource, http.getSharedObject(AuthenticationManager.class)); if (filterSecurityInterceptorOncePerRequest != null) { securityInterceptor .setObserveOncePerRequest(filterSecurityInterceptorOncePerRequest); } securityInterceptor = postProcess(securityInterceptor); http.addFilter(securityInterceptor); http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor); } ... private AccessDecisionManager createDefaultAccessDecisionManager(H http) { AffirmativeBased result = new AffirmativeBased(getDecisionVoters(http)); return postProcess(result); } ... private FilterSecurityInterceptor createFilterSecurityInterceptor(H http, FilterInvocationSecurityMetadataSource metadataSource, AuthenticationManager authenticationManager) throws Exception { FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor(); securityInterceptor.setSecurityMetadataSource(metadataSource); securityInterceptor.setAccessDecisionManager(getAccessDecisionManager(http)); securityInterceptor.setAuthenticationManager(authenticationManager); securityInterceptor.afterPropertiesSet(); return securityInterceptor; }
在这个类的configure中创建了一个FilterSecurityInterceptor,并且也可以明确看到spring security默认给我们创建的AccessDecisionManager是AffirmativeBased。
5.最后再看下HttpSecurity类执行build的最后一步 performBuild,这个方法就是在HttpSecurity中实现的
@Override protected DefaultSecurityFilterChain performBuild() throws Exception { Collections.sort(filters, comparator); return new DefaultSecurityFilterChain(requestMatcher, filters); }
可以看到,这个类只是把我们追加到HttpSecurity中的security进行了排序,用的排序类是FilterComparator,从而保证我们的filter按照正确的顺序执行。接着将filters构建成filterChian返回。在前面WebSecurity的performBuild方法中,这个返回值会被包装成FilterChainProxy,并作为WebSecurity的build方法的放回值。从而以springSecurityFilterChain这个名称注册到springContext中(在WebSecurityConfiguration中做的)
6.在WebSecurity的performBuild方法的最后一步还执行了一个postBuildAction.run,这个方法也是spring security给我们提供的一个hooks,可以在build完成后再做一些事情,比如我们在WebSecurityConfigurerAdapter类的init方法中我们利用这个hook在构建完成后将FilterSecurityInterceptor赋值给了webSecurity类的filterSecurityInterceptor属性