Step by step snow spring security Part 4, what is the login process like? Where is the login user information saved?

Presumably many people want to know after contacting spring security, what is the login process like?

Today, the hanging arc takes everyone to strip the coat of the source code of the login process, and analyze the login process of spring security through the bare source code

review

We are in the third chapter of "Learning spring security step by step, how to customize the login page?" Login Callback " explored the custom login page, and found that the submitted default login user name and password parameters are hard-coded in the code class UsernamePasswordAuthenticationFilter, and can also be modified through code configuration. Since the username and password submitted by login are defined in the UsernamePasswordAuthenticationFilter class, the corresponding login verification must also be verified here

Explore the login process

Explore the attemptAuthentication verification method of UsernamePasswordAuthenticationFilter

The UsernamePasswordAuthenticationFilter class is a filter. There are not many codes and it is not difficult to find out. attemptAuthentication(HttpServletRequest request, HttpServletResponse response) is the interface for verifying login. In the third chapter of "Learning spring security step by step, how to customize the login page?" On the basis of the "Login Callback " project, make a breakpoint, go through the login process, and find that it is true and guessed. It is not difficult
insert image description here
to find

  • First verify whether the login interface is a POST request. If not, throw an exception directly. Of course, this is the default. If you want to support get requests, you need to rewrite the filter UsernamePasswordAuthenticationFilter and set the postOnly attribute to FALSE
  • The username/password in the request is extracted through the obtainUsername and obtainPassword methods. The extraction method is request.getParameter, which is why the default form login in Spring Security needs to pass parameters in the form of key/value instead of JSON parameters. If Like passing JSON parameters, modify the logic here
    insert image description here
  • Use the username and password submitted by login to construct a UsernamePasswordAuthenticationToken object, and the constructed object isAuthenticated() will return false.
    insert image description here
  • Set client information, such as client IP, sessionId, etc.
    insert image description here
    insert image description here

insert image description here

  • Verify user login through this.getAuthenticationManager().authenticate(authRequest), verify user name, password, user status, etc. If all are OK, the login is successful, otherwise the login fails. The specific process is as follows

Explore AuthenticationManager#authenticate(authRequest) authentication process

In the previous attemptAuthentication method, the last step of this method starts to verify. First, we need to obtain an AuthenticationManager, which is ProviderManager, so we will enter the authenticate method of ProviderManager. This method is relatively long. I Post the key points for analysis

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    
    
		Class<? extends Authentication> toTest = authentication.getClass();
		for (AuthenticationProvider provider : getProviders()) {
    
    
			if (!provider.supports(toTest)) {
    
    
				continue;
			}
			try {
    
    
				result = provider.authenticate(authentication);
				if (result != null) {
    
    
					copyDetails(authentication, result);
					break;
				}
			}
		}
		if (result == null && this.parent != null) {
    
    
			try {
    
    
				parentResult = this.parent.authenticate(authentication);
				result = parentResult;
			}
			catch (ProviderNotFoundException ex) {
    
    }
			catch (AuthenticationException ex) {
    
    
				parentException = ex;
				lastException = ex;
			}
		}
		...
		throw lastException;
	}

Almost all authentication logic is here
for analysis:

  • First get the Class of authentication, and judge whether the current provider supports the authentication. If it supports it, call the provider's authenticate method to start verification. After the verification is completed, a new Authentication will be returned. I will talk to you about the specific logic of this method later.
  • There may be multiple providers here. If the provider's authenticate method fails to return an Authentication normally, call the provider's parent's authenticate method to continue verification.
  • The copyDetails method is used to copy the details property of the old Token to the new Token.
  • Next, the eraseCredentials method will be called to erase the credential information, that is, your password. This erasure method is relatively simple, which is to empty the credentials attribute in the Token.
  • Finally, the successful login event is broadcast through the publishAuthenticationSuccess method.

