Spring Security login authentication source code analysis

Posted by vishakh369 on Wed, 05 Jan 2022 01:23:51 +0100

Security implements authentication and authorization based on filter chain. It supports different authentication mechanisms. Here we use user name and password authentication mechanism.

Spring Security provides the following built-in mechanisms to read user names and passwords from:

  • Form Login
  • Basic Authentication
  • Digest Authentication

Username and password storage mechanism:

  • Simple storage with in memory authentication
  • Relational database with JDBC authentication
  • Customizing the data store using UserDetailsService
  • LDAP store with LDAP authentication

1, Certification flow chart

Figure from network

Analyze the certification process:

  1. When users submit their user name and password, UsernamePasswordAuthenticationFilter creates a UsernamePasswordAuthenticationToken, which is a type of Authentication by extracting HttpServletRequest from the user name and password.
  2. The filter submits the UsernamePasswordAuthenticationToken to the authentication manager for authentication
  3. If authentication fails, the SecurityContextHolder will be cleared. RememberMeServices.loginFail is called. If you remember that I have no configuration, this is an empty operation. Finally, AuthenticationFailureHandler is called.
  4. If the Authentication is successful, the Authentication manager returns an Authentication instance filled with information (including the permission information, identity information and details mentioned above, but the password is usually removed). The Authentication is set in the SecurityContextHolder. RememberMeServices.loginSuccess is called. If you remember that I have no configuration, this is an empty operation. ApplicationEventPublisher publishes an InteractiveAuthenticationSuccessEvent. Called in AuthenticationSuccessHandler.

2, Source code analysis

1. View UsernamePasswordAuthenticationFilter filter

UsernamePasswordAuthenticationFilter is mainly used for authentication. The default matching URL is / login and must be a POST request.

Seeing the source code, we should understand why the default is post request, the url is / login, and the parameter name of user name and password is it.

In the attemptAuthentication method, two main things are done:

  1. The filled user name and password are encapsulated in the UsernamePasswordAuthenticationToken
  2. Call the AuthenticationManager object to implement authentication

1.1 the user name and password are encapsulated in the UsernamePasswordAuthenticationToken

The filled user name and password are encapsulated in the UsernamePasswordAuthenticationToken.

1.2 calling the AuthenticationManager object to realize authentication


From the source code, the real authentication logic is the authenticate method on the AuthenticationManager interface. Next, look at the implementation class of AuthenticationManager and the authenticate method of ProviderManager class.

2. View ProviderManager class

The source code is as follows:

	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		// Gets the Authentication type passed in
		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();
		// Circular AuthenticationProvider object. Spring Security encapsulates an AuthenticationProvider object for each authentication, such as third-party login, username and password login, etc.
		for (AuthenticationProvider provider : getProviders()) {
			// 1. Judge whether the current authentication method is supported
			if (!provider.supports(toTest)) {
				continue;
			}
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
						provider.getClass().getSimpleName(), ++currentPosition, size));
			}
			try {
				// 2. Call to find the supported AuthenticationProvider object for authentication logic
				result = provider.authenticate(authentication);
				if (result != null) {
					// 3. Execute the copy logic of authentication details
					copyDetails(authentication, result);
					break;
				}
			} catch (AccountStatusException | InternalAuthenticationServiceException ex) {
				// 4. If an AccountStatusException or InternalAuthenticationServiceException occurs, the exception event will be published through the Spring event publisher AuthenticationEventPublisher.
				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 (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) {
			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) {
				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;
	}

2.1 viewing AbstractUserDetailsAuthenticationProvider class

AbstractUserDetailsAuthenticationProvider is an AuthenticationProvider object that authenticates a form user name password login.
The source code is as follows:

    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
            return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
        });
        //1. Get the user name, which is encapsulated in UsernamePasswordAuthenticationToken
        String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
        boolean cacheWasUsed = true;        
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;

            try {
                //2. Obtain user information. Our own user implements the UserDetails interface
                user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            } catch (UsernameNotFoundException var6) {
                this.logger.debug("User '" + username + "' not found");
                if (this.hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                }

                throw var6;
            }

            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }

        try {
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        } catch (AuthenticationException var7) {
            if (!cacheWasUsed) {
                throw var7;
            }

            cacheWasUsed = false;
            user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            //3. Account status and password verification
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        }

        this.postAuthenticationChecks.check(user);
        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;
        if (this.forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }
		//4. Encapsulate the UsernamePasswordAuthenticationToken again and return Authentication
        return this.createSuccessAuthentication(principalToReturn, authentication, user);
    }

2.1.1 get user object (UserDetails)

View the retrieveUser method.

  • The UserDetails object is an authenticated user object within Spring Security. Our customized users can implement the UserDetails interface.
  • Our SysUserService inherits the UserDetailsService class and reloads the loadUserByUsername method.

3.1.2 account status and password verification

1) Account status verification

2) Encryption verification
Use PasswordEncoder password parser to encrypt and parse the password.


BCryptPasswordEncoder is the most commonly used password parser in spring security.
It uses the BCrypt algorithm. The feature is that encryption uses dynamic salt sault, but decryption does not require salt. Because salt is in the ciphertext. In this way, different ciphertext can be encrypted for the same string by adding different salt each time.

