The startup process of the Spring-Security framework

In springboot’s spring.factories configuration file, SecurityAutoConfiguration is configured as an automatically loaded configuration class through the org.springframework.boot.autoconfigure.EnableAutoConfiguration configuration item. You can see that the @Import annotation is used to import SpringBootWebSecurityConfiguration, WebSecurityEnablerConfiguration, SecurityDataConfiguration has three components.

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(DefaultAuthenticationEventPublisher. class)
@EnableConfigurationProperties(SecurityProperties. class)
@Import({<!-- --> SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,
SecurityDataConfiguration. class })
public class SecurityAutoConfiguration {<!-- -->......}

What the SpringBootWebSecurityConfiguration component does is to create a default configuration DefaultConfigurerAdapter when the Security framework is introduced (the WebSecurityConfigurerAdapter class exists) and there is no customization (the WebSecurityConfigurerAdapter type Bean does not exist).

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(WebSecurityConfigurerAdapter. class)
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnWebApplication(type = Type. SERVLET)
public class SpringBootWebSecurityConfiguration {<!-- -->

@Configuration(proxyBeanMethods = false)
@Order(SecurityProperties. BASIC_AUTH_ORDER)
static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {<!-- -->

}

}

Don’t be intimidated by so many annotations. If you look carefully, there is nothing. The core is to import WebSecurityConfiguration, SpringWebMvcImportSelector, OAuth2ImportSelector components through @EnableWebSecurity.

@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(name = BeanIds. SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@EnableWebSecurity
public class WebSecurityEnablerConfiguration {<!-- -->

}
?…
@Import({<!-- --> WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class,
OAuth2ImportSelector. class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {<!-- -->

boolean debug() default false;
}

There is a @Autowired annotation on the setFilterChainProxySecurityConfigurer() method, so it will be called, and objectPostProcessor (AutowireBeanFactoryObjectPostProcessor) and webSecurityConfigurers (the implementation class Bean of the SecurityConfigurer interface, which is the extended subclass of WebSecurityConfigurerAdapter in our business, will be injected into the method ).

// WebSecurityConfiguration

private WebSecurity webSecurity;
private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;
@Autowired(required = false)
private ObjectPostProcessor<Object> objectObjectPostProcessor;

    @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);
}

?…
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {<!-- -->
webSecurity.apply(webSecurityConfigurer);
}
this. webSecurityConfigurers = webSecurityConfigurers;
}

The AutowireBeanFactoryObjectPostProcessor#postProcess() method will call the Bean’s initialization method to fill in the properties, that is, instantiate the WebSecurity.

 public <T> T postProcess(T object) {<!-- -->
if (object == null) {<!-- -->
return null;
}
T result = null;
try {<!-- -->
result = (T) this.autowireBeanFactory.initializeBean(object,
object.toString());
}
catch (RuntimeException e) {<!-- -->
Class<?> type = object. getClass();
throw new RuntimeException(
"Could not postProcess " + object + " of type " + type, e);
}
this.autowireBeanFactory.autowireBean(object);
if (result instanceof DisposableBean) {<!-- -->
this.disposableBeans.add((DisposableBean) result);
}
if (result instanceof SmartInitializingSingleton) {<!-- -->
this.smartSingletons.add((SmartInitializingSingleton) result);
}
return result;
}

Then save the configuration to configurers through WebSecurity’s AbstractConfiguredSecurityBuilder#apply() method (private final LinkedHashMap>, List>> configurers = new LinkedHashMap< >();), the key of configurers is SecurityConfigurer or a subclass, and the value is the corresponding implementation class object, which is to save the extended subclass of WebSecurityConfigurerAdapter implemented in our business here.

 public <C extends SecurityConfigurer<O, B>> C apply(C configurer) throws Exception {<!-- -->
add(configurer);
return configurer;
}
private <C extends SecurityConfigurer<O, B>> void add(C configurer) {<!-- -->
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");
}
            //allowConfigurersOfSameType is false by default
List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers
.get(clazz) : null;
if (configs == null) {<!-- -->
configs = new ArrayList<>(1);
}
configs. add(configurer);
this.configurers.put(clazz, configs);
if (buildState.isInitializing()) {<!-- -->
this.configurersAddedInInitializing.add(configurer);
}
}
}

