别辜负生命,别辜负本人。
楔子
后面两期我讲了SpringSecurity认证流程和SpringSecurity鉴权流程,明天是第三期,是SpringSecurity
的收尾工作,讲SpringSecurity
的启动流程。
就像很多电影拍火了之后其续作往往是前作的后期故事一样,我这个第三期要讲的SpringSecurity启动流程
也是不择不扣的"后期故事",它能帮忙你真正认清SpringSecurity
的整体全貌。
在之前的文章里,在说到SpringSecurity
中的过滤器链的时候,往往是把它作为一个概念理解的,就是咱们只是晓得有这么个货色,也晓得它到底是干什么用的,然而咱们却不晓得这个过滤器链是由什么类什么时候去怎么样创立进去的。
明天这期就是要理解SpringSecurity
的主动配置到底帮咱们做了什么,它是如何把过滤器链给创立进去的,又是在默认配置的时候怎么退出了咱们的自定义配置。
祝有好播种(边赞边看,法力有限)。
1. ????EnableWebSecurity
咱们先来看看咱们个别是如何应用SpringSecurity
的。
咱们用SpringSecurity
的时候都会先新建一个SpringSecurity
相干的配置类,用它继承WebSecurityConfigurerAdapter
,而后打上注解@EnableWebSecurity
,而后咱们就能够通过重写 WebSecurityConfigurerAdapter
外面的办法来实现咱们本人的自定义配置。
就像这样:
`@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
}
}`
咱们曾经晓得,继承WebSecurityConfigurerAdapter
是为了重写配置,那这个注解是做了什么呢?
从它的名字@EnableWebSecurity
咱们能够大略猜出来,它就是那个帮咱们主动配置了SpringSecurity
的好心人。
`@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@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;
}`
emmm,我猜大家应该有注解相干的常识吧,ok,既然你们都有注解相干的常识,我就间接讲了。
这个@EnableWebSecurity
中有两个中央是比拟重要的:
- 一是
@Import
注解导入了三个类,这三个类中的后两个是SpringSecurity
为了兼容性做的一些货色,兼容SpringMVC
,兼容SpringSecurityOAuth2
,咱们次要看的其实是第一个类,导入这个类代表了加载了这个类外面的内容。 - 二是
@EnableGlobalAuthentication
这个注解,@EnableWebSecurity
大家还没搞明确呢,您这又来一个,这个注解呢,其作用也是加载了一个配置类-AuthenticationConfiguration
,看它的名字大家也可应该晓得它加载的类是什么相干的了吧,没错就是AuthenticationManager
相干的配置类,这个咱们能够当前再说。
综上所述,@EnableWebSecurity
能够说是帮咱们主动加载了两个配置类:WebSecurityConfiguration
和AuthenticationConfiguration
(@EnableGlobalAuthentication
注解加载了这个配置类)。
其中WebSecurityConfiguration
是帮忙咱们建设了过滤器链的配置类,而AuthenticationConfiguration
则是为咱们注入AuthenticationManager
相干的配置类,咱们明天次要讲的是WebSecurityConfiguration
。
2. ????源码概览
既然讲的是WebSecurityConfiguration
,咱们照例先把源码给大家看看,精简了一下无关紧要的:
`@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
private WebSecurity webSecurity;
private Boolean debugEnabled;
private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;
private ClassLoader beanClassLoader;
@Autowired(required = false)
private ObjectPostProcessor<Object> objectObjectPostProcessor;
@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();
}
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
ObjectPostProcessor<Object> objectPostProcessor,
@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
throws Exception {
webSecurity = objectPostProcessor
.postProcess(new WebSecurity(objectPostProcessor));
if (debugEnabled != null) {
webSecurity.debug(debugEnabled);
}
webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);
Integer previousOrder = null;
Object previousConfig = null;
for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
if (previousOrder != null && previousOrder.equals(order)) {
throw new IllegalStateException(
"@Order on WebSecurityConfigurers must be unique. Order of "
- order + " was already used on " + previousConfig + ", so it cannot be used on "
- config + " too.");
}
previousOrder = order;
previousConfig = config;
}
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
}`
如代码所示,首先WebSecurityConfiguration
是个配置类,类下面打了@Configuration
注解,这个注解的作用大家还晓得吧,在这里就是把这个类中所有带@Bean
注解的Bean给实例化一下。
这个类外面比拟重要的就两个办法:springSecurityFilterChain
和setFilterChainProxySecurityConfigurer
。
springSecurityFilterChain
办法上打了@Bean
注解,任谁也能看进去就是这个办法创立了springSecurityFilterChain
,然而先别着急,咱们不能先看这个办法,尽管它在下面。
3. ????SetFilterChainProxySecurityConfigurer
咱们要先看上面的这个办法:setFilterChainProxySecurityConfigurer
,为啥呢?
为啥呢?
因为它是@Autowired
注解,所以它要比springSecurityFilterChain
办法优先执行,从零碎加载的程序来看,咱们须要先看它。
@Autowired
在这里的作用是为这个办法主动注入所须要的两个参数,咱们先来看看这两个参数:
- 参数
objectPostProcessor
是为了创立WebSecurity
实例而注入进来的,先理解一下即可。 - 参数
webSecurityConfigurers
是一个List,它实际上是所有WebSecurityConfigurerAdapter
的子类,那如果咱们定义了自定义的配置类,其实就是把咱们的配置也读取到了。这里其实有点难懂为什么参数中
SecurityConfigurer<Filter, WebSecurity>
这个类型能够拿到WebSecurityConfigurerAdapter
的子类?因为
WebSecurityConfigurerAdapter
实现了WebSecurityConfigurer<WebSecurity>
接口,而WebSecurityConfigurer<WebSecurity>
又继承了SecurityConfigurer<Filter, T>
,通过一层实现,一层继承关系之后,WebSecurityConfigurerAdapter
终于成为了SecurityConfigurer
的子类。而参数中
SecurityConfigurer<Filter, WebSecurity>
中的两个泛型参数其实是起到了一个过滤的作用,认真查看咱们的WebSecurityConfigurerAdapter
的实现与继承关系,你能够发现咱们的WebSecurityConfigurerAdapter
正好是这种类型。
ok,说完了参数,我感觉咱们能够看看代码了:
`@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
ObjectPostProcessor<Object> objectPostProcessor,
@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
throws Exception {
// 创立一个webSecurity实例
webSecurity = objectPostProcessor
.postProcess(new WebSecurity(objectPostProcessor));
if (debugEnabled != null) {
webSecurity.debug(debugEnabled);
}
// 依据order排序
webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);
Integer previousOrder = null;
Object previousConfig = null;
for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
if (previousOrder != null && previousOrder.equals(order)) {
throw new IllegalStateException(
"@Order on WebSecurityConfigurers must be unique. Order of "
- order + " was already used on " + previousConfig + ", so it cannot be used on "
- config + " too.");
}
previousOrder = order;
previousConfig = config;
}
// 保留配置
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
webSecurity.apply(webSecurityConfigurer);
}
// 成员变量初始化
this.webSecurityConfigurers = webSecurityConfigurers;
}`
依据咱们的正文,这段代码做的事件能够分为认为几步:
- 创立了一个webSecurity实例,并且赋值给成员变量。
- 紧接着对
webSecurityConfigurers
通过order
进行排序,order
是加载程序。 - 进行判断是否有雷同
order
的配置类,如果呈现将会间接报错。 - 保留配置,将其放入
webSecurity
的成员变量中。
大家能够将这些间接了解为成员变量的初始化,和加载咱们的配置类配置即可,因为前面的操作都是围绕它初始化的webSecurity
实例和咱们加载的配置类信息来做的。
这些货色还能够拆出来一步步的来讲,然而这样的话真是一篇文章写不完,我也没有那么大的精力可能事无巨细的写进去,我只筛选这条痕迹清晰的主脉络来讲,如果大家看完能明确它的一个加载程序其实就挺好了。
就像Spring的面试题会问SpringBean的加载程序,SpringMVC则会问SpringMVC一个申请的运行过程一样。
全副弄得明明白白,必须要精研源码,在初期,咱们只有晓得它的一条主脉络,在之后的应用中,哪出了问题你能够间接去定位到可能是哪有问题,这样就曾经很好了,学习是一个循环渐进的过程。
4. ????SpringSecurityFilterChain
初始化完变量,加载完配置,咱们要开始创立过滤器链了,所以先走setFilterChainProxySecurityConfigurer
是有起因的,如果咱们不把咱们的自定义配置加载进来,创立过滤器链的时候怎么晓得哪些过滤器须要哪些过滤器不须要。
`@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();
}`
springSecurityFilterChain
办法逻辑就很简略了,如果咱们没加载自定义的配置类,它就替咱们加载一个默认的配置类,而后调用这个build
办法。
看到这相熟的办法名称,你就应该晓得这是建造者模式,不论它什么模式,既然调用了,咱们点进去就是了。
`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");
}`
build()
办法是webSecurity
的父类AbstractSecurityBuilder
中的办法,这个办法又调用了doBuild()
办法。
`@Override
protected final O doBuild() throws Exception {
synchronized (configurers) {
buildState = AbstractConfiguredSecurityBuilder.BuildState.INITIALIZING;
// 空办法
beforeInit();
// 调用init办法
init();
buildState = AbstractConfiguredSecurityBuilder.BuildState.CONFIGURING;
// 空办法
beforeConfigure();
// 调用configure办法
configure();
buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILDING;
// 调用performBuild
O result = performBuild();
buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILT;
return result;
}
}`
通过我的正文能够看到beforeInit()
和beforeConfigure()
都是空办法, 理论有用的只有init()
,configure()
和performBuild()
办法。
咱们先来看看init()
,configure()
办法。
`private void init() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.init((B) this);
}
for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
configurer.init((B) this);
}
}
private void configure() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.configure((B) this);
}
}`
源码中能够看到都是先获取到咱们的配置类信息,而后循环调用配置类本人的init()
,configure()
办法。
后面说过,咱们的配置类是继承了WebSecurityConfigurerAdapter
的子类,而WebSecurityConfigurerAdapter
又是SecurityConfigurer
的子类,所有SecurityConfigurer
的子类都须要实现init()
,configure()
办法。
所以这里的init()
,configure()
办法其实就是调用WebSecurityConfigurerAdapter
本人重写的init()
,configure()
办法。
其中WebSecurityConfigurerAdapter
中的configure()
办法是一个空办法,所以咱们只须要去看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()
办法,这外面初始化退出了很多过滤器。 - 将
HttpSecurity
放入WebSecurity
,将FilterSecurityInterceptor
放入WebSecurity
,就是咱们鉴权那章讲过的FilterSecurityInterceptor
。
那咱们次要看第一步getHttp()
办法:
`protected final HttpSecurity getHttp() throws Exception {
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);
return http;
}`
getHttp()
办法外面http
调用的那一堆办法都是一个个过滤器,第一个csrf()
很显著就是避免CSRF攻打的过滤器,上面还有很多,这就是SpringSecurity
默认会退出过滤器链的那些过滤器了。
其次,还有一个重点就是倒数第二行代码,我也加上了正文,咱们个别在咱们自定义的配置类中重写的就是这个办法,所以咱们的自定义配置就是在这里失效的。
所以在初始化的过程中,这个办法会先加载本人默认的配置而后再加载咱们重写的配置,这样两者联合起来,就变成了咱们看到的默认配置。(如果咱们不重写configure(http)
办法,它也会一点点的默认配置,大家能够去看源码,看了就明确了。)
init()
,configure()
(空办法)完结之后,就是调用performBuild()
办法。
`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;
}`
这个办法次要须要看的是调用securityFilterChainBuilder
的build()
办法,这个securityFilterChainBuilder
是咱们在init()
办法中add的那个,所以这里的securityFilterChainBuilder
其实就是HttpSecurity
,所以这里其实是调用了HttpSecurity
的bulid()
办法。
又来了,WebSecurity
的bulid()
办法还没说完,先来了一下HttpSecurity
的bulid()
办法。
HttpSecurity
的bulid()
办法过程和之前的一样,也是先init()
而后configure()
最初performBuild()
办法,值得一提的是在HttpSecurity
的performBuild()
办法外面,会对过滤器链中的过滤器进行排序:
`@Override
protected DefaultSecurityFilterChain performBuild() {
filters.sort(comparator);
return new DefaultSecurityFilterChain(requestMatcher, filters);
}`
HttpSecurity
的bulid()
办法执行完了之后将DefaultSecurityFilterChain
返回给WebSecurity
的performBuil()
办法,performBuil()
办法再将其转换为FilterChainProxy
,最初WebSecurity
的performBuil()
办法执行完结,返回一个Filter
注入成为name="springSecurityFilterChain"
的Bean
。
通过以上这些步骤之后,springSecurityFilterChain
办法执行结束,咱们的过滤器链就创立实现了,SpringSecurity
也能够跑起来了。
后记
看到这的话,其实你曾经很有耐性了,但可能还感觉云里雾里的,因为SpringSecurity
(Spring大家族)这种工程化极高的我的项目我的项目都是各种设计模式和编码思维满天飞,看不懂的时候只能说这什么玩意,看得懂的时候又该膜拜这是艺术啊。
这些货色它不容易看懂然而比拟解耦容易扩大,像一条线下来的代码就容易看懂然而不容易扩大了,福祸相依。
而且这么多名称相近的类名,各种继承形象,要好好了解下来确实没那么容易,这篇其实想给这个SpringSecurity
来个收尾,逼着本人写的,我这个人喜爱善始善终,这段货色也确实简单,接下来的几篇打算写几个实用的有意思的也轻松的放松一下。
如果你对SpringSecurity
源码有趣味能够跟着来我这个文章,点开你本人的源码点一点,看一看,加油。
自从上篇征文发了之后,感觉多了很多前端的关注者,掘金果然还是前端多啊,没事,尽管我不怎么写前端,说不定哪天改行了呢哈哈。
我也不藏着掖着,其实我当初是写后端的,我对前端呢只能说是略懂略懂,不过无聊了也能够来看看我的文章,点点赞刷刷浏览干干啥的????,说不定某一天忽然看懂了某篇文还前端劝退后端入行,加油了大家。
别辜负生命,别辜负本人。
你们的每个点赞珍藏与评论都是对我常识输入的莫大必定,如果有文中有什么谬误或者疑点或者对我的指教都能够在评论区下方留言,一起探讨。
我是耳朵,一个始终想做常识输入的伪文艺程序员,下期见。