spring-security (1) java config loading mechanism - WebSecurityConfiguration

Foreword:
  spring security provides us with a very powerful security protection mechanism, and the configuration during use is also extremely simple. When integrating with the spring boot project, it is so simple that we only need to use an annotation @EnableWebSecurity to filter the required The device is all set up, but how did it all happen? In the first article of this series of articles, let's take a look at the source code.
Environment:
  spring boot version: 1.5.4.RELEASE
 

1. @EnableWebSecurity annotation
@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;
}

In this annotation, the configuration class WebSecurityConfiguration.class is mainly introduced, and the other is the annotation @EnableGlobalAuthentication. This article mainly introduces the class WebSecurityConfiguration.class, and the next article will focus on the annotation

EnableGlobalAuthentication 2.WebSecurityConfiguration class
@Configuration
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
	private WebSecurity webSecurity;

	private Boolean debugEnabled;

	private List<SecurityConfigurer<Filter, WebSecurity>> 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 <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>}
	 * instances used to create the web configuration.
	 *
	 * @param objectPostProcessor the {@link ObjectPostProcessor} used to create a
	 * {@link WebSecurity} instance
	 * @param webSecurityConfigurers the
	 * {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to
	 * create the web configuration
	 * @throws Exception
	 */
	@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);
		}

		Collections.sort(webSecurityConfigurers, 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;
	}

.....

The most important in this class are the above two methods,
(1) setFilterChainProxySecurityConfigurer, this method has the @Autowired annotation and will be executed first. When executed, AutowiredWebSecurityConfigurersIgnoreParents will get all the classes that implement WebSecurityConfigurer through the getWebSecurityConfigurers() method of this class. If we have The class that implements
WebSecurityConfigurerAdapter by itself will get the corresponding instance by this method, and put it into websecurity after sorting.
In this method, we will also create our first more important object webSecurity.
(2) Next, the springSecurityFilterChain() method will be called. This method will determine whether we have obtained webSecurityConfigurers in the previous method. If not, an instance of WebSecurityConfigurerAdapter will be created and appended to websecurity. Then call the build method of websecurity. The actual call is the build method of the parent class AbstractSecurityBuilder of

websecurity 3. The
main inheritance relationship of the Websecurity class Websecurity class is from Websecurity->AbstractConfiguredSecurityBuilder->AbstractSecurityBuilder, where the build method called in the previous step is in 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;

The specific doBuild() logic is in the doBuild method of the subclass 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;
		}
	}
...

@SuppressWarnings("unchecked")
	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);
		}
	}
...
@SuppressWarnings("unchecked")
	private void configure() throws Exception {
		Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

		for (SecurityConfigurer<O, B> configurer : configurers) {
			configurer.configure((B) this);
		}
	}
...
protected abstract O performBuild() throws Exception;

