An article takes you through the login process of Spring Security

Why do you want to walk through the Spring Security login process with everyone? This is because of a question asked by my friends: How to dynamically modify user information in Spring Security?

If you figure out the Spring Security login process, this is actually not a problem.

First, let's briefly describe the problem scenario:

You use Spring Security for the security management of the server. After the user logs in successfully, Spring Security will help you save the user information in the Session, but you may not know where it is, if you don’t go into it, this brings a problem. If the user modifies the current user information in the front-end operation, how can I obtain the latest user information without logging in again? This is the question to be introduced to the building today.

1. Ubiquitous Authentication

Friends who have played Spring Security know that there is a very important object in Spring Security called Authentication. We can inject Authentication anywhere to get the information of the currently logged in user. Authentication itself is an interface, and it has many implementation classes:

Insert picture description here
Insert picture description here
In this great implementation class, our most commonly used is UsernamePasswordAuthenticationToken, but when we opened the source code of this class, but found that this class mediocrity, he had only two properties, two constructors as well as a number of get/setmethods; of course, He has more attributes on its parent class.

But from its only two attributes, we can also roughly see that this class saves the basic information of our logged-in user. So how is our login information stored in these two objects? This is going to sort out the login process.

Two, login process

In Spring Security, the verification of authentication and authorization is completed in a series of filter chains. In this series of filter chains, the filters related to authentication are the UsernamePasswordAuthenticationFilterspace issue, I will list them here Several important methods in this class:

publicclass UsernamePasswordAuthenticationFilter extends
		AbstractAuthenticationProcessingFilter {
    
    
	public UsernamePasswordAuthenticationFilter() {
    
    
		super(new AntPathRequestMatcher("/login", "POST"));
	}
	public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
    
    
		String username = obtainUsername(request);
		String password = obtainPassword(request);
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
				username, password);
		setDetails(request, authRequest);
		returnthis.getAuthenticationManager().authenticate(authRequest);
	}
	protected String obtainPassword(HttpServletRequest request) {
    
    
		return request.getParameter(passwordParameter);
	}
	protected String obtainUsername(HttpServletRequest request) {
    
    
		return request.getParameter(usernameParameter);
	}
	protected void setDetails(HttpServletRequest request,
			UsernamePasswordAuthenticationToken authRequest) {
    
    
		authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
	}
}

According to this source code we can see:

(1) through the first obtainUsernameand obtainPasswordextraction methods inside a request username / password out, extraction method is request.getParameter, which is why the Spring Security default forms login parameters to be passed through in the form of key / value, and can not pass parameters JSON , If you want to pass JSON parameters, just modify the logic here.

(2) After obtaining the request was passed to the username / password, then on a configuration UsernamePasswordAuthenticationTokenobject passed usernameand password, usernamecorrespond to UsernamePasswordAuthenticationTokenthe principalattributes, and passwordis corresponding to its credentialsattributes.

(3) Next, setDetailsa method to detailsassign attribute, UsernamePasswordAuthenticationTokenitself is not detailsproperty, this property in its parent class AbstractAuthenticationTokenin. detailsIs an object that is placed inside the WebAuthenticationDetailsinstance of two mainly described information, the request remoteAddressand the request sessionId.
(4) The last step is to call the authenticate method to do verification.

Well, from this source code, you can see that all the requested information has basically found its own location, and it will be convenient for us to obtain it in the future.

Next, let's look at the specific verification operation requested.

In the previous attemptAuthenticationmethod, the final step of the process started checking, verification operation begins to get a AuthenticationManager, get here is that ProviderManager, so then we enter into ProviderManagerthe authenticateprocess, of course, this method is relatively long, I Here are just a few important points:

public Authentication authenticate(Authentication authentication)
		throws AuthenticationException {
    
    
	Class<? extends Authentication> toTest = authentication.getClass();
	for (AuthenticationProvider provider : getProviders()) {
    
    
		if (!provider.supports(toTest)) {
    
    
			continue;
		}
		result = provider.authenticate(authentication);
		if (result != null) {
    
    
			copyDetails(authentication, result);
			break;
		}
	}
	if (result == null && parent != null) {
    
    
		result = parentResult = parent.authenticate(authentication);
	}
	if (result != null) {
    
    
		if (eraseCredentialsAfterAuthentication
				&& (result instanceof CredentialsContainer)) {
    
    
			((CredentialsContainer) result).eraseCredentials();
		}
		if (parentResult == null) {
    
    
			eventPublisher.publishAuthenticationSuccess(result);
		}
		return result;
	}
	throw lastException;
}

This method is more magical, because almost all the important logic about authentication will be completed here:

(1) First, get authenticationthe Class, the current provider to determine whether to support the authentication.

(2) If it supports, call the authenticate method of the provider to start the verification. After the verification is completed, a new Authentication will be returned. I will discuss the specific logic of this method with you in a moment.

(3) provider where there may be more, if the provider is not able to authenticate method returns a normal Authentication, parent of the provider of the calling authenticatemethod to continue checking.

(4) The copyDetails method is used to copy the details property of the old Token to the new Token.

(5) Next, the eraseCredentials method will be called to erase the credential information, which is your password. This erasure method is relatively simple, which is to empty the credentials attribute in the Token.

