Preface:

After getting familiar with the use and basic operation of Spring Security, sometimes according to project requirements, we need to add our own filters to the original filter chain of Security to realize functions. We must first understand the flow of the core filter chain of Security and the respective functions of each filter. Therefore, Only then can we add the filters that belong to our project requirements before and after the characteristic filters.

I. Filter Chain diagram

After the configuration of the spring security, will be at the time of operation project, DefaultSecurityFilterChain outputs related log:

	public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters){
		logger.info("Creating filter chain: " + requestMatcher + "," + filters);
		this.requestMatcher = requestMatcher;
		this.filters = new ArrayList<Filter>(filters);
	}

Copy the code
  • Output the following Log:
[main] o.s.s.web.DefaultSecurityFilterChain     :

Creating filter chain:

org.springframework.security.web.util.matcher.AnyRequestMatcher@1,

[

  org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@184de357,

  org.springframework.security.web.context.SecurityContextPersistenceFilter@521ba38f,

  org.springframework.security.web.header.HeaderWriterFilter@77bb916f,

  org.springframework.security.web.csrf.CsrfFilter@76b305e1,

  org.springframework.security.web.authentication.logout.LogoutFilter@17c53dfb,

  org.springframework.security.web.savedrequest.RequestCacheAwareFilter@2086d469,

  org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@b1d19ff,

  org.springframework.security.web.authentication.AnonymousAuthenticationFilter@efe49ab,

  org.springframework.security.web.session.SessionManagementFilter@5a48d186,

  org.springframework.security.web.access.ExceptionTranslationFilter@273aaab7

]

Copy the code

You can also view it from Debug:

Second, filter one by one parsing

Before parsing, let’s talk about two crucial classes: OncePerRequestFilter and GenericFilterBean, which are more or less indirectly or directly inherited from the filter chain’s filters

  • OncePerRequestFilter, as the name suggests, ensures that only one filter is passed in a request, and that it does not need to be repeated.
  • GenericFilterBean is a basic implementation class of the Javax.servlet.filter interface
    • GenericFilterBean takes the configuration parameter -init-param item in the filter tag in web.xml as an attribute of the bean
    • GenericFilterBean can simply become the parent of any type of filter
    • Subclasses of GenericFilterBean can customize some of the attributes they need
    • GenericFilterBean leaves the actual filtering to its subclasses, which have to implement the doFilter method
    • GenericFilterBean does not depend on Spring’s ApplicationContext, Filters usually don’t read their ApplicationContext concept directly. Instead, they get it by accessing service Beans in the Spring root ApplicationContext. This is usually obtained by calling the getServletContext() method in filter

2.1. WebAsyncManagerIntegrationFilter

  public final class WebAsyncManagerIntegrationFilter extends OncePerRequestFilter {...@Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
      WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

      SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager
          .getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
      if (securityProcessingInterceptor == null) {
        asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY,
            newSecurityContextCallableProcessingInterceptor()); } filterChain.doFilter(request, response); }}Copy the code

From the source, we can analyze the WebAsyncManagerIntegrationFilter related functions:

  • Get the WebAsyncManager based on request encapsulation
  • Get/registered from WebAsyncManager SecurityContextCallableProcessingInterceptor

2.2. SecurityContextPersistenceFilter

	public class SecurityContextPersistenceFilter extends GenericFilterBean {...public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
				throws IOException, ServletException {
			HttpServletRequest request = (HttpServletRequest) req;
			HttpServletResponse response = (HttpServletResponse) res;

			if(request.getAttribute(FILTER_APPLIED) ! =null) {
				// ensure that filter is only applied once per request
				chain.doFilter(request, response);
				return;
			}

			final boolean debug = logger.isDebugEnabled();

			request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

			if (forceEagerSessionCreation) {
				HttpSession session = request.getSession();

				if (debug && session.isNew()) {
					logger.debug("Eagerly created session: " + session.getId());
				}
			}

			HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
					response);
			SecurityContext contextBeforeChainExecution = repo.loadContext(holder);

			try {
				SecurityContextHolder.setContext(contextBeforeChainExecution);

				chain.doFilter(holder.getRequest(), holder.getResponse());

			}
			finally {
				SecurityContext contextAfterChainExecution = SecurityContextHolder
						.getContext();
				// Crucial removal of SecurityContextHolder contents - do this before anything
				// else.
				SecurityContextHolder.clearContext();
				repo.saveContext(contextAfterChainExecution, holder.getRequest(),
						holder.getResponse());
				request.removeAttribute(FILTER_APPLIED);

				if (debug) {
					logger.debug("SecurityContextHolder now cleared, as request processing completed"); }}}... }Copy the code

