Spring security filter chain analysis

Filter chain analysis

In Spring security, all functions are implemented through filters, which form a complete filter chain.

4.1 initialization process analysis

4.1.1ObjectPostProcessor
/**
 * Allows objects to be initialized. Typically, this is used to call the Aware method, initializingbean Afterpropertieset() and ensure disposablebean Destroy() is called.
 * It is an object postprocessor, that is, after an object is created successfully, if there are some additional things to be added, it can be processed through ObjectPostProcessor
 * Type parameters: <T> – Boundaries of object types supported by this ObjectPostProcessor
 */
public interface ObjectPostProcessor<T> {
    // Initializing an object may return a modified instance that should be used instead. Complete the secondary processing of the object
    <O extends T> O postProcess(O object);
}

  • AutowireBeanFactoryObjectPostProcessor: due to the extensive use of java configuration in spring security, many filters are directly new, and these directly new objects will not be automatically injected into the spring container. Spring security is intended to simplify the configuration, but it brings another problem that a large number of new objects need to be manually registered in the spring container. This is what the autowirebeanfactory objectpostprocessor object undertakes. After an object is new, it can be successfully injected into the spring container by calling the autowirebeanfactory objectpostprocessor #postprocess method, Its implementation principle is to inject a new object into the spring container by calling the AutowireCapableBeanFactory object in the spring container.
  • CompositeObjectPostProcessor: an object can have a postprocessor, or you can customize multiple object postprocessors. CompositeObjectPostProcessor is a composite object postprocessor. It maintains a List collection in which all postprocessors of an object are stored. When it is necessary to execute the postprocessor of an object, it will traverse all ObjectPostProcessor instances in the collection and call the postProcess method of the instance respectively for postprocessing of the object. In spring security, the final object postprocessor is actually CompositeObjectPostProcessor. By default, there is only one object in its collection, autowirebeanfactory ObjectPostProcessor.

In spring security, developers can flexibly configure which spring security filters are required in the project. Once the filter is selected, each filter will have a corresponding configurator called xxxconfigurer (for example, CorsConfigurer, csrfconfigurator, etc.). The filters are new in the corresponding configurator, and then processed in the postProcess method, These filters are injected into the spring container.

4.1.2SecurityFilterChain

SecurityFilterChain is the filter chain object in spring security:

/**
 * Define a filter chain that can match HttpServletRequest to determine whether it applies to the request.
 * Used to configure FilterChainProxy.
 */
public interface SecurityFilterChain {
    // Determine whether the request should be processed by the current filter chain
    boolean matches(HttpServletRequest request);

    // Returns a collection of filters in spring security. If matches returns true, then request
    // The request is processed in the collection returned in the getFilters method
    List<Filter> getFilters();

}

There is only one default implementation class of SecurityFilterChain, DefaultSecurityFilterChain:

public final class DefaultSecurityFilterChain implements SecurityFilterChain {
    private final RequestMatcher requestMatcher;

    private final List<Filter> filters;

    public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {
        logger.info(LogMessage.format("Will secure %s with %s", requestMatcher, filters));
        this.requestMatcher = requestMatcher;
        this.filters = new ArrayList<>(filters);
    }

    public RequestMatcher getRequestMatcher() {
        return this.requestMatcher;
    }

    @Override
    public List<Filter> getFilters() {
        return this.filters;
    }

    @Override
    public boolean matches(HttpServletRequest request) {
        return this.requestMatcher.matches(request);
    }
}
4.1.3SecurityBuilder

All objects to be built in Spring security can be implemented through SecurityBuilder. The default filter chain, proxy filter, AuthenticationManager, etc. can be built through SecurityBuilder.

SecurityBuilder
/**
 * The interface used to create an object. Different securitybuilders will build different objects in the future.
 * Type parameters: <O> – Concrete construction object
 */
public interface SecurityBuilder<O> {
    O build() throws Exception;
}
HttpSecurityBuilder

HttpSecurityBuilder is used to build HttpSecurity objects:

/**
 * By default, the implementation class of HttpSecurityBuilder has only one HttpSecurity, so H can be understood as HttpSecurity.
 * The final object to build is DefaultSecurityFilterChain.
 */
public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>>
        extends SecurityBuilder<DefaultSecurityFilterChain> {
    <C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C getConfigurer(Class<C> clazz);

    <C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C removeConfigurer(Class<C> clazz);

    // Set up an object that can be shared among multiple configurators
    <C> void setSharedObject(Class<C> sharedType, C object);

    <C> C getSharedObject(Class<C> sharedType);

    // Configure an authenticator
    H authenticationProvider(AuthenticationProvider authenticationProvider);

    // Configure a data source
    H userDetailsService(UserDetailsService userDetailsService) throws Exception;

    H addFilterAfter(Filter filter, Class<? extends Filter> afterFilter);

    H addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter);

    // Adding a filter must be an instance or extension of the filter provided by spring security
    H addFilter(Filter filter);
}
AbstractSecurityBuilder

AbstractSecurityBuilder implements the SecurityBuilder interface and improves the build to ensure that it is built only once.

public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
    // Ensure that the configuration class can only be built once even in a multithreaded environment
    private AtomicBoolean building = new AtomicBoolean();
    private O object;

    // Set to final, subclasses cannot be overridden
    @Override
    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");
    }

    public final O getObject() {
        if (!this.building.get()) {
            throw new IllegalStateException("This object has not been built");
        }
        return this.object;
    }

    protected abstract O doBuild() throws Exception;
}
AbstractConfiguredSecurityBuilder

Firstly, an enumeration class is declared in AbstractConfiguredSecurityBuilder to describe the different states of the construction process:

private enum BuildState {
    // Before configuration class construction
    UNBUILT(0),

    // Initializing (in this state before initialization is completed)
    INITIALIZING(1),

    // In configuration (in this state before starting the build)
    CONFIGURING(2),

    // Under construction
    BUILDING(3),

    // Build complete
    BUILT(4);

    private final int order;

    BuildState(int order) {
        this.order = order;
    }

    // Determine whether initialization is in progress
    public boolean isInitializing() {
        return INITIALIZING.order == this.order;
    }

    // Determine whether the configuration has been completed
    public boolean isConfigured() {
        return this.order >= CONFIGURING.order;
    }
}

The relevant methods of AbstractConfiguredSecurityBuilder class are as follows:

