This article has participated in the call for good writing activities, click to view: back end, big front end double track submission, 20,000 yuan prize pool waiting for you to challenge

SecurityContextPersistenceFilter

SecurityContextPersistenceFilter is the second line of defense in Springsecurity chain, is located in the WebAsyncManagerIntegrationFilter, function is designed to store SecurityContext.

SecurityContextPersistenceFilter mainly do two things:

  1. When the request comes in, the SecurityContext is retrieved from the HttpSession and stored in the SecurityContextHolder so that data is retrieved from the SecurityContextHolder during subsequent processing of the same request
  2. When a request is processed, retrieve the SecurityContext from the SecurityContextHolder and store it in the HttpSession so that it can be used in the HttpSession when the next request comes in. Also erases the login information in SecurityContextHolder.

Deposited into SpringContext HttpSession or load data into SpringContext from HttpSession object, these things are done by implementing class SecurityContextRepository.

SecurityContextRepository interface has three categories:

  • TestSecurityContextRepository: provide support for unit testing
  • NullSecurityContextRepository: do not make SpringContext storage work
  • HttpSessionSecurityContextRepository: The default use class for Springsecurity implements storing SpringContext in HttpSession and loading SecurityContext from HttpSession.

HttpSessionSecurityContextRepository

HttpSessionSecurityContextRepository definition about the request and the encapsulation of two inner classes

SaveToSessionResponseWrapper