From the source, we can analyze the SecurityContextPersistenceFilter related functions:

  1. First instance SecurityContextHolder – > HttpSessionSecurityContextRepository (below) is replaced by a repo. Function: It extracts information about authenticated users from the Session to improve efficiency and avoid the need to query user authentication information every time a request is made.
  2. According to the request and response to build HttpRequestResponseHolder
  3. ‘according to the load HttpRequestResponseHolder context for SecurityContext
  4. SecurityContextHolder sets the obtained SecurityContext into the Context and then proceeds to perform other filters
  5. Finally -> SecurityContextHolder takes the SecurityContext, clears it, and saves it and the request information to the repO, removing the FILTER_APPLIED property from the request

2.3. HeaderWriterFilter

public class HeaderWriterFilter extends OncePerRequestFilter {...@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		for(HeaderWriter headerWriter : headerWriters) { headerWriter.writeHeaders(request, response); } filterChain.doFilter(request, response); }}Copy the code

From the source, we can analyze HeaderWriterFilter features:

  • Add the corresponding information to the Header of the request, controlled by using security:headers inside the HTTP tag

2.4. CsrfFilter

public final class CsrfFilter extends OncePerRequestFilter {...@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
					throws ServletException, IOException {
		request.setAttribute(HttpServletResponse.class.getName(), response);

		CsrfToken csrfToken = this.tokenRepository.loadToken(request);
		final boolean missingToken = csrfToken == null;
		if (missingToken) {
			csrfToken = this.tokenRepository.generateToken(request);
			this.tokenRepository.saveToken(csrfToken, request, response);
		}
		request.setAttribute(CsrfToken.class.getName(), csrfToken);
		request.setAttribute(csrfToken.getParameterName(), csrfToken);

		if (!this.requireCsrfProtectionMatcher.matches(request)) {
			filterChain.doFilter(request, response);
			return;
		}

		String actualToken = request.getHeader(csrfToken.getHeaderName());
		if (actualToken == null) {
			actualToken = request.getParameter(csrfToken.getParameterName());
		}
		if(! csrfToken.getToken().equals(actualToken)) {if (this.logger.isDebugEnabled()) {
				this.logger.debug("Invalid CSRF token found for "
						+ UrlUtils.buildFullRequestUrl(request));
			}
			if (missingToken) {
				this.accessDeniedHandler.handle(request, response,
						new MissingCsrfTokenException(actualToken));
			}
			else {
				this.accessDeniedHandler.handle(request, response,
						new InvalidCsrfTokenException(csrfToken, actualToken));
			}
			return; } filterChain.doFilter(request, response); }... }Copy the code

From the source, we can analyze the CsrfFilter related functions:

  • CSRF, also known as cross-domain request forgery, allows an attacker to forge user requests to access trusted sites.
  • Verify that the request contains the TOKEN information of CSRF. If the request does not contain the token information, an error message is displayed. In this way, the attacking website cannot obtain the token information, and the information submitted across domains cannot pass the filter verification.

2.5. LogoutFilter

public class LogoutFilter extends GenericFilterBean {...public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		if (requiresLogout(request, response)) {
			Authentication auth = SecurityContextHolder.getContext().getAuthentication();

			if (logger.isDebugEnabled()) {
				logger.debug("Logging out user '" + auth
						+ "' and transferring to logout destination");
			}

			this.handler.logout(request, response, auth);

			logoutSuccessHandler.onLogoutSuccess(request, response, auth);

			return; } chain.doFilter(request, response); }... }Copy the code

From the source, we can analyze the LogoutFilter related functions:

  • Matches the URL, which defaults to /logout
  • If the match is successful, the user exits and the authentication information is cleared