public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
        extends AbstractSecurityBuilder<O> {
    private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<>();

    public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer) throws Exception {
        configurer.addObjectPostProcessor(this.objectPostProcessor);
        configurer.setBuilder((B) this);
        add(configurer);
        return configurer;
    }

    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 (this.configurers) {
            if (this.buildState.isConfigured()) {
                throw new IllegalStateException("Cannot apply " + configurer + " to already built object");
            }
            List<SecurityConfigurer<O, B>> configs = null;

            // Judge whether the same type of configuration class is allowed to exist. By default, the common configuration class is false, so there is always only one configuration class in the collection
            if (this.allowConfigurersOfSameType) {
                configs = this.configurers.get(clazz);
            }
            configs = (configs != null) ? configs : new ArrayList<>(1);
            configs.add(configurer);
            this.configurers.put(clazz, configs);
            if (this.buildState.isInitializing()) {
                this.configurersAddedInInitializing.add(configurer);
            }
        }
    }

    public <C extends SecurityConfigurer<O, B>> List<C> getConfigurers(Class<C> clazz) {
        List<C> configs = (List<C>) this.configurers.get(clazz);
        if (configs == null) {
            return new ArrayList<>();
        }
        return new ArrayList<>(configs);
    }

    public <C extends SecurityConfigurer<O, B>> List<C> removeConfigurers(Class<C> clazz) {
        List<C> configs = (List<C>) this.configurers.remove(clazz);
        if (configs == null) {
            return new ArrayList<>();
        }
        return new ArrayList<>(configs);
    }

    public <C extends SecurityConfigurer<O, B>> C getConfigurer(Class<C> clazz) {
        List<SecurityConfigurer<O, B>> configs = this.configurers.get(clazz);
        if (configs == null) {
            return null;
        }
        Assert.state(configs.size() == 1,
                () -> "Only one configurer expected for type " + clazz + ", but got " + configs);
        return (C) configs.get(0);
    }

    public <C extends SecurityConfigurer<O, B>> C removeConfigurer(Class<C> clazz) {
        List<SecurityConfigurer<O, B>> configs = this.configurers.remove(clazz);
        if (configs == null) {
            return null;
        }
        Assert.state(configs.size() == 1,
                () -> "Only one configurer expected for type " + clazz + ", but got " + configs);
        return (C) configs.get(0);
    }

    private Collection<SecurityConfigurer<O, B>> getConfigurers() {
        List<SecurityConfigurer<O, B>> result = new ArrayList<>();
        for (List<SecurityConfigurer<O, B>> configs : this.configurers.values()) {
            result.addAll(configs);
        }
        return result;
    }
}

The core method is doBuild:

// Execute the build method while updating the build status
@Override
protected final O doBuild() throws Exception {
    synchronized (this.configurers) {
        this.buildState = BuildState.INITIALIZING;
        beforeInit();
        init();
        this.buildState = BuildState.CONFIGURING;
        beforeConfigure();
        configure();
        this.buildState = BuildState.BUILDING;
        O result = performBuild();
        this.buildState = BuildState.BUILT;
        return result;
    }
}

protected void beforeInit() throws Exception {
}

protected void beforeConfigure() throws Exception {
}

// Do the final build operation
protected abstract O performBuild() throws Exception;

// Initialization methods of all configuration classes
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 : this.configurersAddedInInitializing) {
        configurer.init((B) this);
    }
}

// Complete the configuration of all configuration classes
@SuppressWarnings("unchecked")
private void configure() throws Exception {
    Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
    for (SecurityConfigurer<O, B> configurer : configurers) {
        configurer.configure((B) this);
    }
}
ProviderManagerBuilder
// The object built is AuthenticationManager
public interface ProviderManagerBuilder<B extends ProviderManagerBuilder<B>>
        extends SecurityBuilder<AuthenticationManager> {
    B authenticationProvider(AuthenticationProvider authenticationProvider);
}
AuthenticationManagerBuilder

To build the AuthenticationManager:

public class AuthenticationManagerBuilder
        extends AbstractConfiguredSecurityBuilder<AuthenticationManager, AuthenticationManagerBuilder>
        implements ProviderManagerBuilder<AuthenticationManagerBuilder> {
    // The second parameter is true, which means that configuration classes of the same type are allowed to exist at the same time. Refer to AbstractConfiguredSecurityBuilder
    public AuthenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
        super(objectPostProcessor, true);
    }

    // Used to set a parent for an authentication manager
    public AuthenticationManagerBuilder parentAuthenticationManager(AuthenticationManager authenticationManager) {
        if (authenticationManager instanceof ProviderManager) {
            eraseCredentials(((ProviderManager) authenticationManager).isEraseCredentialsAfterAuthentication());
        }
        this.parentAuthenticationManager = authenticationManager;
        return this;
    }

    // Used to configure the memory based data source, this method will automatically create the InMemoryUserDetailsManagerConfigurer configuration class, and finally add the configuration class to the configurers variable of the parent class.
    // The inMemoryAuthentication method can be called repeatedly because the configuration classes of the same type are allowed to exist at the same time
    public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication()
            throws Exception {
        return apply(new InMemoryUserDetailsManagerConfigurer<>());
    }

    // ditto
    public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication() throws Exception {
        return apply(new JdbcUserDetailsManagerConfigurer<>());
    }

    // ditto
    public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(
            T userDetailsService) throws Exception {
        this.defaultUserDetailsService = userDetailsService;
        return apply(new DaoAuthenticationConfigurer<>(userDetailsService));
    }

    // Used to add an AuthenticationProvider object to the authenticationProviders collection. One AuthenticationManager instance contains multiple AuthenticationProvider instances,
    // Then multiple authenticationprovider instances can be added through the authenticationprovider method
    @Override
    public AuthenticationManagerBuilder authenticationProvider(AuthenticationProvider authenticationProvider) {
        this.authenticationProviders.add(authenticationProvider);
        return this;
    }

    // Perform specific construction work. The commonly used instance of AuthenticationManager is ProviderManager, so the ProviderManager object is created here,
    // And configure authenticationProviders and parentAuthenticationManager objects. After the ProviderManager object is created successfully,
    // Go to the object post processor to process it again and then return
    @Override
    protected ProviderManager performBuild() throws Exception {
        if (!isConfigured()) {
            this.logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");
            return null;
        }
        ProviderManager providerManager = new ProviderManager(this.authenticationProviders,
                this.parentAuthenticationManager);
        if (this.eraseCredentials != null) {
            providerManager.setEraseCredentialsAfterAuthentication(this.eraseCredentials);
        }
        if (this.eventPublisher != null) {
            providerManager.setAuthenticationEventPublisher(this.eventPublisher);
        }
        providerManager = postProcess(providerManager);
        return providerManager;
    }
}
HttpSecurity

The main function of HttpSecurity is to build a filter chain and reflect it on the code, that is, to build a DefaultSecurityFilterChain object.
A DefaultSecurityFilterChain object contains a path matcher and multiple spring security filters. In HttpSecurity, the configuration classes corresponding to the spring security filter are collected by collecting various xxxconfigurers and saved in the configurers variable of the parent class AbstractConfiguredSecurityBuilder. In the subsequent construction process, Then build these xxxconfigurers into specific spring security filters and add them to the filters object of HttpSecurity.