It is an extension of HttpServletResponse,

  1. Realizing HttpServletResponseWrapper HttpServletResponse interface, it is the adornment of the HttpServletResponse class, using HttpServletResponseWrapper can convenient operation parameters and the output stream.

  2. HttpServletResponseWrapper OnCommittedResponseWrapper inheritance, its function is enhanced, When the HttpServletResponse sendError sendRedirect flushBuffer and other methods are called, the DoonResponsecomadmitted () method is called. It can save data. The onResponseCommitted OnCommittedResponseWrapper is abstract methods, the implementation class in SaveContextOnUpdateOrErrorResponseWrapper

  3. OnCommittedResponseWrapper SaveContextOnUpdateOrErrorResponseWrapper inheritance, and made the onResponseCommitted methods implementation. In SaveContextOnUpdateOrErrorResponseWrapper declare a contextSaved variables, said SecurityContext whether they have been stored successfully. When HttpServletResponse was submitted, the OnResponSecomadmitted method was called. In onResponSecomadmitted the saveContext method was called and the contextSaved was set to true to indicate that it was saved. SaveContext is abstraction method and implementation in SaveToSessionResponseWrapper.

  4. SaveToSessionResponseWrapper

  final class SaveToSessionResponseWrapper extends SaveContextOnUpdateOrErrorResponseWrapper {
      private final HttpServletRequest request;
      private final boolean httpSessionExistedAtStartOfRequest;
      private final SecurityContext contextBeforeExecution;
      private final Authentication authBeforeExecution;
  
      SaveToSessionResponseWrapper(HttpServletResponse response, HttpServletRequest request, boolean httpSessionExistedAtStartOfRequest, SecurityContext context) {
          super(response, HttpSessionSecurityContextRepository.this.disableUrlRewriting);
          this.request = request;
          this.httpSessionExistedAtStartOfRequest = httpSessionExistedAtStartOfRequest;
          this.contextBeforeExecution = context;
          this.authBeforeExecution = context.getAuthentication();
      }
  
      protected void saveContext(SecurityContext context) {
          Authentication authentication = context.getAuthentication();
          HttpSession httpSession = this.request.getSession(false);
          if(authentication ! =null && !HttpSessionSecurityContextRepository.this.trustResolver.isAnonymous(authentication)) {
              if (httpSession == null) {
                  httpSession = this.createNewSessionIfAllowed(context);
              }
  
              if(httpSession ! =null && (this.contextChanged(context) || httpSession.getAttribute(HttpSessionSecurityContextRepository.this.springSecurityContextKey) == null)) {
                  httpSession.setAttribute(HttpSessionSecurityContextRepository.this.springSecurityContextKey, context);
                  if (HttpSessionSecurityContextRepository.this.logger.isDebugEnabled()) {
                      HttpSessionSecurityContextRepository.this.logger.debug("SecurityContext '" + context + "' stored to HttpSession: '"+ httpSession); }}}else {
              if (HttpSessionSecurityContextRepository.this.logger.isDebugEnabled()) {
                  HttpSessionSecurityContextRepository.this.logger.debug("SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.");
              }
  
              if(httpSession ! =null && this.authBeforeExecution ! =null) {
                  httpSession.removeAttribute(HttpSessionSecurityContextRepository.this.springSecurityContextKey); }}}private boolean contextChanged(SecurityContext context) {
          returncontext ! =this.contextBeforeExecution || context.getAuthentication() ! =this.authBeforeExecution;
      }
  
      private HttpSession createNewSessionIfAllowed(SecurityContext context) {
          if (HttpSessionSecurityContextRepository.this.isTransientAuthentication(context.getAuthentication())) {
              return null;
          } else if (this.httpSessionExistedAtStartOfRequest) {
              if (HttpSessionSecurityContextRepository.this.logger.isDebugEnabled()) {
                  HttpSessionSecurityContextRepository.this.logger.debug("HttpSession is now null, but was not null at start of request; session was invalidated, so do not create a new session");
              }
  
              return null;
          } else if(! HttpSessionSecurityContextRepository.this.allowSessionCreation) {
              if (HttpSessionSecurityContextRepository.this.logger.isDebugEnabled()) {
                  HttpSessionSecurityContextRepository.this.logger.debug("The HttpSession is currently null, and the " + HttpSessionSecurityContextRepository.class.getSimpleName() + " is prohibited from creating an HttpSession (because the allowSessionCreation property is false) - SecurityContext thus  not stored for next request");
              }
  
              return null;
          } else if (HttpSessionSecurityContextRepository.this.contextObject.equals(context)) {
              if (HttpSessionSecurityContextRepository.this.logger.isDebugEnabled()) {
                  HttpSessionSecurityContextRepository.this.logger.debug("HttpSession is null, but SecurityContext has not changed from default empty context: ' " + context + "'; not creating HttpSession or storing SecurityContext");
              }
  
              return null;
          } else {
              if (HttpSessionSecurityContextRepository.this.logger.isDebugEnabled()) {
                  HttpSessionSecurityContextRepository.this.logger.debug("HttpSession being created as SecurityContext is non-default");
              }
  
              try {
                  return this.request.getSession(true);
              } catch (IllegalStateException var3) {
                  HttpSessionSecurityContextRepository.this.logger.warn("Failed to create a session, as response has been committed. Unable to store SecurityContext.");
                  return null; }}}}Copy the code

The saveContext method is mainly used to save the SecurityContext. If the Authentication object is null or it is an anonymous object, you do not need to save the SecurityContext. Also, if httpSession is not null and authBeforeExecution is not null, the saved logged-in user data is removed from httpSession, mainly to prevent the developer from calling the chain-dofilter method after a successful logout callback. The original login information cannot be cleared. If httpSession is null, create an httpSession object. Finally, if the SpringContext changes or is not saved in httpSession, the setAttribute method in httpSession is called to save the SpringContext.

This is HttpSessionSecurityContextRepository encapsulated SaveToSessionResponseWrapper object, is a core function in the HttpServletResponse submitted, Save the SecurityContext to HttpSession.

SaveToSessionRequestWrapper

private static class SaveToSessionRequestWrapper extends HttpServletRequestWrapper {
    private final SaveContextOnUpdateOrErrorResponseWrapper response;

    SaveToSessionRequestWrapper(HttpServletRequest request, SaveContextOnUpdateOrErrorResponseWrapper response) {
        super(request);
        this.response = response;
    }

    public AsyncContext startAsync(a) {
        this.response.disableSaveOnResponseCommitted();
        return super.startAsync();
    }