Ciphertext such as: $2a 10 10 10lLi6A4xdn7lCWYgU5yIXoek9DeYC91mTf6d9nO4UUyB/Bv4QXq6.i

Where: $is a delimiter and meaningless; 2a is the bcrypt encryption version number; 10 is the value of cost; Then the first 22 digits are salt values; Then the string is the ciphertext of the password.

2.1.3 encapsulate UsernamePasswordAuthenticationToken object

View the createSuccessAuthentication method.

This time, the UsernamePasswordAuthenticationToken object is encapsulated. See its construction method for details.

3. View UsernamePasswordAuthenticationToken class

The source code is as follows:

public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
    private static final long serialVersionUID = 510L;
    private final Object principal;
    private Object credentials;
    //Before the authentication is successful, the two parameter is called.
    public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
        super((Collection)null);
        this.principal = principal;
        this.credentials = credentials;
        this.setAuthenticated(false);
    }
    //After the authentication is successful, it calls the three parameter.
    public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
                                               Collection<? extends GrantedAuthority> authorities) {
        //Focus on the parent class
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true); // Mark certified
    }
}

3.1 view super(authorities)

View the construction method of the parent class AbstractAuthenticationToken. The source code is as follows:

public abstract class AbstractAuthenticationToken implements Authentication,
        CredentialsContainer {
    private final Collection<GrantedAuthority> authorities; // User's permission information collection
    private Object details;
    private boolean authenticated = false;
            
    public AbstractAuthenticationToken(Collection<? extends GrantedAuthority> authorities) {
		
        if (authorities == null) {//These are two parameters
            this.authorities = AuthorityUtils.NO_AUTHORITIES;
        } else {//Three parameters, look here
            Iterator var2 = authorities.iterator();
			//1. Add user permission information
            GrantedAuthority a;
            do {
                if (!var2.hasNext()) {
                    ArrayList<GrantedAuthority> temp = new ArrayList(authorities.size());
                    // Add permission, GrantedAuthority type
                    temp.addAll(authorities);
                    this.authorities = Collections.unmodifiableList(temp);
                    return;
                }
                a = (GrantedAuthority)var2.next();
            } while(a != null);
			//2. If there is no permission information, an exception will be thrown
            throw new IllegalArgumentException("Authorities collection cannot contain any null elements");
        }
    }
}

The main purpose here is to add user permission information, because our role class implements the GrantedAuthority interface. To obtain user information, role information has also been put into user information, so it is not difficult to understand here.
Therefore, permission information (of type GrantedAuthority) must be placed in the UserDetails object returned by our custom authentication business logic.

Now, the user information in the authentication process is encapsulated and authenticated. Next, let's look at the logic after successful authentication.

4. View doFilter method

Back to the original UsernamePasswordAuthenticationFilter filter, we didn't find the doFilter method, this class didn't, so go to the parent class!
View the doFilter method of the parent class AbstractAuthenticationProcessingFilter. The source code is as follows:

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        //1. Determine whether certification is required
        if (!this.requiresAuthentication(request, response)) {
            chain.doFilter(request, response);
        } else {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Request is to process authentication");
            }

            Authentication authResult;
            try {
                //2. Call subclass methods to obtain Authentication information and encapsulate it in Authentication
                authResult = this.attemptAuthentication(request, response);
                if (authResult == null) {
                    return;
                }

                // 3. Session policy processing
                this.sessionStrategy.onAuthentication(authResult, request, response);
            } catch (InternalAuthenticationServiceException var8) {
                this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
                this.unsuccessfulAuthentication(request, response, var8);
                return;
            } catch (AuthenticationException var9) {
                //5. Handling of authentication failure
                this.unsuccessfulAuthentication(request, response, var9);
                return;
            }

            if (this.continueChainBeforeSuccessfulAuthentication) {
                chain.doFilter(request, response);
            }

            // 4. Successful authentication processing
            this.successfulAuthentication(request, response, chain, authResult);
        }
    }

Call subclass methods to obtain authentication information, which we have analyzed above.

4.1 viewing successful authentication methods

    //Successful authentication, call successfulAuthentication
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Authentication success. Updating SecurityContextHolder tocontain: " + authResult);
        }
		//1. Authentication succeeded. Store the authentication information in SecurityContext!
        SecurityContextHolder.getContext().setAuthentication(authResult);
		//2. Successfully log in and call rememberMeServices
        this.rememberMeServices.loginSuccess(request, response, authResult);
        if (this.eventPublisher != null) {
            this.eventPublisher.publishEvent(new
                    InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
        }
        this.successHandler.onAuthenticationSuccess(request, response, authResult);
    }

4.2 viewing the unsuccessfulAuthentication method

	//Authentication failed, call unsuccessfulAuthentication
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        SecurityContextHolder.clearContext();
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Authentication request failed: " + failed.toString(), failed);
            this.logger.debug("Updated SecurityContextHolder to contain null Authentication");
            this.logger.debug("Delegating to authentication failure handler " +
                    this.failureHandler);
        }
        this.rememberMeServices.loginFail(request, response);
        this.failureHandler.onAuthenticationFailure(request, response, failed);
    }

The above is the general process of the certification process.

- -- if you are hungry for knowledge, if you are foolish with an open mind.