// The configuration of other filters is similar to that of form login, so I will only talk about one
// Each set of filter chain will have an AuthenticationManager object to authenticate (if authentication fails, the parent of AuthenticationManager will be called to authenticate again)
public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
		implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
	// Return a FormLoginConfigurer. Developers can continue to improve the configuration of form based on this object
	public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
		return getOrApply(new FormLoginConfigurer<>());
	}

	// Developers can configure the FormLoginConfigurer object outside in advance, and then directly pass it in for configuration, and can directly configure other filters after the method returns
	public HttpSecurity formLogin(Customizer<FormLoginConfigurer<HttpSecurity>> formLoginCustomizer) throws Exception {
		formLoginCustomizer.customize(getOrApply(new FormLoginConfigurer<>()));
		return HttpSecurity.this;
	}

	// Configure the AuthenticationProvider object used to perform authentication
	@Override
	public HttpSecurity authenticationProvider(AuthenticationProvider authenticationProvider) {
		getAuthenticationRegistry().authenticationProvider(authenticationProvider);
		return this;
	}

	// Configure UserDetailsService object
	@Override
	public HttpSecurity userDetailsService(UserDetailsService userDetailsService) throws Exception {
		getAuthenticationRegistry().userDetailsService(userDetailsService);
		return this;
	}

	private AuthenticationManagerBuilder getAuthenticationRegistry() {
		return getSharedObject(AuthenticationManagerBuilder.class);
	}

	// Triggers the construction of the AuthenticationManager object
	@Override
	protected void beforeConfigure() throws Exception {
		setSharedObject(AuthenticationManager.class, getAuthenticationRegistry().build());
	}

	@Override
	protected DefaultSecurityFilterChain performBuild() {
		this.filters.sort(OrderComparator.INSTANCE);
		List<Filter> sortedFilters = new ArrayList<>(this.filters.size());
		for (Filter filter : this.filters) {
			sortedFilters.add(((OrderedFilter) filter).filter);
		}
		return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters);
	}

	// Add a custom filter after a filter
	@Override
	public HttpSecurity addFilterAfter(Filter filter, Class<? extends Filter> afterFilter) {
		return addFilterAtOffsetOf(filter, 1, afterFilter);
	}

	// Add a custom filter before a filter
	@Override
	public HttpSecurity addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter) {
		return addFilterAtOffsetOf(filter, -1, beforeFilter);
	}

	// To add a filter to the filter chain, it must be an instance of the filter provided by the spring security framework or its extension. In fact, in the configure method of each xconfigurer,
	// The addFilter method will be called to add the constructed filter to the filters collection in HttpSecurity
	@Override
	public HttpSecurity addFilter(Filter filter) {
		Integer order = this.filterOrders.getOrder(filter.getClass());
		if (order == null) {
			throw new IllegalArgumentException("The Filter class " + filter.getClass().getName()
					+ " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
		}
		this.filters.add(new OrderedFilter(filter, order));
		return this;
	}

	// You can add a filter at a specified location. It should be noted that adding multiple filters at the same location does not overwrite the existing filters
	public HttpSecurity addFilterAt(Filter filter, Class<? extends Filter> atFilter) {
		return addFilterAtOffsetOf(filter, 0, atFilter);
	}

	// Call the getConfigurer method of the parent class to check whether there is a corresponding configuration class. If so, return directly; If not, call the apply method to add it to the configurers variable of the parent class
	private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(C configurer)
			throws Exception {
		C existingConfig = (C) getConfigurer(configurer.getClass());
		if (existingConfig != null) {
			return existingConfig;
		}
		return apply(configurer);
	}
}
WebSecurity

Compared with HttpSecurity, WebSecurity builds filters on a larger level.
An HttpSecurity can build a filter chain, that is, a DefaultSecurityFilterChain, while a project can have multiple HttpSecurity objects, which can build multiple DefaultSecurityFilterChain filter chains.
WebSecurity is responsible for rebuilding the DefaultSecurityFilterChain object (there may be multiple) built by HttpSecurity and other requests that need to be ignored into a FilterChainProxy object, and adding an HTTP firewall at the same time.

public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity>
		implements SecurityBuilder<Filter>, ApplicationContextAware {
	// Save all ignored requests, because in the actual project, for example, some static resources do not need to go through the spring security filter chain
	private final List<RequestMatcher> ignoredRequests = new ArrayList<>();

	// Used to save all HttpSecurity objects
	private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList<>();

	// Configure request firewall
	public WebSecurity httpFirewall(HttpFirewall httpFirewall) {
		this.httpFirewall = httpFirewall;
		return this;
	}

	// After each HttpSecurity object is created successfully, it is added to the securityFilterChainBuilders collection through this method
	public WebSecurity addSecurityFilterChainBuilder(
			SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
		this.securityFilterChainBuilders.add(securityFilterChainBuilder);
		return this;
	}

	@Override
	protected Filter performBuild() throws Exception {
		int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
		List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);

		for (RequestMatcher ignoredRequest : this.ignoredRequests) {
			// Only the request matcher is passed in without the corresponding filter chain, which means that these ignored requests do not have to go through the spring security filter chain in the future
			securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
		}

		// Traverse the collection and call the build method to build the DefaultSecurityFilterChain object
		for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
			securityFilterChains.add(securityFilterChainBuilder.build());
		}
		FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
		if (this.httpFirewall != null) {
			filterChainProxy.setFirewall(this.httpFirewall);
		}
		if (this.requestRejectedHandler != null) {
			filterChainProxy.setRequestRejectedHandler(this.requestRejectedHandler);
		}
		filterChainProxy.afterPropertiesSet();

		Filter result = filterChainProxy;
		this.postBuildAction.run();

		// Return the constructed FilterChainProxy object, and then embed it into the web filter (native filter chain) through the DelegatingFilterProxy provided by spring
		return result;
	}
}
4.1.4FilterChainProxy

FilterChainProxy is integrated into web filter through DelegatingFilterProxy proxy filter. DelegatingFilterProxy, as a proxy object, does not carry specific business.
Therefore, the final execution of the filter in spring security is in FilterChainProxy.
The source code of FilterChainProxy is relatively long. It is divided into sections:

// Save the filter chain
private List<SecurityFilterChain> filterChains;
// The validator after the filter chain configuration is completed is NullFilterChainValidator by default. In fact, it does not do any validation
private FilterChainValidator filterChainValidator = new NullFilterChainValidator();
// Default firewall object
private HttpFirewall firewall = new StrictHttpFirewall();

public FilterChainProxy() {
}

public FilterChainProxy(SecurityFilterChain chain) {
	this(Arrays.asList(chain));
}

public FilterChainProxy(List<SecurityFilterChain> filterChains) {
	this.filterChains = filterChains;
}

Because FilterChainProxy is essentially a filter, its core method is doFilter:

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
		throws IOException, ServletException {
	boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
	if (!clearContext) {
		doFilterInternal(request, response, chain);
		return;
	}
	try {
		request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
		doFilterInternal(request, response, chain);
	}
	catch (RequestRejectedException ex) {
		this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response, ex);
	}
	finally {
		// If the doFilter method is executed for the first time (clearContext is true), after execution,
		// In the finally code block, you need to clear the user information from the SecurityContextHolder,
		// This is mainly to prevent users from not configuring SecurityContextPersistenceFilter correctly,
		// As a result, the login user information is not correctly cleared, resulting in memory leakage
		SecurityContextHolder.clearContext();
		request.removeAttribute(FILTER_APPLIED);
	}
}

The doFilter method is equivalent to the entry of the whole spring security filter chain. Some specific filters, such as SecurityContextPersistenceFilter, are executed after this method.
In the doFilter method, the specific implementation of the filter is entrusted to the doFilterInternal method:

