General documentation: Article directory Github: github.com/black-ant

A. The preface

Last time we looked at how the Security Filter works, this time we’ll look at the Security authentication process.

2. Circulation of authentication information

2.1 SecurityContext Basic object information

The core Security information is the SecurityContext. Let’s look at how authentication information is determined and circulated

SecurityContextHolder is where Spring Security stores the details of the verifier. Spring Security does not care how the SecurityContextHolder is populated. If it contains a value, it is used as the currently authenticated user.

Generate a SecurityContext

// The easiest way to indicate that the user is authenticated is to set SecurityContextHolder directly
SecurityContext context = SecurityContextHolder.createEmptyContext(); 
// Generate an Authentication object
Authentication authentication =new TestingAuthenticationToken("username"."password"."ROLE_USER"); 
context.setAuthentication(authentication);
// Set context in SecurityContextHolder
SecurityContextHolder.setContext(context); 
Copy the code

An authenticated user is obtained

// Step 1: Obtain the SecurityContext
SecurityContext context = SecurityContextHolder.getContext();
// Step 2: Obtain the Authentication and related information
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
Copy the code

The logic associated with SecurityContextHolder

  • By default, SecurityContextHolder is usedThreadLocalTo store these details (PS: but can be by SecurityContextHolder MODE global configuration)
    • The first is to set system properties
    • The second is to call the static method on SecurityContextHolder
  • Spring Security’s FilterChainProxy ensures that the SecurityContext is always cleared
// SecurityContextHolder provides the following parameters

// Three different modes are provided
public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
public static final String MODE_GLOBAL = "MODE_GLOBAL";

public static final String SYSTEM_PROPERTY = "spring.security.strategy";
// The name of the policy type
private static String strategyName = System.getProperty(SYSTEM_PROPERTY);
private static SecurityContextHolderStrategy strategy;
private static int initializeCount = 0;


// Node 1: class initialization. There is a static initializer block
static {
    initialize();
}

// Initialization is performed
private static void initialize(a) {
    if(! StringUtils.hasText(strategyName)) { strategyName = MODE_THREADLOCAL; }// You can see that there are three different modes, which correspond to three different policies
    if (strategyName.equals(MODE_THREADLOCAL)) {
        strategy = new ThreadLocalSecurityContextHolderStrategy();
    }else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
        strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
    }else if (strategyName.equals(MODE_GLOBAL)) {
        strategy = new GlobalSecurityContextHolderStrategy();
    }else {
        try {
            // Invert colorClass<? > clazz = Class.forName(strategyName); Constructor<? > customStrategy = clazz.getConstructor(); strategy = (SecurityContextHolderStrategy) customStrategy.newInstance(); }catch (Exception ex) {
            ReflectionUtils.handleReflectionException(ex);
        }
    }
    initializeCount++;
}

// Node 2: setContext logic, called by policy
public static void setContext(SecurityContext context) {
    strategy.setContext(context);
}


Copy the code

GlobalSecurityContextHolderStrategy: including SecurityContext is a static variable InheritableThreadLocalSecurityContextHolderStrategy: Containing a ThreadLocal < SecurityContext > ThreadLocalSecurityContextHolderStrategy: and a little on the difference

2.2 SecurityContext process

sequenceDiagram FilterChainProxy->>AbstractAuthenticationProcessingFilter: Calling doFilter AbstractAuthenticationProcessingFilter - > > DatabaseAuthenticationFilter: Call attemptAuthentication DatabaseAuthenticationFilter -- -- > > the AuthenticationManager: Call Provider AuthenticationManager from Manager-->> Provider: Call Provider to request Authentication Provider-->> AuthenticationManager: But will the Authentication the AuthenticationManager - > > DatabaseAuthenticationFilter: Returns the Authentication DatabaseAuthenticationFilter - > > AbstractAuthenticationProcessingFilter: Returns the Authentication Note right of AbstractAuthenticationProcessingFilter: handle Authentication invokes the relevant Handler finally here

Step 1: Call the Provider to handle the situation, where the Authentication is complete and an Authentication is returned

// Recall that earlier in the Filter, calling AuthenticationManager started the Provider's process
DatabaseUserToken authRequest = new DatabaseUserToken(username, password);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);

/ / to the outer back, you can see, its core is called AbstractAuthenticationProcessingFilter is the abstract class
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) 

Copy the code

Step 2: Provider processing

Here the AuthenticationManager is primarily ProviderManager and these are the main ones, and we’ll just keep the more important logic:

public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
    Class<? extends Authentication> toTest = authentication.getClass();
    AuthenticationException lastException = null;
    Authentication result = null;
    Authentication parentResult = null;
    boolean debug = logger.isDebugEnabled();
    
    for (AuthenticationProvider provider : getProviders()) {
        // Each Provider overrides supports to determine whether the Provider is supported
        if(! provider.supports(toTest)) {continue;
        }

        try {
            // Here the specific Provider execution is invoked
            result = provider.authenticate(authentication);
            if(result ! =null) {
                copyDetails(authentication, result);
                break; }}catch (AccountStatusException|InternalAuthenticationServiceException e) 
                prepareException(e, authentication);
                throw e;
        }catch(AuthenticationException e) { lastException = e; }}// There is also a compensation policy that will be handled by the parent if the current AuthenticationManager cannot handle it
    // I don't know how to use it. It might be suitable for fine-grained permissions
    if (result == null&& parent ! =null) {	
        try {
            result = parentResult = parent.authenticate(authentication);
        }catch(AuthenticationException e) { lastException = e; }}if(result ! =null) {
        if (eraseCredentialsAfterAuthentication
					&& (result instanceof CredentialsContainer)) {
            ((CredentialsContainer) result).eraseCredentials();
        }
        // Publish the time of authentication success
        if (parentResult == null) {
            eventPublisher.publishAuthenticationSuccess(result);
        }
        return result;
    }

    if (lastException == null) {
        lastException = new ProviderNotFoundException(messages.getMessage(
					"ProviderManager.providerNotFound".new Object[] { toTest.getName() },
					"No AuthenticationProvider found for {0}"));
    }
    prepareException(lastException, authentication);
    throw lastException;
}

Copy the code

As you can see, at this point the Provider returns an Authentication back

Step 3: AbstractAuthenticationProcessingFilter processing

Returning from the second step Prodiver Authentication, he was eventually passed to AbstractAuthenticationProcessingFilter

/ / AbstractAuthenticationProcessingFilter pseudo code:

// Step 1: Perform pre-certification
authResult = attemptAuthentication(request, response);

/ / Step 2: the Session strategy to deal with, here is sessionStrategy NullAuthenticatedSessionStrategy, its inside is empty
sessionStrategy.onAuthentication(authResult, request, response) 

// Step 3: Perform container processing after success
successfulAuthentication(request, response, chain, authResult)    
    |- SecurityContextHolder.getContext().setAuthentication(authResult) // Put the authResult into the SecurityContext
    |- rememberMeServices.loginSuccess(request, response, authResult) // Remember my function processing, remember that the RememberFilter will process this
    |- successHandler.onAuthenticationSuccess(request, response, authResult) // SavedRequestAwareAuthenticationSuccessHandler
Copy the code

At this point, the Authentication generated by the Provider is successfully placed into the container

Extension SavedRequestAwareAuthenticationSuccessHandler treatment Success results

To sum up, customize the cache and jump relationship.

C- SavedRequestAwareAuthenticationSuccessHandler P- RequestCache requestCache : Request cache tool, M -onAuthenticationSuccess -SavedRequest SavedRequest = requestCache.getreQuest (Request, Response) : get the cached object first - String targetUrlParameter = getTargetUrlParameter() : See if there is a successful jump address? - if you want to realize the jump from different users, custom here - clearAuthenticationAttributes (request) : Delete temporary data related to authentication, these data may be stored in the session in the authentication process, to avoid sensitive information - String targetUrl = savedRequest. GetRedirectUrl (); - getRedirectStrategy().sendRedirect(request, response, targetUrl); ? - Redirect outCopy the code