2.6. RequestCacheAwareFilter

public class RequestCacheAwareFilter extends GenericFilterBean {...public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

		HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest(
				(HttpServletRequest) request, (HttpServletResponse) response);

		chain.doFilter(wrappedSavedRequest == null? request : wrappedSavedRequest, response); }}Copy the code

RequestCacheAwareFilter (RequestCacheAwareFilter)

  • A RequestCache is maintained internally with HttpSessionRequestCache to cache HttpServletRequest

2.7. SecurityContextHolderAwareRequestFilter

public class SecurityContextHolderAwareRequestFilter extends GenericFilterBean {...public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
				throws IOException, ServletException {
			chain.doFilter(this.requestFactory.create((HttpServletRequest) req, (HttpServletResponse) res), res); }... }Copy the code

From the source, we can analyze the SecurityContextHolderAwareRequestFilter related functions:

  • The ServletRequest has been wrapped to make the Request have a richer API

2.8. AnonymousAuthenticationFilter

public class AnonymousAuthenticationFilter extends GenericFilterBean implements InitializingBean {...public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
				throws IOException, ServletException {

			if (SecurityContextHolder.getContext().getAuthentication() == null) {
				SecurityContextHolder.getContext().setAuthentication(
						createAuthentication((HttpServletRequest) req));

				if (logger.isDebugEnabled()) {
					logger.debug("Populated SecurityContextHolder with anonymous token: '"
							+ SecurityContextHolder.getContext().getAuthentication() + "'"); }}else {
				if (logger.isDebugEnabled()) {
					logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '"
							+ SecurityContextHolder.getContext().getAuthentication() + "'"); } } chain.doFilter(req, res); }... }Copy the code

From the source, we can analyze the AnonymousAuthenticationFilter related functions:

  • When the authentication information in the SecurityContextHolder is empty, an anonymous user is created and stored in the SecurityContextHolder. Anonymous identity filter and the filter personally think that is very important, it needs to be in with more understanding, UsernamePasswordAuthenticationFilter spring security to compatible not login access, also walked a certification process, is just an anonymous identity.
  • Anonymous authentication filter, some people may think: anonymous and identity? Personal understanding of Anonymous is that Spirng Security gave an Anonymous identity even to users who did not pass authentication for the sake of the unity of the whole logic. While AnonymousAuthenticationFilter position of the filter is very scientific, It is in the common authentication filter (such as UsernamePasswordAuthenticationFilter BasicAuthenticationFilter RememberMeAuthenticationFilter), Means that only after the status filter is performed, SecurityContext still no user information, AnonymousAuthenticationFilter the filters will only be meaningful – – is based on an anonymous user identity.

2.9. SessionManagementFilter

public class SessionManagementFilter extends GenericFilterBean {...public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
			HttpServletRequest request = (HttpServletRequest) req;
			HttpServletResponse response = (HttpServletResponse) res;

			if(request.getAttribute(FILTER_APPLIED) ! =null) {
				chain.doFilter(request, response);
				return;
			}

			request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

			if(! securityContextRepository.containsContext(request)) { Authentication authentication = SecurityContextHolder.getContext()  .getAuthentication();if(authentication ! =null && !trustResolver.isAnonymous(authentication)) {
					// The user has been authenticated during the current request, so call the
					// session strategy
					try {
						sessionAuthenticationStrategy.onAuthentication(authentication,
								request, response);
					}
					catch (SessionAuthenticationException e) {
						// The session strategy can reject the authentication
						logger.debug(
								"SessionAuthenticationStrategy rejected the authentication object",
								e);
						SecurityContextHolder.clearContext();
						failureHandler.onAuthenticationFailure(request, response, e);

						return;
					}
					// Eagerly save the security context to make it available for any possible
					// re-entrant
					// requests which may occur before the current request completes.
					// SEC-1396.
					securityContextRepository.saveContext(SecurityContextHolder.getContext(),
							request, response);
				}
				else {
					// No security context or authentication present. Check for a session
					// timeout
					if(request.getRequestedSessionId() ! =null
							&& !request.isRequestedSessionIdValid()) {
						if (logger.isDebugEnabled()) {
							logger.debug("Requested session ID "
									+ request.getRequestedSessionId() + " is invalid.");
						}

						if(invalidSessionStrategy ! =null) {
							invalidSessionStrategy
									.onInvalidSessionDetected(request, response);
							return; } } } } chain.doFilter(request, response); }... }Copy the code