    public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException {
        this.response.disableSaveOnResponseCommitted();
        return super.startAsync(servletRequest, servletResponse); }}Copy the code

This disables the automatic saving of the SecurityContext when an asynchronous Servlet commits. Why ban it?

In asynchronous servlets, the HttpServletResponse is automatically submitted when the task is completed. During the submission process, the SecurityContext is automatically saved to the HttpSession. The saving fails because the child thread cannot obtain user information. If using asynchronous servlets, default to automatically save SecurityContext submitted to disable the HttpServletResponse, but by SecurityContextPersistenceFilter save SecurityContext.

HttpSessionSecurityContextRepository source code:

public class HttpSessionSecurityContextRepository implements SecurityContextRepository {
    public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT";
    protected final Log logger = LogFactory.getLog(this.getClass());
    private final Object contextObject = SecurityContextHolder.createEmptyContext();
    private boolean allowSessionCreation = true;
    private boolean disableUrlRewriting = false;
    private String springSecurityContextKey = "SPRING_SECURITY_CONTEXT";
    private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();

    public HttpSessionSecurityContextRepository(a) {}public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
        HttpServletRequest request = requestResponseHolder.getRequest();
        HttpServletResponse response = requestResponseHolder.getResponse();
        HttpSession httpSession = request.getSession(false);
        SecurityContext context = this.readSecurityContextFromSession(httpSession);
        if (context == null) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("No SecurityContext was available from the HttpSession: " + httpSession + ". A new one will be created.");
            }

            context = this.generateNewContext();
        }

        HttpSessionSecurityContextRepository.SaveToSessionResponseWrapper wrappedResponse = newHttpSessionSecurityContextRepository.SaveToSessionResponseWrapper(response, request, httpSession ! =null, context);
        requestResponseHolder.setResponse(wrappedResponse);
        requestResponseHolder.setRequest(new HttpSessionSecurityContextRepository.SaveToSessionRequestWrapper(request, wrappedResponse));
        return context;
    }

    public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
        SaveContextOnUpdateOrErrorResponseWrapper responseWrapper = (SaveContextOnUpdateOrErrorResponseWrapper)WebUtils.getNativeResponse(response, SaveContextOnUpdateOrErrorResponseWrapper.class);
        if (responseWrapper == null) {
            throw new IllegalStateException("Cannot invoke saveContext on response " + response + ". You must use the HttpRequestResponseHolder.response after invoking loadContext");
        } else {
            if(! responseWrapper.isContextSaved()) { responseWrapper.saveContext(context); }}}public boolean containsContext(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session == null) {
            return false;
        } else {
            return session.getAttribute(this.springSecurityContextKey) ! =null; }}private SecurityContext readSecurityContextFromSession(HttpSession httpSession) {
        boolean debug = this.logger.isDebugEnabled();
        if (httpSession == null) {
            if (debug) {
                this.logger.debug("No HttpSession currently exists");
            }

            return null;
        } else {
            Object contextFromSession = httpSession.getAttribute(this.springSecurityContextKey);
            if (contextFromSession == null) {
                if (debug) {
                    this.logger.debug("HttpSession returned null object for SPRING_SECURITY_CONTEXT");
                }

                return null;
            } else if(! (contextFromSessioninstanceof SecurityContext)) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn(this.springSecurityContextKey + " did not contain a SecurityContext but contained: '" + contextFromSession + "'; are you improperly modifying the HttpSession directly (you should always use SecurityContextHolder) or using the HttpSession attribute reserved for this class?");
                }

                return null;
            } else {
                if (debug) {
                    this.logger.debug("Obtained a valid SecurityContext from " + this.springSecurityContextKey + ": '" + contextFromSession + "'");
                }

                return(SecurityContext)contextFromSession; }}}protected SecurityContext generateNewContext(a) {
        return SecurityContextHolder.createEmptyContext();
    }

    public void setAllowSessionCreation(boolean allowSessionCreation) {
        this.allowSessionCreation = allowSessionCreation;
    }

    public void setDisableUrlRewriting(boolean disableUrlRewriting) {
        this.disableUrlRewriting = disableUrlRewriting;
    }

    public void setSpringSecurityContextKey(String springSecurityContextKey) {
        Assert.hasText(springSecurityContextKey, "springSecurityContextKey cannot be empty");
        this.springSecurityContextKey = springSecurityContextKey;
    }

    private boolean isTransientAuthentication(Authentication authentication) {
        returnAnnotationUtils.getAnnotation(authentication.getClass(), Transient.class) ! =null;
    }

    public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
        Assert.notNull(trustResolver, "trustResolver cannot be null");
        this.trustResolver = trustResolver;
    }