The general process is as above. In the for loop, the provider obtained for the first time is an AnonymousAuthenticationProvider. This provider does not support UsernamePasswordAuthenticationToken, that is, it will directly return false in the provider.supports method, end the for loop, and then enter In the next if, directly call the authenticate method of the parent for verification. And parent is ProviderManager, so it will return to this authenticate method again. Going back to the authenticate method again, the provider has also become DaoAuthenticationProvider. This provider supports UsernamePasswordAuthenticationToken, so it will smoothly enter the authenticate method of this class to execute. DaoAuthenticationProvider inherits from AbstractUserDetailsAuthenticationProvider and does not override the authenticate method, so we finally come to To the AbstractUserDetailsAuthenticationProvider#authenticate method:

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    
    
		String username = determineUsername(authentication);
		boolean cacheWasUsed = true;
		UserDetails user = this.userCache.getUserFromCache(username);
		if (user == null) {
    
    
			cacheWasUsed = false;
			try {
    
    
				user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
			}
			catch (UsernameNotFoundException ex) {
    
    
				this.logger.debug("Failed to find user '" + username + "'");
				if (!this.hideUserNotFoundExceptions) {
    
    
					throw ex;
				}
				throw new BadCredentialsException(this.messages
						.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
			}
		}
		try {
    
    
			this.preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException ex) {
    
    
			if (!cacheWasUsed) {
    
    
				throw ex;
			}
			cacheWasUsed = false;
			user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
			this.preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}
		this.postAuthenticationChecks.check(user);
		if (!cacheWasUsed) {
    
    
			this.userCache.putUserInCache(user);
		}
		Object principalToReturn = user;
		if (this.forcePrincipalAsString) {
    
    
			principalToReturn = user.getUsername();
		}
		return createSuccessAuthentication(principalToReturn, authentication, user);
	}

The logic here is relatively simple:

  • First extract the login username from Authentication.

  • Then get the current user object by calling the retrieveUser method with username
    insert image description here

  • Next, call the preAuthenticationChecks.check method to check whether the status attributes of each account in the user are normal, such as whether the account is disabled, whether the account is locked, whether the account has expired, and so on.

  • The additionalAuthenticationChecks method is for password comparison. Many friends are curious about how to compare Spring Security's password after encryption. You can understand it here.
    insert image description here

  • Check if the password has expired in the postAuthenticationChecks.check method.
    insert image description here

  • There is a forcePrincipalAsString attribute, which is whether to force the principal attribute in Authentication to be set to a string. The default is false. Generally, you don’t need to change it, just use false, so that it is much more convenient to obtain the current user information later.

  • Finally, construct a new UsernamePasswordAuthenticationToken via the createSuccessAuthentication method.
    Ok, then the login verification process is complete
    insert image description here

How is the information saved after the user logs in?

To find the logged-in user information, we must first solve a problem, that is, we have mentioned so many UsernamePasswordAuthenticationFilter #attemptAuthentication login verification process, but where is attemptAuthentication triggered?

In fact, there is no need to think about it. Login verification must be triggered by an interceptor or a filter. UsernamePasswordAuthenticationFilter is originally a filter, but there is a parent class AbstractAuthenticationProcessingFilter on it. Many times when we want to customize a login verification code in Spring Security Or when changing the login parameter to JSON, we need to customize the filter to inherit from AbstractAuthenticationProcessingFilter. There is no doubt that the UsernamePasswordAuthenticationFilter#attemptAuthentication method is triggered in the doFilter method of the AbstractAuthenticationProcessingFilter class: From the above code, when the attemptAuthentication
insert image description here
method When it is called, it actually triggers the UsernamePasswordAuthenticationFilter#attemptAuthentication method. When the login throws an exception, the unsuccessfulAuthentication method will be called, and when the login is successful, the successfulAuthentication method will be called. Let's take a look at successfulAuthentication method:

insert image description here
You can see a very important line of code, which is SecurityContextHolder.getContext().setAuthentication(authResult); , the user information of successful login is saved here, that is to say, in any place, if we want to obtain user login information, we can Obtained from SecurityContextHolder.getContext(), if you want to modify it, you can also modify it here.

Finally, you can also see that there is a successHandler.onAuthenticationSuccess, which is the login success callback method we configured in SecurityConfig, and it is triggered here

After analyzing the source code, I found that it was the same

Guess you like

Origin blog.csdn.net/huangxuanheng/article/details/119082778