Explanation of Spring Security login process

  • 2021-12-09 09:01:39
  • OfStack

In Spring Security, authentication and authorization are achieved through filters.

When starting login, there is a key filter UsernamePasswordAuthenticationFilter, which inherits the abstract class AbstractAuthenticationProcessingFilter, and there is an doFilter method in AbstractAuthenticationProcessingFilter.


private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
      throws IOException, ServletException {
   if (!requiresAuthentication(request, response)) {
      chain.doFilter(request, response);
      return;
   }
   try {
      Authentication authenticationResult = attemptAuthentication(request, response);
      if (authenticationResult == null) {
         // return immediately as subclass has indicated that it hasn't completed
         return;
      }
      this.sessionStrategy.onAuthentication(authenticationResult, request, response);
      // Authentication success
      if (this.continueChainBeforeSuccessfulAuthentication) {
         chain.doFilter(request, response);
      }
      successfulAuthentication(request, response, chain, authenticationResult);
   }
   catch (InternalAuthenticationServiceException failed) {
      this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
      unsuccessfulAuthentication(request, response, failed);
   }
   catch (AuthenticationException ex) {
      // Authentication failed
      unsuccessfulAuthentication(request, response, ex);
   }
}

First, requiresAuthentication judges whether to try verification, and then calls attemptAuthentication method, which is attemptAuthentication method in UsernamePasswordAuthenticationFilter.


public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
      throws AuthenticationException {
   if (this.postOnly && !request.getMethod().equals("POST")) {
      throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
   }
   String username = obtainUsername(request);
   username = (username != null) ? username : "";
   username = username.trim();
   String password = obtainPassword(request);
   password = (password != null) ? password : "";
   UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
   // Allow subclasses to set the "details" property
   setDetails(request, authRequest);
   return this.getAuthenticationManager().authenticate(authRequest);
}

1. In the attemptAuthentication method of UsernamePasswordAuthenticationFilter, first verify the type of request, whether it is an POST request, and if not, throw an exception. (PS: You must use the POST method for login.)
2. Then get username and password. The obtainUsername method is used here, that is, the get method.


@Nullable
protected String obtainPassword(HttpServletRequest request) {
   return request.getParameter(this.passwordParameter);
}

@Nullable
protected String obtainUsername(HttpServletRequest request) {
   return request.getParameter(this.usernameParameter);
}

From this, we know that the parameters are obtained by get method in Spring Security, so JSON data cannot be accepted when separating the front and back ends. The processing method is to customize an Filter to inherit UsernamePasswordAuthenticationFilter, rewrite attemptAuthentication method, then create an Filter instance to write the logical processing of login success and failure, and replace the official filter provided by Spring Security by addFilterAt in configure of HttpSecurity parameters.
3. Create an UsernamePasswordAuthenticationToken instance.
4. Set Details, where the key is to record the user's remoteAddress and sessionId in the WebAuthenticationDetails class.


public WebAuthenticationDetails(HttpServletRequest request) {
   this.remoteAddress = request.getRemoteAddr();
   HttpSession session = request.getSession(false);
   this.sessionId = (session != null) ? session.getId() : null;
}

5. Get an AuthenticationManager and verify it by authenticate method. Here, take the implementation class ProviderManager as an example.


@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
   // Get Authentication Runtime class of 
   Class<? extends Authentication> toTest = authentication.getClass();
   AuthenticationException lastException = null;
   AuthenticationException parentException = null;
   Authentication result = null;
   Authentication parentResult = null;
   int currentPosition = 0;
   int size = this.providers.size();
   
   for (AuthenticationProvider provider : getProviders()) {
       // Determines whether processing the class's provider
      if (!provider.supports(toTest)) {
         continue;
      }
      if (logger.isTraceEnabled()) {
         logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
               provider.getClass().getSimpleName(), ++currentPosition, size));
      }
      try {
          // Getting user information 
         result = provider.authenticate(authentication);
         if (result != null) {
            copyDetails(authentication, result);
            break;
         }
      }
      catch (AccountStatusException | InternalAuthenticationServiceException ex) {
         prepareException(ex, authentication);
         // SEC-546: Avoid polling additional providers if auth failure is due to
         // invalid account status
         throw ex;
      }
      catch (AuthenticationException ex) {
         lastException = ex;
      }
   }
   // If not, jump out of the loop and execute again 
   if (result == null && this.parent != null) {
      // Allow the parent to try.
      try {
         parentResult = this.parent.authenticate(authentication);
         result = parentResult;
      }
      catch (ProviderNotFoundException ex) {
         // ignore as we will throw below if no other exception occurred prior to
         // calling parent and the parent
         // may throw ProviderNotFound even though a provider in the child already
         // handled the request
      }
      catch (AuthenticationException ex) {
         parentException = ex;
         lastException = ex;
      }
   }
   if (result != null) {
       // Erase user's credentials   That is, the password 
      if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
         // Authentication is complete. Remove credentials and other secret data
         // from authentication
         ((CredentialsContainer) result).eraseCredentials();
      }
      // If the parent AuthenticationManager was attempted and successful then it
      // will publish an AuthenticationSuccessEvent
      // This check prevents a duplicate AuthenticationSuccessEvent if the parent
      // AuthenticationManager already published it
      if (parentResult == null) {
          // Publicity login is successful 
         this.eventPublisher.publishAuthenticationSuccess(result);
      }

      return result;
   }

   // Parent was null, or didn't authenticate (or throw an exception).
   if (lastException == null) {
      lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
            new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
   }
   // If the parent AuthenticationManager was attempted and failed then it will
   // publish an AbstractAuthenticationFailureEvent
   // This check prevents a duplicate AbstractAuthenticationFailureEvent if the
   // parent AuthenticationManager already published it
   if (parentException == null) {
      prepareException(lastException, authentication);
   }
   throw lastException;
}

6. After 1 series of verification, the login verification is basically completed at this time. After the verification is passed, successfulAuthentication method in doFilter will be executed and jump to the login success interface set by us. If the verification fails, unsuccessfulAuthentication method will be executed and jump to the login failure interface set by us.


Related articles: