Spring Security (5): Source code analysis of authentication process

You should be able to learn a lot of function points from the previous studies, but you only know how to implement it. The bottom layer is completely a black box state, and these knowledge points are very fragmented. This article will analyze the authentication process. The underlying source code of, connects the entire authentication process in series.

Knowledge review

Introductory practice of the certification process:

Function summary

Features achieve
Processing user information acquisition logic Implement UserDetailsService interface
Processing user verification logic Implement UserDetails interface
Encryption and decryption of passwords Implement the PasswordEncoder interface
Custom login successful processing Implement the AuthenticationSuccessHandler interface
Custom login failure handling Implement the AuthenticationFailHandler interface

Authentication process source code

Fundamental

  • The basic principle of Spring Security
    Processing flow chart

Certification process

  • Certification flow chart
    Certification flow chart
  • The first step: first enter UsernamePasswordAuthenticationFilter, get the user name and password passed from the front end, and then use the user name and password to construct an UsernamePasswordAuthenticationTokenobject (unauthorized), its class is Authenticationan implementation, the interface encapsulates the authentication information, and finally AuthenticationManagerthe authenticatemethod is called , as shown in the figure In the second step, its role is to manage the third step AuthenticationProvider.
	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());
        } else {
    
    
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            if (username == null) {
    
    
                username = "";
            }

            if (password == null) {
    
    
                password = "";
            }

            username = username.trim();
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }
  • Step 2: Enter authenticatethe class where the method is located ProviderManager, which implements the AuthenticationManagerinterface. There is a for loop inside the method to get all of them AuthenticationProvider. Why is the loop? Because different authentication logic is different, it AuthenticationManageris responsible for collecting all authentication requests. , And then each goes to the loop to determine whether the login method passed in by the method is supported, and if it is supported, an authentication process is performed, that is, the method of the provider is called authenticate.
	while(var6.hasNext()) {
    
    
        AuthenticationProvider provider = (AuthenticationProvider)var6.next();
        if (provider.supports(toTest)) {
    
    
            if (debug) {
    
    
                logger.debug("Authentication attempt using " + provider.getClass().getName());
            }

            try {
    
    
                result = provider.authenticate(authentication);
                if (result != null) {
    
    
                    this.copyDetails(authentication, result);
                    break;
                }
            } catch (AccountStatusException var11) {
    
    
                this.prepareException(var11, authentication);
                throw var11;
            } catch (InternalAuthenticationServiceException var12) {
    
    
                this.prepareException(var12, authentication);
                throw var12;
            } catch (AuthenticationException var13) {
    
    
                lastException = var13;
            }
        }
    }
  • The third step: In fact AbstractUserDetailsAuthenticationProvider, the method of this abstract class is called. This method calls a retrieveUsermethod, which is DaoAuthenticationProviderimplemented by the class . There is a sentence in this method, which is this.getUserDetailsService().loadUserByUsername(username);actually called the UserDetailsServiceimplementation of our interface to get it UserDetails. This is connected with our custom authentication logic.
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    
    
        UserDetails loadedUser;
        try {
    
    
            loadedUser = this.getUserDetailsService().loadUserByUsername(username);
        } catch (UsernameNotFoundException var6) {
    
    
            if (authentication.getCredentials() != null) {
    
    
                String presentedPassword = authentication.getCredentials().toString();
                this.passwordEncoder.isPasswordValid(this.userNotFoundEncodedPassword, presentedPassword, (Object)null);
            }

            throw var6;
        } catch (Exception var7) {
    
    
            throw new InternalAuthenticationServiceException(var7.getMessage(), var7);
        }

        if (loadedUser == null) {
    
    
            throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
        } else {
    
    
            return loadedUser;
        }
    }
  • Step 4: The previous step calls our own loadUserByUsernamemethod, and returns to the third step to get the UserDetailsobject; continue to go down, perform this.preAuthenticationChecks.check(user);a pre-check, the logic inside is to judge UserDetailssome bool attributes, including whether the account is locked, Whether it is available or expired, if one of them is satisfied, the corresponding exception will be thrown. After the pre-check, the additionalAuthenticationChecksmethod will be called to perform additional checks. The additional check mainly checks whether the password matches. After the pre-check, there will be a this.postAuthenticationChecks.check(user);post -check at the end . This check is to determine the UserDetailslast bool attribute and check whether the authentication information has expired or not. ????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
	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);
            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();
        }

        return this.createSuccessAuthentication(principalToReturn, authentication, user);
  • Step Five: After all checks, based on authenticationand usercreate a Success of Authenticationone re-created inside the UsernamePasswordAuthenticationToken(authorized), but not the same as before and is calling the constructor of the three parameters, which will pass Enter the authorization information, and the attributes have been set and authenticated.
	protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
    
    
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
        result.setDetails(authentication.getDetails());
        return result;
    }
  • Step 6: Back to AbstractAuthenticationProcessingFilterthe doFiltermethod, after calling the attemptAuthenticationmethod and going through the above process to get the authentication success Authentication, the successfulAuthenticationmethod will be called . The last step is this.successHandler.onAuthenticationSuccess(request, response, authResult);actually calling the login success handler written by myself.
	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 to contain: " + authResult);
        }

        SecurityContextHolder.getContext().setAuthentication(authResult);
        this.rememberMeServices.loginSuccess(request, response, authResult);
        if (this.eventPublisher != null) {
    
    
            this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
        }

        this.successHandler.onAuthenticationSuccess(request, response, authResult);
    }
  • Step 7: As long as an exception is thrown in one of the above steps, the exception will be caught and called after the capture. this.unsuccessfulAuthentication(request, response, var8);The processing logic inside this.failureHandler.onAuthenticationFailure(request, response, failed);is to call the login failure handler written by yourself.
	Authentication authResult;
    try {
    
    
        authResult = this.attemptAuthentication(request, response);
        if (authResult == null) {
    
    
            return;
        }

        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) {
    
    
        this.unsuccessfulAuthentication(request, response, var9);
        return;
    }