(6) Finally, publishAuthenticationSuccessthe method will be successful logon event broadcasted.
The general process is the above. In the for loop, the provider you get for the first time is an AnonymousAuthenticationProvider. This provider does not support UsernamePasswordAuthenticationToken at all, that is, it returns false directly in the provider.supports method, ends the for loop, and then It will enter the next if and directly call the parent's authenticate method to verify.

The parent is ProviderManager, it will once again return to this authenticatemethod. Again back authenticatemethod, has become a provider DaoAuthenticationProvider, the provider is supported UsernamePasswordAuthenticationToken, it will be well into the class authenticatemethod to perform, and DaoAuthenticationProviderinherited from AbstractUserDetailsAuthenticationProviderand does not override the authenticate method, so we have finally arrived AbstractUserDetailsAuthenticationProvider#authenticateapproach:

public Authentication authenticate(Authentication authentication)
		throws AuthenticationException {
    
    
	String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
			: authentication.getName();
	user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);
	preAuthenticationChecks.check(user);
	additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication);
	postAuthenticationChecks.check(user);
	Object principalToReturn = user;
	if (forcePrincipalAsString) {
    
    
		principalToReturn = user.getUsername();
	}
	return createSuccessAuthentication(principalToReturn, authentication, user);
}

The logic here is relatively simple:

(1) First extract the login user name from Authentication.

(2) then took by usernamecalling to retrieveUserthe method to get the current user object, this step will call our own during login to write loadUserByUsernamemethod, so there is actually a return to the user objects you login, you can refer to micro personnel org / javaboy /vhr/service/HrService.java#L34.

(3) Next, call the preAuthenticationChecks.checkmethod to examine each user account properties in the state is normal, for example, whether the account is disabled, whether the account is locked, the account has expired and so on.

(4) The additionalAuthenticationChecksmethod is to do a password comparison. Many friends are curious about how Spring Security's passwords are encrypted and how to compare. You can understand it here. Because the logic of the comparison is very simple, I will not post the code here. .

(5) Finally, postAuthenticationChecks.checkcheck whether the password is expired methods.

(6) Then there is a forcePrincipalAsStringproperty, whether this is to force Authenticationthe principalproperty to a string, we start at this property UsernamePasswordAuthenticationFilteris actually set to the string (ie username) class, but by default, when a user logs After success, the value of this attribute becomes the object of the current user. The reason for this is because forcePrincipalAsStringthe default is false, but do not change this fact, use false, so to get the current user information in the latter part of the time but a lot easier.

(7) Finally, by createSuccessAuthenticationconstructing a new method UsernamePasswordAuthenticationToken.

Okay, so the log-in verification process is now basically repeated with everyone. Then there is another question, where do we look for the logged-in user information?

Three, user information preservation

To find the logged-in user information, we have to solve a problem first, that is, we have said so much above, where did all this start to be triggered?

We came to UsernamePasswordAuthenticationFilterthe parent class AbstractAuthenticationProcessingFilter, this class we often see, because many times when we want in a Spring Security custom code or login authentication login parameters will be changed to JSON, and we all need custom filters inherited from AbstractAuthenticationProcessingFilterno doubt, UsernamePasswordAuthenticationFilter#attemptAuthenticationit is to the AbstractAuthenticationProcessingFilterclass doFiltermethod is triggered:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
		throws IOException, ServletException {
    
    
	HttpServletRequest request = (HttpServletRequest) req;
	HttpServletResponse response = (HttpServletResponse) res;
	Authentication authResult;
	try {
    
    
		authResult = attemptAuthentication(request, response);
		if (authResult == null) {
    
    
			return;
		}
		sessionStrategy.onAuthentication(authResult, request, response);
	}
	catch (InternalAuthenticationServiceException failed) {
    
    
		unsuccessfulAuthentication(request, response, failed);
		return;
	}
	catch (AuthenticationException failed) {
    
    
		unsuccessfulAuthentication(request, response, failed);
		return;
	}
	if (continueChainBeforeSuccessfulAuthentication) {
    
    
		chain.doFilter(request, response);
	}
	successfulAuthentication(request, response, chain, authResult);
}

From the above code, we can see that when the attemptAuthenticationmethod is called, in fact, trigger a UsernamePasswordAuthenticationFilter#attemptAuthenticationmethod throws an exception when the login, the unsuccessfulAuthenticationmethod is called, and when the login is successful, the successfulAuthenticationmethod will be called, then we take a look at successfulAuthenticationmethods:

protected void successfulAuthentication(HttpServletRequest request,
		HttpServletResponse response, FilterChain chain, Authentication authResult)
		throws IOException, ServletException {
    
    
	SecurityContextHolder.getContext().setAuthentication(authResult);
	rememberMeServices.loginSuccess(request, response, authResult);
	// Fire event
	if (this.eventPublisher != null) {
    
    
		eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
				authResult, this.getClass()));
	}
	successHandler.onAuthenticationSuccess(request, response, authResult);
}

Here there is a very important code, that is SecurityContextHolder.getContext().setAuthentication(authResult);, user information is stored in the login is successful here, that is to say, in any place, if we want to get the user login information, are available from the SecurityContextHolder.getContext()get into, you want to modify, can also be in Modify here.

Finally, everyone also saw one successHandler.onAuthenticationSuccess. This is the callback method that we configure in SecurityConfig. It is triggered here. You can also refer to the configuration in the micro-personnel org/javaboy/vhr/config/SecurityConfig.

Guess you like

Origin blog.csdn.net/nanhuaibeian/article/details/108585792