From the source, we can analyze the SessionManagementFilter related functions:

  • SecurityContextRepository limit the number of the same user to open multiple sessions
  • SessionAuthenticationStrategy to prevent the session – fixation protection, attack (protect the anonymous users)

2.10. ExceptionTranslationFilter

public class ExceptionTranslationFilter extends GenericFilterBean {...public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		try {
			chain.doFilter(request, response);

			logger.debug("Chain processed normally");
		}
		catch (IOException ex) {
			throw ex;
		}
		catch (Exception ex) {
			// Try to extract a SpringSecurityException from the stacktrace
			Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
			RuntimeException ase = (AuthenticationException) throwableAnalyzer
					.getFirstThrowableOfType(AuthenticationException.class, causeChain);

			if (ase == null) {
				ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
						AccessDeniedException.class, causeChain);
			}

			if(ase ! =null) {
				handleSpringSecurityException(request, response, chain, ase);
			}
			else {
				// Rethrow ServletExceptions and RuntimeExceptions as-is
				if (ex instanceof ServletException) {
					throw (ServletException) ex;
				}
				else if (ex instanceof RuntimeException) {
					throw (RuntimeException) ex;
				}

				// Wrap other Exceptions. This shouldn't actually happen
				// as we've already covered all the possibilities for doFilter
				throw newRuntimeException(ex); }}}... }Copy the code

From the source, we can analyze the ExceptionTranslationFilter related functions:

  • ExceptionTranslationFilter anomaly filter is located in the whole springSecurityFilterChain rear, used to convert the entire link in the exception
  • The effect of this filter is processing FilterSecurityInterceptor thrown exception, then redirect requests to the corresponding page, or return to the corresponding response error code

2.11. FilterSecurityInterceptor

public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {...public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		FilterInvocation fi = new FilterInvocation(request, response, chain);
		invoke(fi);
	}

	public FilterInvocationSecurityMetadataSource getSecurityMetadataSource(a) {
		return this.securityMetadataSource;
	}

	public SecurityMetadataSource obtainSecurityMetadataSource(a) {
		return this.securityMetadataSource;
	}
	public void invoke(FilterInvocation fi) throws IOException, ServletException {
			if((fi.getRequest() ! =null) && (fi.getRequest().getAttribute(FILTER_APPLIED) ! =null)
					&& observeOncePerRequest) {
				// filter already applied to this request and user wants us to observe
				// once-per-request handling, so don't re-do security checking
				fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
			}
			else {
				// first time this request being called, so perform security checking
				if(fi.getRequest() ! =null) {
					fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
				}

				InterceptorStatusToken token = super.beforeInvocation(fi);

				try {
					fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
				}
				finally {
					super.finallyInvocation(token);
				}

				super.afterInvocation(token, null); }}... }Copy the code

From the source, we can analyze the FilterSecurityInterceptor related functions:

  • The authorization information about the configured resources is obtained
  • Determines whether a user has permissions based on the information stored in the SecurityContextHolder
  • Mainly some functionality in its parent class AbstractSecurityInterceptor

2.12. UsernamePasswordAuthenticationFilter

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

			String username = obtainUsername(request);
			String password = obtainPassword(request);

			if (username == null) {
				username = "";
			}

			if (password == null) {
				password = "";
			}

			username = username.trim();

			UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
					username, password);

			// Allow subclasses to set the "details" property
			setDetails(request, authRequest);

			return this.getAuthenticationManager().authenticate(authRequest); }... }Copy the code

From the source, we can analyze the UsernamePasswordAuthenticationFilter related functions:

  • Forms authentication is the most commonly used a authentication way, one of the most intuitive business scenario is in the form allows the user to input the user name and password to log in, and that the UsernamePasswordAuthenticationFilter behind, It plays a crucial role in the entire Spring Security authentication system