How authentication results are shared among multiple requests

Thinking?

  • First of all, sharing between multiple requests must be put in the session. When did Spring Security put something in the session and when did it read it out from the session?

Authentication information access

  • Follow-up diagram of the certification process
    Follow-up image
  • In fact, in the above successfulAuthenticationmethod after successful authentication , this line SecurityContextHolder.getContext().setAuthentication(authResult);is to Authenticationsave the successful authentication to SecurityContextthe implementation class SecurityContextImplof the interface , and then save it SecurityContextHolder.
  • So who will use SecurityContextHolderit? In fact SecurityContextPersistenceFilter, it is the last step of the filter, which is at the top of the filter connection we mentioned earlier; it has two functions. When a request comes in, advance the filter to check whether there is a session SecurityContext, and if there is one , remove it from the session. SecurityContextTake it out and put it in the thread; when the filter chain is finished and the request goes out, it will pass through the filter again. At this time, it will check whether there is a thread, and if there is , take it out and put it in the session.
  • In this case, different requests can get the same authentication information in the session, and then put it in the thread after getting it, because the entire request is completed in one thread. Therefore, it can be used at any time in the thread SecurityContextHolder.getContext()to obtain authentication information.
  • Complete diagram of the basic principle
    Filter chain diagram

Get authenticated user information

method one

	@GetMapping("/me")
	public Object getCurrentUser() {
    
    
		return SecurityContextHolder.getContext().getAuthentication();
	}

Way two

	@GetMapping("/me")
	public Object getCurrentUser(Authentication authentication) {
    
    
		return authentication;
	}

Get UserDetails only

	@GetMapping("/me")
	public Object getCurrentUser(@AuthenticationPrincipal UserDetails userDetails) {
    
    
		return userDetails;
	}

Guess you like

Origin blog.csdn.net/qq_36221788/article/details/105871472