So far, spring has saved the WebSecurityConfigurerAdapter extension we defined, and then builds the object through WebSecurity’s parent class AbstractSecurityBuilder#build().

 @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {<!-- -->
boolean hasConfigurers = webSecurityConfigurers != null
& amp; & amp; !webSecurityConfigurers.isEmpty();
if (!hasConfigurers) {<!-- -->
WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
.postProcess(new WebSecurityConfigurerAdapter() {<!-- -->
});
webSecurity.apply(adapter);
}
return webSecurity. build();
}

The core is in the AbstractConfiguredSecurityBuilder#doBuild() method, see the process of the doBuild() method.

 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;
}
}
  1. Configure AuthenticationManagerBuilder protected void configure(AuthenticationManagerBuilder auth), instantiate HttpSecurity, and call the hook function protected void configure(HttpSecurity http) to customize HttpSecurity, in our business code The two configs are called here.
 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);
});
}
    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);
return http;
}
  1. Instantiate FilterChainProxy, which encapsulates the filter chain
 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<SecurityFilterChain> 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("\
\
"
+ "************************************************ *********************\
"
+ "********** Security debugging is enabled. ***************\
"
+ "********** This may include sensitive information. ***************\
"
+ "********** Do not use in a production system! ***************\
"
+ "************************************************ *********************\
\
");
result = new DebugFilter(filterChainProxy);
}
postBuildAction. run();
return result;
}

Finally, the springSecurityFilterChain() method returns the FilterChainProxy object. Continue to look at the DelegatingFilterProxyRegistrationBean component created through the Bean method in the SecurityFilterAutoConfiguration configuration class.

 @Bean
@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
SecurityProperties securityProperties) {<!-- -->
DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
DEFAULT_FILTER_NAME);
registration.setOrder(securityProperties.getFilter().getOrder());
registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
return registration;
}

RegistrationBean, the parent class of DelegatingFilterProxyRegistrationBean, implements the onStartup() method of the ServletContextInitializer interface.

// Registration Bea
public final void onStartup(ServletContext servletContext) throws ServletException {<!-- -->
String description = getDescription();
if (!isEnabled()) {<!-- -->
logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
return;
}
register(description, servletContext);
}

First call the AbstractFilterRegistrationBean#getDescription() method of the parent class of DelegatingFilterProxyRegistrationBean, which is to inject the targetBeanName into the DelegatingFilterProxy. The default value of this.targetBeanName is “springSecurityFilterChain”, which is the name of the FilterChainProxy object created by calling the springSecurityFilterChain() method.

// AbstractFilterRegistrationBean
protected String getDescription() {<!-- -->
Filter filter = getFilter();
Assert.notNull(filter, "Filter must not be null");
return "filter " + getOrDeduceName(filter);
}
// DelegatingFilterProxyRegistrationBean
    public DelegatingFilterProxy getFilter() {<!-- -->
//targetBeanName="springSecurityFilterChain"
return new DelegatingFilterProxy(this. targetBeanName, getWebApplicationContext()) {<!-- -->

@Override
protected void initFilterBean() throws ServletException {<!-- -->
// Don't initialize filter bean on init()
}

};
}

Going back to the register() method call in the onStartup() method, it is actually calling the AbstractFilterRegistrationBean#addRegistration() method to register the filter to the servlet context, that is, to add the DelegatingFilterProxy filter to the tomcat container.

// DynamicRegistrationBean.java
protected final void register(String description, ServletContext servletContext) {<!-- -->
D registration = addRegistration(description, servletContext);
if (registration == null) {<!-- -->
logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
return;
}
configure(registration);
}
// AbstractFilterRegistrationBean.java
protected Dynamic addRegistration(String description, ServletContext servletContext) {<!-- -->
Filter filter = getFilter();
return servletContext.addFilter(getOrDeduceName(filter), filter);
}

To sum up, spring imports the WebSecurityConfigurerAdapter extension through automatic configuration, then instantiates HttpSecurity, uses the extension to configure HttpSecurity, and injects it into the DelegatingFilterProxy after the configuration is completed, and adds it to the tomcat container by the DelegatingFilterProxyRegistrationBean, so that the filter DelegatingFilterProxy can be passed Find FilterChainProxy to complete the call of the filter. If there is something wrong, please point it out, everyone is welcome to discuss and communicate together to make progress together.