Detailed explanation of the source code of Spring Security user authentication process

The last article explained the basic principles of Spring Security and how to customize the user authentication logic. This article will interpret the source code of Spring Security on the basis of the previous article to understand its authentication logic process more clearly.

This article mainly focuses on the following questions to delve into the source code:

  1. User Authentication Process
  2. How authentication results are shared across multiple requests
  3. Get authenticated user information

1. User Authentication Process

As mentioned in the previous section, the core of Spring Security is a series of filter chains. When a request comes, it must first pass the verification of the filter chain. After the verification is passed, various user information will be accessed.
What should be explained here is that there is a SecurityContextPersistenceFilter at the front end of the filter. When the request comes in and returns, it will go through this filter, which mainly stores the user's authentication information. It is briefly mentioned here, and will be explained in detail later.
write picture description here

When the user sends a login request, it first enters the UsernamePasswordAuthenticationFilter for verification.
write picture description here
When the breakpoint sends a login request into the source code, we will find that it will enter the UsernamePasswordAuthenticationFilter. In this class, there is an attemptAuthentication method. In this method, the information of the user's username and password parameters will be obtained, and then the constructor new UsernamePasswordAuthenticationToken will be used. (username, password) is encapsulated as a UsernamePasswordAuthenticationToken object. Inside this constructor, the corresponding information will be assigned to their respective local variables, and the parent class AbstractAuthenticationToken constructor will be called (the parent class constructor will be introduced later), pass A null value goes in, why is it null? Because there is no authentication at the beginning, the user does not have any permissions, and sets the information without authentication (setAuthenticated(false)), and finally enters the implementation class ProviderManager of the AuthenticationManager interface.
write picture description here

write picture description here

In the implementation class of ProviderManager, it will call the implementation class of the AuthenticationProvider interface to obtain the user's information, and the verification of the user's information authority is verified in this class. Enter the ProviderManager class to call the authenticate(Authentication authentication) method. After it obtains the user's login method through the AuthenticationProvider implementation class, there will be a for loop to traverse whether it supports this login method. The specific login methods include form login, qq login, WeChat login etc. If it does not support it, it will end the for loop. If it is supported, it will enter the abstract implementation class AbstractUserDetailsAuthenticationProvider of the AuthenticationProvider interface and call the authenticate(Authentication authentication) method to verify the user's identity.
write picture description here

After entering the internal authenticate method of the abstract class AbstractUserDetailsAuthenticationProvider, it will first determine whether the user is empty. This user is the object of UserDetail. If it is empty, it means that there is no authentication, and you need to call the retrieveUser method to obtain the user's information. This method is abstract A method of class AbstractUserDetailsAuthenticationProvider that extends DaoAuthenticationProvider.
write picture description here

In the retrieveUser method of the extension class, call the loadUserByUsername method of the implementation class of the UserDetailsService interface to obtain user information, and here I have written the implementation class MyUserDetail class. Obtain permission information such as user password and return.
write picture description here

write picture description here
After getting the user's information, return to the AbstractUserDetailsAuthenticationProvider class to call the createSuccessAuthentication(principalToReturn, authentication, user) method. In this method, the UsernamePasswordAuthenticationToken constructor with three parameters will be called, which is different from the previous call with two parameters, because it is already there. The user's information and permissions are verified, so the null value is no longer passed to the parent class constructor, but the user's permission set, and the authentication is set to pass (setAuthenticated(true)),
write picture description here
write picture description here
write picture description here
in the parent class of UsernamePasswordAuthenticationToken, it will Check the permissions used. If one of them is null, it means that the permissions do not have the corresponding permissions, and an exception is thrown.
write picture description here

Then after the createSuccessAuthentication method returns, return to the authenticate method of the ProviderManager to return the result, and finally return to the attemptAuthentication method of the UsernamePasswordAuthenticationFilter that was just entered.
write picture description here

Through the above source code, we have gone deep into the source code to understand the specific authentication process of the user. Here is a brief summary. First, when the user sends a request, it will enter the UsernamePasswordAuthenticationFilter to get a UsernamePasswordAuthenticationToken, which is actually equivalent to a token, but has not been authenticated, and then call the AuthenticationManager implementation class ProviderManager to determine whether the login method is supported. If it is supported, it will call the abstract implementation class of the AuthenticationProvider interface, AbstractUserDetailsAuthenticationProvider, and call its extension class DaoAuthenticationProvider to obtain the MyUserDetails class that we implemented by ourselves to obtain the user password for user authentication, and then return the object and set the UsernamePasswordAuthenticationToken token to pass the authentication. Identity verification succeeded.

2. How the authentication result is shared among multiple requests

Let's take a look at how the authentication result is shared among multiple requests after the user passes the identity verification. It must be put into the session. Let's take a look at the flowchart first.
write picture description here

After the authentication is successful, finally after the UsernamePasswordAuthenticationFilter returns, it will enter an AbstractAuthenticationProcessingFilter class to call the successfulAuthentication method. This method will finally return our own defined login success handler handler. Before returning, it will call the SecurityContext and finally put the authentication result. Put into SecurityContextHolder, the SecurityContext class is very simple. It rewrites the equals method and the hascode method to ensure the uniqueness of authentication. The SecurityContextHolder class is actually an encapsulation of ThreadLocal, which can communicate between different methods. We can simply understand it as a global variable at the thread level. Therefore, authentication information can be obtained in different methods in the same thread. Finally, it will be used by the SecurityContextPersistenceFilter filter. What is the function of this filter? When a request comes, it will pass the value in the session to the thread, and when the request returns, it will determine whether the request thread has a SecurityContext, and if so, it will put it into the session, so It is guaranteed that the request result can be shared among different requests.
write picture description here
write picture description here
write picture description here

3. Obtaining authenticated user information

If we need to get all the verified information used, how do we get it? Above we know that the verification result will be put into the session, so we can get it through the session.

    @GetMapping("/me")
    public Object getMeDetail() {
        return SecurityContextHolder.getContext().getAuthentication();
    }
@GetMapping("/me1")
    public Object getMeDetail(Authentication authentication){
        return authentication;
    }

After the login is successful, there are two ways to obtain it. Accessing the above request will obtain all the verification information of the user, including the ip address and other information.
write picture description here
If we only want to get the username and password and its permissions, and don't need too much information such as the ip address, we can use the following method to get the information.

@GetMapping("/me2")
    public Object getMeDetail(@AuthenticationPrincipal UserDetails userDetails){
        return userDetails;
    }

write picture description here

So far, this article has gone deep into the source code to understand the authentication process of Spring Seucrity and how the authentication results are shared among multiple requests. Maybe the above content is not very clear, you can interpret it in combination with the source code, and the authentication process of Spring Security will be clearer if you look at the source code yourself.


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324661551&siteId=291194637