You can see that the build process is mainly divided into three steps, init->configure->peformBuild
  • The first step init
  • In the init method, the init method of each configurator in the set SecurityConfigurer list will be called. This SecurityConfigurer list is set by calling the apply() method in the WebSecurityConfiguration class, that is, the WebSecurityConfigurerAdapter class or the one we inherit from the WebSecurityConfigurerAdapter kind.

    WebSecurityConfigurerAdapter class (the most important class, our customization is also inherited from this class to set)
    /**
    	 * 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<Class<? extends Object>, 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<HttpSecurity>()).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;
    	}
    
    ....
    	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:
    	 *
    	 * <pre>
    	 * http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
    	 * </pre>
    	 *
    	 * @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();
    	}
    ...
    

    The main three methods in this class are the above three methods. The
    *1 init method does two things. One is to call the getHttp() method to obtain an http instance, and assign the obtained instance to the securityFilterChainBuilders of WebSecurity through the web.addSecurityFilterChainBuilder method. Property, this property will be used when we execute the build, the second is to add a postBuildAction to WebSecurity, after the build is completed, take the FilterSecurityInterceptor object from http and assign it to WebSecurity.
    *2 getHttp() method, this method will append various SecurityConfigurer specific implementation classes to httpSecurity when we use the default configuration (in most cases), such as exceptionHandling() method will append an ExceptionHandlingConfigurer, sessionManagement() The method will append a SessionManagementConfigurer, and the securityContext() method will append a SecurityContextConfigurer object. The specific implementation classes of these SecurityConfigurer will eventually configure various specific filters for us. How these SecurityConfigurer classes are called, we will talk about the
    other getHttp() At the end of the method, configure(http) will be called. This method is also the most likely to be rewritten after we inherit the WebSecurityConfigurerAdapter class.
    *3 configure(HttpSecurity http) method, the default configure(HttpSecurity http) method continues to add the specific implementation class of SecurityConfigurer to the httpSecurity class, such as the authorizeRequests() method adds an ExpressionUrlAuthorizationConfigurer, and the formLogin() method adds a FormLoginConfigurer.
    The implementation class of ExpressionUrlAuthorizationConfigurer will be discussed further later, because it will create a very important object FilterSecurityInterceptor object for us. The FormLoginConfigurer object is relatively simple, but it will also provide us with a filter that is often used in the security authentication process: UsernamePasswordAuthenticationFilter.
    The above three methods are the main logic of the init method in the WebSecurityConfigurerAdapter class.
  • The second step configure
  • The configure method finally calls the configure(WebSecurity web) method of WebSecurityConfigurerAdapter, which is an empty method in the default implementation, and is often rewritten in specific applications to achieve specific requirements.
  • The third peformBuild
  • The specific implementation logic is in the WebSecurity class

    ...
    @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<SecurityFilterChain> securityFilterChains = new ArrayList<SecurityFilterChain>(
    				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;
    	}
    ...
    

    The main task in this method is to traverse the SecurityBuilder object in the securityFilterChainBuilders property and call its build method.
    This securityFilterChainBuilders property, which we mentioned earlier, is assigned to WebSecurity after obtaining http in the init method of the WebSecurityConfigurerAdapter class. So this place is to call the build method of httpSecurity.


    3. The HttpSecurity class
    HttpSecurity and WebSecurity have the same inheritance relationship, so the build of httpSecurity is also divided into three steps
    init->configure->performBuild, but in webSecurity, there is only one WebSecurityConfigurerAdapter in the SecurityConfigurer list by default, while the one in httpSecurity SecurityConfigurer list We have set a lot in the init method of WebSecurityConfigurerAdapter, then we will call the specific implementation class of SecurityConfigurer we set in turn. I read while debugging, by default springSecurity has the following
    [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. Each of the above configurations completes a piece of logic. For example, RememberMeConfigurer will add a RememberMeAuthenticationFilter instance and a RememberMeAuthenticationProvider instance to HttpSecurity for actual authentication. Because the things these classes do are relatively simple and clear, you can guess the work done by these classes from the class name. I will not explain them one by one here. The following is a simple example of the ExpressionUrlAuthorizationConfigurer class to illustrate, because this class will do it for us. Create a very important object FilterSecurityInterceptor

    ExpressionUrlAuthorizationConfigurer inheritance relationship
    ExpressionUrlAuthorizationConfigurer->AbstractInterceptUrlConfigurer->AbstractHttpConfigurer->SecurityConfigurerAdapter->SecurityConfigurer
    The corresponding init method is in the SecurityConfigurerAdapter class, which is an empty implementation and does nothing. The configure method also has a configuration method in the SecurityConfigurerAdapter class. Empty implementation, overridden in AbstractInterceptUrlConfigurer class
    @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;
    	}
    

    A FilterSecurityInterceptor is created in the configure of this class, and it can also be clearly seen that the AccessDecisionManager created by spring security for us by default is AffirmativeBased.

    5. Finally, look at performBuild, the last step of the HttpSecurity class to execute the build. This method is implemented in HttpSecurity
    @Override
    	protected DefaultSecurityFilterChain performBuild() throws Exception {
    		Collections.sort(filters, comparator);
    		return new DefaultSecurityFilterChain(requestMatcher, filters);
    	}
    

    As you can see, this class just sorts the security we append to HttpSecurity, and the sorting class used is FilterComparator to ensure that our filters are executed in the correct order. Then the filters are constructed to be returned by filterChian. In the previous WebSecurity's performBuild method, this return value will be wrapped into a FilterChainProxy and used as the return value of the WebSecurity's build method. So it is registered in springContext with the name springSecurityFilterChain (done in WebSecurityConfiguration)

    6. In the last step of WebSecurity's performBuild method, a postBuildAction.run is also executed, which is also a hook provided by spring security, which can be completed during build Then do some things. For example, in the init method of the WebSecurityConfigurerAdapter class, we use this hook to assign the FilterSecurityInterceptor to the filterSecurityInterceptor property of the webSecurity class after the construction is completed.

    Guess you like

    Origin http://43.154.161.224:23101/article/api/json?id=326119977&siteId=291194637