private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
		throws IOException, ServletException {
	// Convert the request object into a FirewalledRequest object, and the conversion process will be processed by http firewall
	FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
	// At the same time, the response is converted to HttpServletResponse
	HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
	// Get the filter chain corresponding to the current request
	List<Filter> filters = getFilters(firewallRequest);

	// If the filters are null or there are no elements in the filters, the current request does not need to go through the spring security filter chain,
	// Execute firewallrequest Reset resets the attributes in the http firewall, and then executes chain Dofilter, go back to the web filter,
	// The spring security filter chain will be skipped
	if (filters == null || filters.size() == 0) {
		firewallRequest.reset();
		chain.doFilter(firewallRequest, firewallResponse);
		return;
	}

	// Build the virtual filter chain object VirtualFilterChain and execute doFilter
	VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
	virtualFilterChain.doFilter(firewallRequest, firewallResponse);
}

// Traverse the filterChains set to determine which filter chain the current request corresponds to
private List<Filter> getFilters(HttpServletRequest request) {
	for (SecurityFilterChain chain : this.filterChains) {
		if (chain.matches(request)) {
			return chain.getFilters();
		}
	}
	return null;
}

Next, let's take a look at the specific implementation of VirtualFilterChain:

private static final class VirtualFilterChain implements FilterChain {
	// Represents the native filter chain, and the doFilter method executing it will return to the web filter
	private final FilterChain originalChain;
	// Stored Filter of this request
	private final List<Filter> additionalFilters;
	// Current request object
	private final FirewalledRequest firewalledRequest;
	// Size of filter chain
	private final int size;
	// Subscript of filter chain execution
	private int currentPosition = 0;

	// Assign a value to the corresponding variable
	private VirtualFilterChain(FirewalledRequest firewalledRequest, FilterChain chain,
			List<Filter> additionalFilters) {
		this.originalChain = chain;
		this.additionalFilters = additionalFilters;
		this.size = additionalFilters.size();
		this.firewalledRequest = firewalledRequest;
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
		// Judge whether the currently executed subscripts are equal to the size of the filter chain. If they are equal, it means that all filters in the whole filter chain have gone through one by one,
		// At this point, the attributes in the http firewall are reset first, then originalChain. is called. The dofilter method jumps out of the spring security filter,
		// Go back to the web filter; If they are not equal, the currentPosition will increase automatically, and then take a filter from the filter chain set for execution. Note,
		// When executing, the third parameter this represents the current object (i.e. VirtualFilterChain), so after each filter is executed,
		// The last chain The doFilter method will return to the current doFilter method and continue to call the next filter
		if (this.currentPosition == this.size) {
			// Deactivate path stripping as we exit the security filter chain
			this.firewalledRequest.reset();
			this.originalChain.doFilter(request, response);
			return;
		}
		this.currentPosition++;
		Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
		nextFilter.doFilter(request, response, this);
	}
}
4.1.5SecurityConfigurer

There are many implementation classes of SecurityConfigurer, mainly including the following common implementation classes:

SecurityConfigurer
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
	// Used to complete the initialization of configuration classes, such as the initialization of SecurityBuilder
	void init(B builder) throws Exception;

	// Configure the configuration class, such as SecurityBuilder
	void configure(B builder) throws Exception;
}

There are many subclasses of SecurityConfigurer, because each filter has its own corresponding xxxConfigurer. Here are some key:

SecurityConfigurerAdapter
public abstract class SecurityConfigurerAdapter<O, B extends SecurityBuilder<O>> implements SecurityConfigurer<O, B> {
    // Each configuration class is provided with a SecurityBuilder object, which can be used to build specific configuration objects in the future
    private B securityBuilder;

    private CompositeObjectPostProcessor objectPostProcessor = new CompositeObjectPostProcessor();

    @Override
	public void init(B builder) throws Exception {
	}

	@Override
	public void configure(B builder) throws Exception {
	}

    // Return the SecurityBuilder object through the and method, which is convenient for chain configuration of different configuration classes
    public B and() {
		return getBuilder();
	}

    protected final B getBuilder() {
		Assert.state(this.securityBuilder != null, "securityBuilder cannot be null");
		return this.securityBuilder;
	}

    protected <T> T postProcess(T object) {
		return (T) this.objectPostProcessor.postProcess(object);
	}

    public void addObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
		this.objectPostProcessor.addObjectPostProcessor(objectPostProcessor);
	}

    public void setBuilder(B builder) {
		this.securityBuilder = builder;
	}

    // Internal class, which is a composite object post processor
    private static final class CompositeObjectPostProcessor implements ObjectPostProcessor<Object> {
        private List<ObjectPostProcessor<?>> postProcessors = new ArrayList<>();

        public Object postProcess(Object object) {
			for (ObjectPostProcessor opp : this.postProcessors) {
				Class<?> oppClass = opp.getClass();
				Class<?> oppType = GenericTypeResolver.resolveTypeArgument(oppClass, ObjectPostProcessor.class);
				if (oppType == null || oppType.isAssignableFrom(object.getClass())) {
					object = opp.postProcess(object);
				}
			}
			return object;
		}

        // Add a new ObjectPostProcessor instance to the composite object postprocessor
        private boolean addObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
			boolean result = this.postProcessors.add(objectPostProcessor);
			this.postProcessors.sort(AnnotationAwareOrderComparator.INSTANCE);
			return result;
		}
    }
}
UserDetailsAwareConfigurer

The subclass of userdetailsaawareconfigurer is mainly responsible for configuring components related to user authentication, such as UserDetailsService. Userdetailsaawareconfigurer provides an abstract method to obtain UserDetailsService, and the specific implementation is in its subclass.

  • Abstractdaoauthenticationconfigurator: completes the configuration of DaoAuthenticationProvider.
  • UserDetailsServiceConfigurer: completes the configuration of UserDetailsService.
  • UserDetailsManagerConfigurer: use UserDetailsManager to build user objects and complete the filling of AuthenticationManagerBuilder.
  • JdbcUserDetailsManagerConfigurer: configure the JdbcUserDetailsManager and populate it into the AuthenticationManagerBuilder.
  • InMemoryUserDetailsManagerConfigurer: configure InMemoryUserDetailsManager.
  • Daoauthenticationconfigurator: completes the configuration of DaoAuthenticationProvider.
AbstractHttpConfigurer

Abstracthttpconfigurator is mainly used to add a convenient parent class to the configuration class used in HttpSecurity and extract common operations.

public abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T, B>, B extends HttpSecurityBuilder<B>>
		extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B> {
    // It means disabling a configuration. In essence, it means removing a configuration class from the configurers collection of the builder, so that the configuration class will not exist when it is built in the future,
    // Then the corresponding function does not exist
    public B disable() {
		getBuilder().removeConfigurer(getClass());
		return getBuilder();
	}

    // It means adding an object post processor to an object. Since the return value of this method is the current object, this method can be used in chain configuration
    public T withObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
		addObjectPostProcessor(objectPostProcessor);
		return (T) this;
	}
}

Abstracthttpconfigurator has many implementation classes, which are basically used to configure various filters:

Configuration class nameeffect
HttpBasicConfigurerConfigure the http basic authentication filter
LogoutConfigurerConfigure logout filter LogoutFilter
RequestCacheConfigurerConfigure request cache filter requestcachewarefilter
RememberMeConfigurerConfigure remember me login filterrembermeauthenticationfilter
ServletApiConfigurerConfigure the wrapper original request filter securitycontextholderawarerequesfilter
DefaultLoginPageConfigurerConfigure the filter DefaultLoginPageGeneratingFilter that provides the default login page and the filter DefaultLogoutPageGeneratingFilter that provides the default logout page
SessionManagementConfigurerConfigure session management filters SessionManagementFilter and ConcurrentSessionFilter
PortMapperConfigurerConfigure a shared PortMapper instance to determine the port when redirecting between HTTP and HTTPS
ExceptionHandlingConfigurerConfigure exception handling filter ExceptionTranslationFilter
HeadersConfigurerConfigure security related response header information
CsrfConfigurerConfigure CsrfFilter to prevent CSRF attack
OAuth2ClientConfigurerConfigure OAuth2 related filters OAuth2AuthorizationRequestRedirectFilter and OAuth2AuthorizationCodeGrantFilter
ImplicitGrantConfigurerConfigure the filter for OAuth2 authentication request redirection OAuth2AuthorizationRequestRedirectFilter
AnonymousConfigurerConfigure anonymous authenticationfilter
JeeConfigurerConfigure J2EE identity pre verification filter
ChannelSecurityConfigurerConfigure request protocol processing filter ChannelProcessingFilter
CorsConfigurerConfigure CorsFilter to handle cross domain filters
SecurityContextConfigurerConfigure the filter for login information storage and recovery SecurityContextPersistenceFilter
OAuth2ResourceServerConfigurerConfigure OAuth2 identity request authentication filter BearerTokenAuthenticationFilter
AbstractAuthenticationFilterConfigurerParent of authentication configuration class
FormLoginConfigurerConfigure the authentication filter UsernamePasswordAuthenticationFilter and the filter DefaultLoginPageGeneratingFilter of the default login page
OAuth2LoginConfigurerConfigure the OAuth2 authentication request redirection filter OAuth2AuthorizationRequestRedirectFilter and the processing third-party callback filter OAuth2LoginAuthenticationFilter
OpenIDLoginConfigurerConfigure OpenID authentication filter
Saml2LoginConfigurerConfigure saml2 0 authentication filters Saml2WebSsoAuthenticationFilter and Saml2WebSsoAuthenticationRequestFilter
X509ConfigurerConfigure X509 authentication filter
AbstractInterceptUrlConfigurerParent class of interceptor configuration class
UrlAuthorizationConfigurerConfigure the URL based authority authentication interceptor FilterSecurityInterceptor
ExpressionUrlAuthorizationConfigurerConfigure the URL authority authentication interceptor FilterSecurityInterceptor based on the SpEL expression
GlobalAuthenticationConfigurerAdapter

It is mainly used to configure global AuthenticationManagerBuilder. In the AuthenticationConfiguration class, the bean provided by GlobalAuthenticationConfigurerAdapter will be automatically used to configure global AuthenticationManagerBuilder.
By default, ProviderManager has a parent, which is built through the global AuthenticationManagerBuilder here.
GlobalAuthenticationConfigurerAdapter has four different subclasses:

  • InitializeAuthenticationProviderBeanManagerConfigurer: initializes the global AuthenticationProvider object.
  • Initializeauthenticationprovidermanagerconfigurator: configure the global AuthenticationProvider object. The configuration process is to find the AuthenticationProvider from the spring container and set it to the global AuthenticationManagerBuilder object.
  • Initializeuserdetailsbeanmanagerconfigurator: initializes the global UserDetailsService object.
  • InitializeUserDetailsManagerConfigurer: configure the global UserDetailsService object. The configuration process is to find the UserDetailsService from the spring container and set it to the global AuthenticationManagerBuilder object.
  • EnableGlobalAuthenticationAutowiredConfigurer: loads bean s marked by the @ EnableGlobalAuthentication annotation from the spring container.
WebSecurityConfigurer

This is an empty interface defined by WebSecurity. Only one implementation class is WebSecurityConfigurerAdapter. In most cases, developers implement custom configuration of WebSecurity by inheriting WebSecurityConfigurerAdapter.

WebSecurityConfigurerAdapter

It is convenient to create the base class of WebSecurityConfigurer instance. Developers can customize HttpSecurity and WebSecurity by overriding the methods in it.
Two AuthenticationManagerBuilder objects are declared in WebSecurityConfigurerAdapter to build the AuthenticationManager:

// Responsible for building local authentication manager
private AuthenticationManagerBuilder authenticationBuilder;

// Responsible for building the global authentication manager
private AuthenticationManagerBuilder localConfigureAuthenticationBldr;

The local AuthenticationManager is bound to each HttpSecurity object, while the global AuthenticationManager object is the parent of all local authenticationmanagers.
It should be noted that localConfigureAuthenticationBldr is not always useful. If the developer does not override the configure(AuthenticationManagerBuilder) method, the global AuthenticationManager object is provided by the getAuthenticationManager method in the AuthenticationConfiguration class. If the user overrides the configure(AuthenticationManagerBuilder) method, Then the global authentication manager is built by localConfigureAuthenticationBldr.
The initialization method of WebSecurityConfigurerAdapter class is as follows:

public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
    @Override
	public void init(WebSecurity web) throws Exception {
        // Get an HttpSecurity instance
		HttpSecurity http = getHttp();
        // Add to WebSecurity
		web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
			FilterSecurityInterceptor securityInterceptor = http.getSharedObject(FilterSecurityInterceptor.class);
			web.securityInterceptor(securityInterceptor);
		});
	}

    protected final HttpSecurity getHttp() throws Exception {
        // If it has been initialized, return directly
		if (this.http != null) {
			return this.http;
		}
        // Set the event publisher for localConfigureAuthenticationBldr
		AuthenticationEventPublisher eventPublisher = getAuthenticationEventPublisher();
		this.localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
        // Gets the global AuthenticationManager object
		AuthenticationManager authenticationManager = authenticationManager();
		this.authenticationBuilder.parentAuthenticationManager(authenticationManager);
		Map<Class<?>, Object> sharedObjects = createSharedObjects();
        // Create HttpSecurity instance
		this.http = new HttpSecurity(this.objectPostProcessor, this.authenticationBuilder, sharedObjects);
		if (!this.disableDefaults) {
            // Apply default configuration
			applyDefaultConfiguration(this.http);
			ClassLoader classLoader = this.context.getClassLoader();
			List<AbstractHttpConfigurer> defaultHttpConfigurers = SpringFactoriesLoader
					.loadFactories(AbstractHttpConfigurer.class, classLoader);
			for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
				this.http.apply(configurer);
			}
		}
        // Make extended configuration
		configure(this.http);
		return this.http;
	}

    protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests((requests) -> requests.anyRequest().authenticated());
		http.formLogin();
		http.httpBasic();
	}

    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		this.disableLocalConfigureAuthenticationBldr = true;
	}

    protected AuthenticationManager authenticationManager() throws Exception {
		if (!this.authenticationManagerInitialized) {
			configure(this.localConfigureAuthenticationBldr);
			if (this.disableLocalConfigureAuthenticationBldr) {
				this.authenticationManager = this.authenticationConfiguration.getAuthenticationManager();
			}
			else {
                // If the developer rewrites the configure(AuthenticationManagerBuilder) method,
                // The disableLocalConfigureAuthenticationBldr variable is always false
				this.authenticationManager = this.localConfigureAuthenticationBldr.build();
			}
			this.authenticationManagerInitialized = true;
		}
		return this.authenticationManager;
	}
}
4.1.6 initialization process analysis

Use spring security in spring boot. Initialization starts from the automatic configuration class of spring security:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
// Import three configuration classes
@Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,
		SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {

    // Define the default event publisher
	@Bean
	@ConditionalOnMissingBean(AuthenticationEventPublisher.class)
	public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
		return new DefaultAuthenticationEventPublisher(publisher);
	}

}
  • Springboot websecurityconfiguration: imports the default configuration of web security. If the developer specifies his own WebSecurityConfigurerAdapter or SecurityFilterChain, this will become invalid.
  • SecurityDataConfiguration: it mainly provides a SecurityEvaluationContextExtension instance to provide data query for authenticated users through spiel.
  • Websecurity enablerconfiguration: focus of analysis.
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnClass(EnableWebSecurity.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@EnableWebSecurity	// Introduce the key configuration class WebSecurityConfiguration
class WebSecurityEnablerConfiguration {

}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class,
		HttpSecurityConfiguration.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;

}
  • WebSecurityConfiguration: used to configure websecurity (focus analysis).
  • Spring webmvcimportselector: judge whether spring MVC exists in the current environment. If so, introduce relevant configurations.
  • OAuth2ImportSelector: judge whether OAuth2 exists in the current environment. If so, introduce relevant configurations.
  • HttpSecurityConfiguration: exposes the configuration of HttpSecuritybean.

In addition, there is a @ EnableGlobalAuthentication annotation to enable global configuration:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(AuthenticationConfiguration.class)	// Import configuration class AuthenticationConfiguration
@Configuration
public @interface EnableGlobalAuthentication {

}
WebSecurityConfiguration

The main purpose is to build the spring security filter chain proxy object FilterChainProxy. In WebSecurity configuration, the WebSecurity object will be built first, and then it will be used to build FilterChainProxy.
Let's first look at the attributes defined therein:

// By implementing the ImportAware interface, you can easily obtain the attribute value in @ EnableWebSecurity, such as debug
// By implementing the BeanClassLoaderAware interface, you can easily obtain the ClassLoader object
@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
    // WebSecurity object to build
    private WebSecurity webSecurity;

    private Boolean debugEnabled;

    // All configuration classes are saved, that is, WebSecurityConfigurerAdapter object. A WebSecurityConfigurerAdapter object can create an HttpSecurity,
    // Then a filter chain is constructed, and multiple filter chains can be constructed by multiple WebSecurityConfigurerAdapter objects
    private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;

    private ClassLoader beanClassLoader;

    // Object post processor. Note that this object is injected directly from the spring container
    @Autowired(required = false)
    private ObjectPostProcessor<Object> objectObjectPostProcessor;
}

setFilterChainProxySecurityConfigurer is mainly used to build a WebSecurity object and load all configuration class objects:

/**
 * Set the instance of < securityconfigurer < filterchainproxy, websecuritybuilder > used to create web configuration.
 */
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object> objectPostProcessor,
        @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
        throws Exception {
    // Go to the object post processor for processing, so that the WebSecurity object can be registered in the spring container
    this.webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
    if (this.debugEnabled != null) {
        this.webSecurity.debug(this.debugEnabled);
    }

    // Sort all configuration classes in the collection according to the @ Order annotation of each configuration class, because a configuration class corresponds to a filter chain,
    // When the request arrives, which filter chain needs to be matched first, there must be a priority problem here
    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) {
        // Add the configuration class to the configurers collection in the WebSecurity parent class. In the future, traverse the collection and call the init and configure methods of the configuration class respectively
        // Complete the initialization of the configuration class
        this.webSecurity.apply(webSecurityConfigurer);
    }
    this.webSecurityConfigurers = webSecurityConfigurers;
}

@Bean
public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
        ConfigurableListableBeanFactory beanFactory) {
    return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
}

// Get all WebSecurityConfigurer instances in the spring container, that is, various configuration classes customized by developers that inherit from WebSecurityConfigurerAdapter
public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
    List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<>();
    Map<String, WebSecurityConfigurer> beansOfType = this.beanFactory.getBeansOfType(WebSecurityConfigurer.class);
    for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
        webSecurityConfigurers.add(entry.getValue());
    }
    return webSecurityConfigurers;
}

// Create spring security filter chain
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
    boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
    boolean hasFilterChain = !this.securityFilterChains.isEmpty();

    // If there is no configuration class and no filter chain, an anonymous WebSecurityConfigurerAdapter object is created immediately and registered in the spring container
    if (!hasConfigurers && !hasFilterChain) {
        WebSecurityConfigurerAdapter adapter = this.objectObjectPostProcessor
                .postProcess(new WebSecurityConfigurerAdapter() {
                });
        this.webSecurity.apply(adapter);
    }

    // securityFilterChains is configured by importing SpringBootWebSecurityConfiguration into SecurityAutoConfiguration
    for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
        this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
        for (Filter filter : securityFilterChain.getFilters()) {
            if (filter instanceof FilterSecurityInterceptor) {
                this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
                break;
            }
        }
    }

    for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
        customizer.customize(this.webSecurity);
    }

    // Call the build method to build
    return this.webSecurity.build();
}

After the build method of WebSecurity object is executed, all configuration classes, namely WebSecurityConfigurerAdapter instances, will be built first. In the init method of WebSecurityConfigurerAdapter, the construction of HttpSecurity will be completed. In the construction process of HttpSecurity, the construction of local AuthenticationManager objects and each specific filter will be completed.

AuthenticationConfiguration

It is mainly used for global configuration and provides a global authentication manager instance.

/**
 * If the developer overrides the configure(AuthenticationManagerBuilder) method in the custom configuration class,
 * The global AuthenticationManager object here will not take effect, but will be rewritten in most cases
 */
