本系列源码分析是基于springboot 2.1.3版本, springcloud Greenwich.RELEASE 版本。 当前springsecurity 版本 5.1.4
当我们新建一个配置类加上 @EnableWebSecurity 就开始了 spring security 的安全认证流程。看这里注解干了些什么:
@Import({ WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class,
OAuth2ImportSelector.class })
@EnableGlobalAuthentication
引入了三个类和使用了另一个 @EnableGlobalAuthentication 注解,日常使用呢,就看这个 WebSecurityConfiguration 类,打开 IDEA 的 Structure视图,能看到个概览:
我们在没有这类安全验证框架时,都是在Servlet的拦截器里拦截用户请求,在spring的时代,用的就是filter,在Spring Security里面他就是用的filter chain来做安全认证的。我们这里可以找到一个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();
}
这里如果你的配置类实现了 WebSecurityConfigurerAdapter ,那么 webSecurityConfigurers list就会有你自定义的那个实现类, 重点看 webSecurity.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");
}
这是 AbstractSecurityBuilder 类里面的方法, building 这个变量默认就是false的安全实现:
private AtomicBoolean building = new AtomicBoolean();
然后执行 dobuild 方法, 这个方法的实现类是在 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;
}
}
build 流程一看就出来了 init ------>> configure ------->> performBuild ,接下去往下剖析:
private void init() throws Exception {
Collection> configurers = getConfigurers();
for (SecurityConfigurer configurer : configurers) {
configurer.init((B) this);
}
for (SecurityConfigurer configurer : configurersAddedInInitializing) {
configurer.init((B) this);
}
}
O泛型表示Filter类, B 泛型表示WebSecurity类, 这里的 configurers 就是你自己实现的那个配置类了(继承自 WebSecurityConfigurerAdapter ), 执行 init 方法,一般我们不会实现init方法,所以看的是 WebSecurityConfigurerAdapter 的 init 方法:
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);
}
});
}
来看gethttp方法:
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, Object> sharedObjects = createSharedObjects();
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
sharedObjects);
if (!disableDefaults) {
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();
ClassLoader classLoader = this.context.getClassLoader();
List defaultHttpConfigurers =
SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
http.apply(configurer);
}
}
configure(http);
return http;
}
disableDefaults 默认初始化为false, 可以看到这里对httpSecurity做默认的配置,最后调用 configure(http) 方法, 这里调用的就是我们一般都会实现的那个方法了,这样我们的自定义配置就起作用了. 再回过去看doBuild的configure()方法, 调用的就是我们自定义实现:
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
重点来看performBuild方法:
@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();
List securityFilterChains = 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;
}
这个方法,构建了 securityFilterChains 并添加了一些 filter 进去:
好,那么大致流程我们已经知道了, 那么问题又出现了, 这些 filter 是怎么加进去的呢? 其实是在httpSecurity的对象配置时候加进去的:
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();
随便点开一个方法,看其源码:
public CsrfConfigurer csrf() throws Exception {
ApplicationContext context = getContext();
return getOrApply(new CsrfConfigurer<>(context));
}
public FormLoginConfigurer formLogin() throws Exception {
return getOrApply(new FormLoginConfigurer<>());
}
发现都是getOrApply方法来实现, 该方法内部又是 apply 方法实现的, 继续看:
public > C apply(C configurer)
throws Exception {
configurer.addObjectPostProcessor(objectPostProcessor);
configurer.setBuilder((B) this);
add(configurer);
return configurer;
}
里面有个add方法, 将该 configurer 加入到 AbstractConfiguredSecurityBuilder 抽象类的 configurers 变量中去, 然后我们抽取个别 configrurer 看
public final class DefaultLoginPageConfigurer ... {
@Override
public void init(H http) throws Exception {
Function> hiddenInputs = request -> {
CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (token == null) {
return Collections.emptyMap();
}
return Collections.singletonMap(token.getParameterName(), token.getToken());
};
this.loginPageGeneratingFilter.setResolveHiddenInputs(hiddenInputs);
this.logoutPageGeneratingFilter.setResolveHiddenInputs(hiddenInputs);
http.setSharedObject(DefaultLoginPageGeneratingFilter.class,
loginPageGeneratingFilter);
}
@Override
@SuppressWarnings("unchecked")
public void configure(H http) throws Exception {
AuthenticationEntryPoint authenticationEntryPoint = null;
ExceptionHandlingConfigurer> exceptionConf = http
.getConfigurer(ExceptionHandlingConfigurer.class);
if (exceptionConf != null) {
authenticationEntryPoint = exceptionConf.getAuthenticationEntryPoint();
}
if (loginPageGeneratingFilter.isEnabled() && authenticationEntryPoint == null) {
loginPageGeneratingFilter = postProcess(loginPageGeneratingFilter);
http.addFilter(loginPageGeneratingFilter);
http.addFilter(this.logoutPageGeneratingFilter);
}
}
}
和dobuild 方法的 几个方法类似, 在这里一个会将filter加载进来, 所以说每个 configurer 其实就是filter的包装. 这一节就说这些东西,下一节将分析具体的 filter 流程.