Copy the code
  • LoadContext: call readSecurityContextFromSession obtain SecurityContext object. If null, generateNewContext is called to generate an empty SecurityContext object and the request and response decorator classes are constructed and stored in the requestResponseHolder.
  • SaveContext: Saves the SecurityContext

SecurityContextPersistenceFilter source

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.security.web.context;

import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;

public class SecurityContextPersistenceFilter extends GenericFilterBean {
    static final String FILTER_APPLIED = "__spring_security_scpf_applied";
    private SecurityContextRepository repo;
    private boolean forceEagerSessionCreation;

    public SecurityContextPersistenceFilter(a) {
        this(new HttpSessionSecurityContextRepository());
    }

    public SecurityContextPersistenceFilter(SecurityContextRepository repo) {
        this.forceEagerSessionCreation = false;
        this.repo = repo;
    }

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        if (request.getAttribute("__spring_security_scpf_applied") != null) {
            chain.doFilter(request, response);
        } else {
            boolean debug = this.logger.isDebugEnabled();
            request.setAttribute("__spring_security_scpf_applied", Boolean.TRUE);
            if (this.forceEagerSessionCreation) {
                HttpSession session = request.getSession();
                if (debug && session.isNew()) {
                    this.logger.debug("Eagerly created session: " + session.getId());
                }
            }

            HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
            SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
            boolean var13 = false;

            try {
                var13 = true;
                SecurityContextHolder.setContext(contextBeforeChainExecution);
                chain.doFilter(holder.getRequest(), holder.getResponse());
                var13 = false;
            } finally {
                if (var13) {
                    SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
                    SecurityContextHolder.clearContext();
                    this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
                    request.removeAttribute("__spring_security_scpf_applied");
                    if (debug) {
                        this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
                    }

                }
            }

            SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
            SecurityContextHolder.clearContext();
            this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
            request.removeAttribute("__spring_security_scpf_applied");
            if (debug) {
                this.logger.debug("SecurityContextHolder now cleared, as request processing completed"); }}}public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) {
        this.forceEagerSessionCreation = forceEagerSessionCreation; }}Copy the code

The core method is the doFilter method:

  1. The FILTER_APPLIED attribute is obtained from the request, and is not allowed for NULL. Null sets the value of the applied attribute to true. Make sure the request executes this filter only once
  2. ForceEagerSessionCreation said will work in front of the filter chain execution to ensure session, comparison of investing resources, default is false
  3. Tectonic HttpRequestResponseHolder object, be it HttpServletResponse deposit.
  4. Call rebo. loadContext to load the SecurityContext
  5. The SecurityContext is put into the SecurityContextHolder so that the current user information can be retrieved via the SecurityContextHolder
  6. Call chain. The doFilter method performs the next filter chain, it is passed SaveToSessionRequestWrapper SaveToSessionResponseWrapper instance.
  7. Once the request is processed, in the finally module, get the latest SecurityContext, and then empty the data in the SecurityContextHolder. Call repo.savecontext to save the SecurityContext
  8. Remove the FILTER_APPLIED attribute

Conclusion:

Read from HttpSession SecurityContext SecurityContextPersistenceFilter first, in the SecurityContextHolder for subsequent use, When the request to leave SecurityContextPersistenceFilter, get the latest SecurityContext and deposited in the HttpSession, at the same time in the empty SecurityContextHolder login information