Above is the flow chart of authentication from and authentication failure, you can see the specific processing class:

To summarize what certification success and certification failure do:

If authentication fails:

  • The Security Contextholder is cleared.
  • Call RememberMeServices loginFail. If rememberme is not configured, this is a no-op
  • Call AuthenticationFailureHandler.

Also compare authentication success:

  • Will notify the SessionAuthenticationStrategy when in a new login
  • Set authentication on SecurityContextHolder, then SecurityContextPersistenceFilter to save SecurityContext into HttpSession
  • Call RememberMeServices loginSuccess. If remember me is not configured, this is a no-op
  • ApplicationEventPublisher release interactive authentication connection
  • Call AuthenticationSuccessHandler

3. Re-visit and exit

So that’s what happens during a certification process, so let’s look at that and then go back >>>

3.1 Access after authentication

SecurityContextHolder is written to SecurityContextHolder. The Security checks the user by determining the SecurityContext
// In the same way, the next time to access the same way:


/ / Step 1: SecurityContextPersistenceFilter intercept to the request

// Step 2: Get the SecurityContext from the request
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);

/ / get from HttpSessionSecurityContextRepository
C- HttpSessionSecurityContextRepository
    SecurityContext context = readSecurityContextFromSession(httpSession);

// The authentication information can be seen at the breakpoint:
org.springframework.security.core.context.SecurityContextImpl@45eed422: 
Authentication: com.security.demo.token.DatabaseUserToken@45eed422: 
Principal: gang; 
Credentials: [PROTECTED]; 
Authenticated: true; 
Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffdaa08: RemoteIpAddress: 127.0. 01.; 
SessionId: A58D946FBFCB17743E2E0A44DBAB7A76; 
Granted Authorities: ROLE_USER	

// Finally set the SecurityContext
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
SecurityContextHolder.clearContext();
repo.saveContext(contextAfterChainExecution, holder.getRequest(),holder.getResponse());



Copy the code

PS : Because it’s Session based, so it expires after a while and of course it’s Session based, so the lifecycle is the same as Session, but it’s often used for longer lifecycle schemes like AccessToken, Cookie, and so on, while a Session is just to maintain a temporary state of authentication

3.2 the Logout exit

Logout related classes:

  • PersistentTokenBasedRememberMeServices
  • TokenBasedRememberMeServices
  • CookieClearingLogoutHandler
  • CsrfLogoutHandler
  • SecurityContextLogoutHandler
  • HeaderWriterLogoutHandler

Also, the Logout Filter and Handler also exist

  • LogoutFilter
  • SimpleUrlLogoutSuccessHandler
  • HttpStatusReturningLogoutSuccessHandler

As with the previous analysis of the Filter, the core is done through LogoutFilter:

this.handler.logout(request, response, auth); logoutSuccessHandler.onLogoutSuccess(request, response, auth); C - SecurityContextLogoutHandler: core classes, processingContext 
	public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
		Assert.notNull(request, "HttpServletRequest required");
		if (invalidateHttpSession) {
			HttpSession session = request.getSession(false);
			if(session ! =null) {
				logger.debug("Invalidating session: "+ session.getId()); session.invalidate(); }}if (clearAuthentication) {
             // Set SecurityContext to NULL
			SecurityContext context = SecurityContextHolder.getContext();
			context.setAuthentication(null);
		}

		SecurityContextHolder.clearContext();
	}

Copy the code

Conclusion:

At this point, a complete Security lifecycle is seen, in fact, very simple, summed up as:

  • Filter makes business decisions
  • AuthenticationManager determines the verification mode
  • Provider performs authentication and verification
  • Handler does the result processing that has been external to the jump

The next chapter takes a closer look at the Security configuration logic to see what’s going on underneath