@Configuration(proxyBeanMethods = false)
// Import the configuration class ObjectPostProcessorConfiguration, which provides a basic object postprocessor, autowirebeanfactory objectpostprocessor,
// Used to register an object into the spring container
@Import(ObjectPostProcessorConfiguration.class)
public class AuthenticationConfiguration {
	/**
	 * Define an AuthenticationManagerBuilder instance to build the global AuthenticationManager object,
	 * This process will find the AuthenticationEventPublisher instance from the spring container and set it to the AuthenticationManagerBuilder object
	 */
	@Bean
	public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor,
			ApplicationContext context) {
		LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
		AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context,
				AuthenticationEventPublisher.class);
		DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(
				objectPostProcessor, defaultPasswordEncoder);
		if (authenticationEventPublisher != null) {
			result.authenticationEventPublisher(authenticationEventPublisher);
		}
		return result;
	}

	@Bean
	public static GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer(
			ApplicationContext context) {
		return new EnableGlobalAuthenticationAutowiredConfigurer(context);
	}

	@Bean
	public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer(
			ApplicationContext context) {
		return new InitializeUserDetailsBeanManagerConfigurer(context);
	}

	@Bean
	public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer(
			ApplicationContext context) {
		return new InitializeAuthenticationProviderBeanManagerConfigurer(context);
	}

	// Build a concrete AuthenticationManager object
	public AuthenticationManager getAuthenticationManager() throws Exception {
		if (this.authenticationManagerInitialized) {
			return this.authenticationManager;
		}
		AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);

		// AuthenticationManagerDelegator is used to prevent infinite recursion when initializing AuthenticationManager
		if (this.buildingAuthenticationManager.getAndSet(true)) {
			return new AuthenticationManagerDelegator(authBuilder);
		}
		for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) {
			authBuilder.apply(config);
		}
		this.authenticationManager = authBuilder.build();
		if (this.authenticationManager == null) {
			this.authenticationManager = getAuthenticationManagerBean();
		}
		this.authenticationManagerInitialized = true;
		return this.authenticationManager;
	}
}

4.2ObjectPostProcessor usage

All filters are created by the corresponding configuration class. After successfully creating the filter, the configuration class will call the postProcess method of the parent class. In this method, it will traverse all ObjectPostProcessor objects stored in the collection maintained by the CompositeObjectPostProcessor object, and call its postProcess method to post process the object. By default, there is only one object in the collection maintained in the CompositeObjectPostProcessor object, that is, autowirebeanfactory ObjectPostProcessor. Calling its postProcess method can register the object in the spring container.
Developers can customize ObjectPostProcessor objects and add them to the collection maintained by CompositeObjectPostProcessor. At this time, when a filter is created successfully, it will be processed by two object postprocessors.
The typical usage of objectprocessor is dynamic configuration of custom objects.

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
				// After the formLogin method is called, the configuration of FormLoginConfigurer is enabled to configure the UsernamePasswordAuthenticationFilter. After the formLogin method is executed,
                // Call the withObjectPostProcessor method to process the UsernamePasswordAuthenticationFilter again
                .formLogin()
                .withObjectPostProcessor(new ObjectPostProcessor<UsernamePasswordAuthenticationFilter>() {
                    @Override
                    public <O extends UsernamePasswordAuthenticationFilter> O postProcess(O object) {
                        object.setUsernameParameter("name");
                        object.setPasswordParameter("passwd");
                        object.setAuthenticationSuccessHandler((request, response, authentication) -> {
                            response.getWriter().write("login success");
                        });

                        return object;
                    }
                })
                .and()
                .csrf().disable();
    }
}

4.3 multiple user-defined methods

There are two types of AuthenticationManager in spring security, one is global and the other is local. The local AuthenticationManager is configured by HttpSecurity, while the global AuthenticationManager can not be configured. The system will provide a global AuthenticationManager object by default, or it can be configured globally by overriding the configure(AuthenticationManagerBuilder) method.
When user authentication is performed, it will first be verified through the local AuthenticationManager object. If the authentication fails, its parent, that is, the global object, will be called for authentication again.

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	/**
	 * User defined for local AuthenticationManager. It should be noted that the system will still automatically generate global users at this time.
	 * The user object automatically provided by the system is actually an InMemoryUserDetailsManager object registered in the spring container.
	 * Since the configure(AuthenticationManagerBuilder) method is not overridden, this means that the global AuthenticationManager
	 * It is automatically generated through the AuthenticationConfiguration#getAuthenticationManager method. During the generation process,
	 * The corresponding UserDetailsService instance will be found from the spring container for configuration (the specific configuration is in InitializeUserDetailsManagerConfigurer)
	 */
	@Override
    protected void configure(HttpSecurity http) throws Exception {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("javaboy").password("{noop}123").roles("admin").build());

        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .permitAll()
                .and()
                .userDetailsService(manager)
                .csrf().disable();
    }
}

You can also configure the defined user to the global AuthenticationManager. Since the default global AuthenticationManager will find the UserDetailsService instance from the spring container during configuration, if you configure the user for the global AuthenticationManager, you only need to inject a UserDetailsService instance into the spring container.

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	@Bean
    UserDetailsService us() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("A little rain in the south of the Yangtze River").password("{noop}123").roles("admin").build());
        return manager;
    }

	@Override
    protected void configure(HttpSecurity http) throws Exception {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("javaboy").password("{noop}123").roles("admin").build());

        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .permitAll()
                .and()
                .userDetailsService(manager)
                .csrf().disable();
    }
}

Of course, instead of using the default global AuthenticationManager object provided by spring security, developers can customize the global AuthenticationManager object by overriding the configure(AuthenticationManagerBuilder) method:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("javagirl").password("{noop}123").roles("admin");
    }

	@Override
    protected void configure(HttpSecurity http) throws Exception {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("javaboy").password("{noop}123").roles("admin").build());

        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .permitAll()
                .and()
                .userDetailsService(manager)
                .csrf().disable();
    }
}

Once the configure(AuthenticationManagerBuilder) method is overridden, the global AuthenticationManager object will no longer be built through the authenticationconfiguration #getauthenticationmanager method, but through the localConfigureAuthenticationBldr variable in the websecurity configureradapter, This variable is also a parameter of the overridden configure(AuthenticationManagerBuilder) method.
It should be noted that once the configure(AuthenticationManagerBuilder) method is overridden, the users used in the global AuthenticationManager object will be subject to the users defined in the configure(AuthenticationManagerBuilder) method. At this time, if another UserDetailsService instance is injected into the spring container, the user defined in the instance will not take effect (because the AuthenticaitonConfiguration#getAuthenticationManager method has not been called).

4.4 define multiple filter chains

Multiple filter chains can exist in spring security at the same time. An instance of WebSecurityConfigurerAdapter can configure one filter chain.

@Configuration
public class SecurityConfig {
    @Bean
    UserDetailsService us() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("javaboy").password("{noop}123").roles("admin").build());
        return manager;
    }

    @Configuration
    @Order(1)
    static class SecurityConfig01 extends WebSecurityConfigurerAdapter {
        /**
         * Since only configure(HttpSecurity http) is rewritten in SecurityConfig01, the UserDetailsService(us) registered in the spring container will be used as a local
         * Authentication The user corresponding to the parent of. Therefore, if the login path is / bar/login, you can use bar/123 and javaboy/123 to log in
         * @param http
         * @throws Exception
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
            manager.createUser(User.withUsername("bar").password("{noop}123").roles("admin").build());

            http.antMatcher("/bar/**")
                    .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginProcessingUrl("/bar/login")
                    .successHandler((request, response, authentication) -> {
                        response.setContentType("application/json;charset=utf-8");
                        String s = new ObjectMapper().writeValueAsString(authentication);
                        response.getWriter().write(s);
                    })
                    .permitAll()
                    .and()
                    .csrf().disable()
                    .userDetailsService(manager);
        }
    }

    @Configuration
    @Order(2)
    static class SecurityConfig02 extends WebSecurityConfigurerAdapter {
        /**
         * Since configure(AuthenticationManagerBuilder auth) is rewritten, the user corresponding to the parent of the local AuthenticationManager is defined in this method,
         * At this time, the UserDetailsService(us) instance registered in the spring container is no longer valid for the / foo / * * filter chain. In other words, if the login path is / foo/login, you can use
         * foo/123 And javagirl/123 instead of javaboy/123
         * @param auth
         * @throws Exception
         */
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication().withUser("javagirl").password("{noop}123").roles("admin");
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
            manager.createUser(User.withUsername("foo").password("{noop}123").roles("admin").build());

            http.antMatcher("/foo/**")
                    .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginProcessingUrl("/foo/login")
                    .successHandler((request, response, authentication) -> {
                        response.setContentType("application/json;charset=utf-8");
                        String s = new ObjectMapper().writeValueAsString(authentication);
                        response.getWriter().write(s);
                    })
                    .permitAll()
                    .and()
                    .csrf().disable()
                    .userDetailsService(manager);
        }
    }
}

