Security配置生效源码分析
Posted jazon@
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Security配置生效源码分析相关的知识,希望对你有一定的参考价值。
文章概述
本文主要解决几个问题:
- security配置生效的入口是哪里
- 我们平时写的security配置是如何被调用的
- 为什么我们写的配置需要继承WebSecurityConfigurerAdapter
- HttpSecurity和WebSecurity是干嘛用的
配置核心类
- 启动Security,需要在启动类上添加
@EnableSecurity
注解
@SpringBootApplication
@EnableWebSecurity
public class ThingsboardApp
...
@EnableSecurity
解析
@Import( WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class,
OAuth2ImportSelector.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;
@EnableSecurity
注解通过@Import
引入了WebSecurityConfiguration作为bean。
- WebSecurityConfiguration类,是一个bean,spring启动后,关于security的配置生效从这里开始。
@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware
...
实现了ImportAware接口,ImportAware接口作用是将引入了@Import
注解的类的AnnotationMetadata信息通过setImportMetadata
方法传入ImportAware的实现类中。借鉴点:@Import
是叠加在@EnableSecurity
注解上的,而@EnableSecurity又是注解在启动类上的,于是在WebSecurityConfiguration类就能拿到启动类上的注解信息,通常关心的是@EnableXXXX
注解的信息,因为这里可能包含一些开启的配置信息。
实现了BeanClassLoaderAware接口,将Spring容器的BeanClassLoader设置到属性中,目前没看到有用到这个属性。
配置注入
WebSecurityConfiguration类
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
ObjectPostProcessor<Object> objectPostProcessor,
@Value("#@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
throws Exception
...
方法参数利用SPEL将注入类型为WebSecurityConfigurer.class的bean,WebSecurityConfigurer的重要实现类WebSecurityConfigurerAdapter,是我们配置security通常需要继承的基类。于是这里拿到了我们的配置bean,将它们设置到webSecurityConfigurers属性中。
配置生效入口
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception
...
return webSecurity.build();
这个方法返回了一个Filter的bean,即为Sevlet加入了一个过滤器,我们从之前的Security整体架构文章知道Security本质上就一个Filter,所以我们的重点就关注这个Filter是怎么生成,以及它里边有哪些属性。
WebSecurity–生效配置的对象
一个应用应当只有一个WebSecurity即可,它用于构建Security的ServletFitler,即下图红色框起来Fitler, 所以WebSecurity是一个builder。
看一下类定义
public final class WebSecurity extends
AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements
SecurityBuilder<Filter>, ApplicationContextAware
- 继承类AbstractConfiguredSecurityBuilder,从字面含义来看,这个类是可配置的Builder,意思是配置Builder参数来个性化地build对象。
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
extends AbstractSecurityBuilder<O>
...
AbstractConfiguredSecurityBuilder继承自AbstractSecurityBuilder,AbstractSecurityBuilder是SecurityBuilder的抽象实现类,它相当于模板类,定义了build的流程及提供了一些公共方法。借鉴点:AbstractConfiguredSecurityBuilder是在AbstractSecurityBuilder基础上叠加了可配置的概念,所以将他们抽象成两个类。AbstractConfiguredSecurityBuilder实现可配置的方式是往builder内加入SecurityConfigurer对象(前面说的配置注入即实现了此功能),在build过程中,会将配置应用到builder上。
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>>
void init(B builder) throws Exception;
void configure(B builder) throws Exception;
SecurityConfigurer接口定义了两个方法,都是可用于配置builder的。
到此,我们知道了WebSecurity对象里有很多SecurityConfigurer对象,SecurityConfigurer对象就是我们日常开发时写的那些配置类,前文配置注入也提到了。
开始生效配置并创建Filter
protected final O doBuild() throws Exception
synchronized (configurers)
buildState = BuildState.INITIALIZING;
beforeInit();
// 这一步会调用所有SecurityConfigurer类的init方法
init();
buildState = BuildState.CONFIGURING;
beforeConfigure();
// 循环调用SecurityConfigurer类的configure方法
configure();
buildState = BuildState.BUILDING;
// build方法,真正创建
O result = performBuild();
buildState = BuildState.BUILT;
return result;
init方法
我们重点关注WebSecurityConfigurerAdapter这个配置类的,看看它的init方法
public void init(final WebSecurity web) throws Exception
final HttpSecurity http = getHttp();
web.addSecurityFilterChainBuilder(http).postBuildAction(() ->
FilterSecurityInterceptor securityInterceptor = http
.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
);
可以看到这一步有个getHttp,我们平时写的配置类的方法configure(HttpSecurity http)就是在这一步被调用到的。
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);
authenticationBuilder.authenticationEventPublisher(eventPublisher);
Map<Class<?>, 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<AbstractHttpConfigurer> defaultHttpConfigurers =
SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
for (AbstractHttpConfigurer configurer : defaultHttpConfigurers)
http.apply(configurer);
// 调用我们写的配置类的configure(http)
configure(http);
return http;
可以看到首先创建了一个HttpSecurity对象,然后对它进行.csrf()等各种方法调用,我们点开方法后方法,其实.csrf()等各种方法就是往HttpSecurity对象中加入各种SecurityConfigurer,最后再调用configure(http), 这个就是我们写的配置类的configure(HttpSecurity http)方法。从命名上,我们可以推测出HttpSecurity也是一个Builder,那么HttpSecurity方法build的是什么呢?这里先不分析,继续往后看。
拿到一个HttpSecurity对象后,就调用WebSecurity.addSecurityFilterChainBuidler往里添加httpSecurity对象。
稍微总结下,WebSecurity里有很多Configurer对象,这些对象是我们平时开发时写的Security配置类,每个配置类对象都会调用init方法,创建HttpSecurity对象,并加入到WebSecurity的filterChain列表中。HttpSecurity也是一个Builder,内部也有很多Configurer对象,在getHttp()中会去调用我们写的配置类的configure(HttpSecurity http),往其中加入Configurer对象。
configure方法
init方法分析完了,接下来是configure方法,configure方法即调用我们写的配置类的configure(WebSecurity webSecurity)方法,这个方法就是直接配置的WebSecurity的某些属性。
performBuild方法
configure方法执行完后,就是performBuild方法,这个方法就是最终生成Filter的方法。我们知道,security的架构中,整个security是一个ServletFilter,在这个filter内有很多filterchain,当一个http请求进来后,会经过总filter,然后进入到筛选filterchain的过程,选中后,执行fitlerchain内的所有SecurityFilter。
在前文,我们留了个问题: HttpSecurity的build方法是生成什么的,下面我们来看看WebSecurity的perfomBuild()方法,从中寻找答案。
protected Filter performBuild() throws Exception
....
int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
chainSize);
for (RequestMatcher ignoredRequest : ignoredRequests)
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
// 可以看到这里调用了securityFilterChainBuilder.build(),这里很重要
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;
postBuildAction.run();
return result;
第12行代码,循环了securityFilterChainBuilders,我们前文将HttpSecurity加入到此列表中,也就是这里调用的即HttpSecurity.build,所以HttpSecurity是创建SecurityFilterChain对象的,结合上面的架构图,我们可以分析出,一个HttpSecurity就对应一个SecurityFilterChain,前文我们说过,WebSecurityConfigurer配置类的init()会调用getHttp(),生成HttpSecurity对象,于是我们推出,我们写的每个配置类最终都会转变为一个SecurityFilterChain。其实从HttpSecurity的类继承关系我们也可以知道,它是用来创建SecurityFilterChain的。
FilterChainProxy将securityFilterChains作为构造参数构建对象,并返回给到最外层的方法,FilterChainProxy就是我们Security创建的ServletFilter了。
至此,security使用配置,最终创建Filter的过程就分析完毕。
借鉴点总结
@Import
是叠加在@EnableSecurity
注解上的,而@EnableSecurity又是注解在启动类上的,于是在WebSecurityConfiguration类就能拿到启动类上的注解信息,通常关心的是@EnableXXXX
注解的信息,因为这里可能包含一些开启的配置信息。- 类定义的设计上,假设有可配置等功能,可以将其抽象出单独的类,继承自基类,参考有AbstractConfiguredSecurityBuilder和SecurityBuilder,最终将不同功能的类再从类继承关系上结合到一起,使得类名清晰,层次分明。
- Builder和Configurer的关系,利用泛型表明Builder是创建什么的,Configurer是配置什么的,Builder对象组合了Configurer对象,使得Configurer可以配置Builder。A类功能能够影响B类,可以用的此种方法。
以上是关于Security配置生效源码分析的主要内容,如果未能解决你的问题,请参考以下文章
Spring实战----Security4.1.3鉴权之美--基于投票的AccessDecisionManager实现及源码分析
Spring Security源码:权限访问控制是如何做到的?
Spring实战----Security4.1.3认证过程源码分析