It should be noted that if multiple filter chains are configured, the @ Order annotation needs to be used to mark the priority of different configurations (that is, the priority of different filter chains). The higher the number, the lower the priority. When the request arrives, it will be matched from high to low according to the priority of the filter chain.

4.5 static resource filtering

WebSecurity maintains a ignoredRequests variable, which records all requests that need to be ignored. These ignored requests will no longer pass through the spring security filter.

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * The ignored addresses will eventually be added to the ignoredRequests collection and finally presented in the form of filter chain (see WebSecurity#performBuild method for details). In other words, the rewritten method contains a total of five filter chains:
     * configure(WebSecurity web)Four configured in and one configured in configure(HttpSecurity http) (i.e. / * *, all requests need authentication filtering)
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/login.html", "/css/**", "/js/**", "/images/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                .csrf().disable();
    }
}

4.6 login in JSON format

The extraction of login parameters is completed in the UsernamePasswordAuthenticationFilter filter. If you want to login in JSON format, you just need to define your own filter by imitating it, and then put the customized filter in the location of UsernamePasswordAuthenticationFilter.
First, customize a LoginFilter that inherits from UsernamePasswordAuthenticationFilter:

public class LoginFilter extends UsernamePasswordAuthenticationFilter {
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!"POST".equals(request.getMethod())) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

        // Login in JSON format
        if (MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(request.getContentType()) || MediaType.APPLICATION_JSON_UTF8_VALUE.equalsIgnoreCase(request.getContentType())) {
            Map userInfo = new HashMap<>();
            try {
                userInfo = new ObjectMapper().readValue(request.getInputStream(), Map.class);
                String username = userInfo.get(getUsernameParameter()).toString();
                String password = userInfo.get(getPasswordParameter()).toString();
                UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
                setDetails(request, authRequest);
                return this.getAuthenticationManager().authenticate(authRequest);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        // If the login is not in JSON format, call the parent method to continue authentication
        return super.attemptAuthentication(request, response);
    }
}

After the definition is completed, add it to the spring security filter chain:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("javaboy").password("{noop}123").roles("admin");
    }

	// Override the authenticationManagerBean method of the parent class to provide an AuthenticationManager instance and configure it to LoginFilter
	@Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

	@Bean
    LoginFilter loginFilter() throws Exception {
        LoginFilter loginFilter = new LoginFilter();
        loginFilter.setAuthenticationManager(authenticationManagerBean());
        loginFilter.setAuthenticationSuccessHandler((request, response, authentication) -> {
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write(new ObjectMapper().writeValueAsString(authentication));
        });

        return loginFilter;
    }

	@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                .csrf().disable();

        // Add a custom filter at the location of UsernamePasswordAuthenticationFilter for processing. If it cannot be processed,
        // Then call UsernamePasswordAuthenticationFilter through super for processing
        http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

There are two ways to obtain an AuthenticationManager instance:

  • The first is obtained by overriding the authenticationManager method of the parent class.
  • The second is obtained by overriding the authenticationManagerBean method of the parent class.

The difference between the two is that the first one obtains the global authentication manager instance, while the second one obtains the local instance. As a link in the filter chain, LoginFilter should obviously configure local instances, because if the global instance is configured to LoginFilter, the user corresponding to the local AuthenticationManager instance will become invalid.

/**
 * Unable to log in with javagirl/123 because the global AuthenticationManager is specified in LoginFilter for authentication,
 * So the local authentication manager fails
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("javaboy").password("{noop}123").roles("admin");
    }

	@Override
	@Bean
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

	@Bean
    LoginFilter loginFilter() throws Exception {
        LoginFilter loginFilter = new LoginFilter();
        loginFilter.setAuthenticationManager(authenticationManager());
        loginFilter.setAuthenticationSuccessHandler((request, response, authentication) -> {
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write(new ObjectMapper().writeValueAsString(authentication));
        });

        return loginFilter;
    }

	@Override
    protected void configure(HttpSecurity http) throws Exception {
        InMemoryUserDetailsManager users = new InMemoryUserDetailsManager();
        users.createUser(User.withUsername("javagirl").password("{noop}123").roles("admin").build());

        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                .csrf().disable()
                .userDetailsService(users);

        http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

In practical application, if you need to configure an AuthenticationManager instance yourself, in most cases, it is obtained by overriding the authenticationManagerBean method.

4.7 add login verification code

public class LoginFilter extends UsernamePasswordAuthenticationFilter {
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!"POST".equalsIgnoreCase(request.getMethod())) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

        String kaptcha = request.getParameter("kaptcha");
        String sessionKaptcha = (String) request.getSession().getAttribute("kaptcha");

        if (StringUtils.hasLength(kaptcha) && StringUtils.hasLength(sessionKaptcha) && kaptcha.equalsIgnoreCase(sessionKaptcha)) {
            return super.attemptAuthentication(request, response);
        }

        throw new AuthenticationServiceException("Verification code input error");
    }
}

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
     @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("javaboy").password("{noop}123").roles("admin");
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    LoginFilter loginFilter() throws Exception {
        LoginFilter loginFilter = new LoginFilter();
        loginFilter.setFilterProcessesUrl("/doLogin");  // Equivalent to loginprocessing URL
        loginFilter.setAuthenticationManager(authenticationManagerBean());
        loginFilter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler("/hello"));
        loginFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/mylogin.html"));
        // loginFilter.setUsernameParameter("uname");   //  If UsernamePasswordAuthenticationFilter is inherited, the relevant configuration items need to be configured on the LoginFilter instance
        // loginFilter.setPasswordParameter("passwd");
        return loginFilter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/vc.jpg").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/mylogin.html")
                // . usernameParameter("uname") / / after inheriting UsernamePasswordAuthenticationFilter, the configuration in this place is invalid
                // .passwordParameter("passwd")
                .permitAll()
                .and()
                .csrf().disable();

        http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

Keywords: Java Spring

Added by bbaker on Thu, 24 Feb 2022 08:29:24 +0200