Overall process of authentication and authorization

Start with a self-organized flow chart of Spring Security authentication and authorization. This chapter, mainly according to the process, simple walking comb source code.

Certified source code analysis

AbstractAuthenticationProcessingFilter, this class is the entrance of the certification process.

Create the Authentication AbstractAuthenticationProcessingFilter according to the Request.

The Authentication specific types, determined by the actual use AbstractAuthenticationProcessingFilter child class. Common are: UsernamePasswordAuthenticationToken.

By default, using UsernamePasswordAuthenticationFilter classes, so the corresponding Authentication is a UsernamePasswordAuthenticationToken.

UsernamePasswordAuthenticationFilter# attemptAuthentication, / /… Indicates that some secondary logic is deleted.

/ / UsernamePasswordAuthenticationFilter# attemptAuthentication ` / /... 'indicates that some secondary logic is deleted.
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    / /...
        String username = obtainUsername(request);
        String password = obtainPassword(request);
        / /...
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                username, password);
 
        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);
 
        return this.getAuthenticationManager().authenticate(authRequest);
    }
Copy the code

We can see that the AuthenticationManager is called in the last sentence above. From the official documentation, we know that ProviderManager is the default implementation of AuthenticationManager. So we need to go to the ProviderManager.

Support provider is DaoAuthenticationProvider UsernamePasswordAuthenticationToken, DaoAuthenticationProvider inherited from AbstractUserDetailsAuthenticationProvider.

ProviderManager#authenticate

 
  public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
    / / for Autentication type, this is we are actually UsernamePasswordAuthenticationToken
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        AuthenticationException parentException = null;
        Authentication result = null;
        Authentication parentResult = null;
    // Get all authenticationProviders
        for (AuthenticationProvider provider : getProviders()) {
      // Determine whether the provider supports the Autentication
      / / here DaoAuthenticationProvider through verification
            if(! provider.supports(toTest)) {continue;
            }
            / /...
            try {
                / / perform authentication AbstractUserDetailsAuthenticationProvider# authenticate
                result = provider.authenticate(authentication);
                if(result ! =null) {
                    copyDetails(authentication, result);
                    break; }}/ /...
        }
        / /.....................
        if(result ! =null) {
            if (eraseCredentialsAfterAuthentication
                    && (result instanceof CredentialsContainer)) {
                // Authentication is complete. Remove credentials and other secret data
                // from authentication
                ((CredentialsContainer) result).eraseCredentials();
            }
      / /.....................................................................
      / /.....................................................................
    }
 
Copy the code

AbstractUserDetailsAuthenticationProvider# supports, authenticate

public boolean supports(Class
        authentication) {
    return (UsernamePasswordAuthenticationToken.class
            .isAssignableFrom(authentication));
}
 
public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        / /...
        // Determine username
    // Get the user name
        String username = (authentication.getPrincipal() == null)?"NONE_PROVIDED"
                : authentication.getName();
 
        boolean cacheWasUsed = true;
    // Get the user body based on the user name
        UserDetails user = this.userCache.getUserFromCache(username);
 
        if (user == null) {
            cacheWasUsed = false;
            try {
        // Not in cache, call the retrieveUser method to get the user
                user = retrieveUser(username,
                        (UsernamePasswordAuthenticationToken) authentication);
            }
            catch (UsernameNotFoundException notFound) {
        / /...
            }
 
            / /...
      / /...
        return createSuccessAuthentication(principalToReturn, authentication, user);
    }
Copy the code

DaoAuthenticationProvider# retrieveUser, createSuccessAuthentication

protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        prepareTimingAttackProtection();
        try {
      // if there is no user in memory, call UserDeatilsService#loadUserByUsername to get the user
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        }
        / /.....................
    }
 
@Override
    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
    // Verify password
        boolean upgradeEncoding = this.userDetailsPasswordService ! =null
                && this.passwordEncoder.upgradeEncoding(user.getPassword());
        if (upgradeEncoding) {
            String presentedPassword = authentication.getCredentials().toString();
            String newPassword = this.passwordEncoder.encode(presentedPassword);
            user = this.userDetailsPasswordService.updatePassword(user, newPassword);
        }
        return super.createSuccessAuthentication(principal, authentication, user);
    }
 
// super.createSuccessAuthentication
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
        / / create a UsernamePasswordAuthenticationToken returns
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
                principal, authentication.getCredentials(),
                authoritiesMapper.mapAuthorities(user.getAuthorities()));
        result.setDetails(authentication.getDetails());
 
        return result;
    }
Copy the code

Authorized source code analysis

Unauthorized entry is FilterSecurityInterceptor.

FilterSecurityInterceptor

/ /...
private FilterInvocationSecurityMetadataSource securityMetadataSource;
/ /...
public void invoke(FilterInvocation fi) throws IOException, ServletException {
          / /...
            InterceptorStatusToken token = super.beforeInvocation(fi);
            try {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            }
            finally {
                super.finallyInvocation(token);
            }
            super.afterInvocation(token, null); }}/ /...
Copy the code

This shows that Spring Security is the default SecurityMetadataSource FilterInvocationSecurityMetadataSource.

We observed that the super-.beforeInvocation () goes to its parent class and continues the process.

AbstractSecurityInterceptor

protected InterceptorStatusToken beforeInvocation(Object object) {
        Assert.notNull(object, "Object was null");
        / /...
    / / call FilterInvocationSecurityMetadataSource here, to get access to the current path needs to be set
        Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
                .getAttributes(object);
    // If the value is empty, the authentication process is not continued and the system returns directly
        if (attributes == null || attributes.isEmpty()) {
            / /...
            publishEvent(new PublicInvocationEvent(object));
            return null; // no further work post-invocation
        }
        / /...
    // If there is no authentication, an exception is thrown
        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            credentialsNotFound(messages.getMessage(
                    "AbstractSecurityInterceptor.authenticationNotFound"."An Authentication object was not found in the SecurityContext"),
                    object, attributes);
        }
 
     / /...
 
        AccessDecisionManager authorization is invoked here, and an exception is thrown if permissions are insufficient.
        try {
            this.accessDecisionManager.decide(authenticated, object, attributes);
        }
        catch (AccessDeniedException accessDeniedException) {
            publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
                    accessDeniedException));
            throw accessDeniedException;
        }
 
    / /...
    / /...
    }
Copy the code

AccessDecisionManager is an interface. As can be seen from the log, Spring Security uses its implementation class AffirmativeBased by default and WebExpressionVoter is also used.

o.s.s.access.vote.AffirmativeBased       : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@bb39abe, returned: -1
Copy the code

AffirmativeBased#decide

public void decide(Authentication authentication, Object object, Collection
       
         configAttributes)
        throws AccessDeniedException {
        int deny = 0;
 
        for (AccessDecisionVoter voter : getDecisionVoters()) {
      // Handle authorization
            int result = voter.vote(authentication, object, configAttributes);
            / /...
            switch (result) {
                  case AccessDecisionVoter.ACCESS_GRANTED:
                      return;
                  case AccessDecisionVoter.ACCESS_DENIED:
                      deny++;
                      break;
                  default:
                      break; }}// If the authorization fails, add one. At this point, if greater than 0, an exception is thrown, otherwise normal.
        if (deny > 0) {
            throw new AccessDeniedException(messages.getMessage(
                    "AbstractAccessDecisionManager.accessDenied"."Access is denied"));
        }
     / /...
    }
Copy the code

The actual authentication is done in matter.vote (). According to the log, it is handled by its implementation class WebExpressionVoter.

WebExpressionVoter# vote, findConfigAttribute

public int vote(Authentication authentication, FilterInvocation fi, Collection
       
         attributes)
        {        assertauthentication ! =null;        assertfi ! =null;        assertattributes ! =null;       / / the attributes into WebExpressionConfigAttribute WebExpressionConfigAttribute weca = findConfigAttribute (attributes); // If (weca == null) {return ACCESS_ABSTAIN; } / / to create an environment determine EvaluationContext CTX. = expressionHandler createEvaluationContext (authentication, fi); ctx = weca.postProcess(ctx, fi); / / execution decision return ExpressionUtils. EvaluateAsBoolean (weca. GetAuthorizeExpression (), CTX)? ACCESS_GRANTED : ACCESS_DENIED; } / / WebExpressionConfigAttribute attribute translate into, Matching to it returns null if no private WebExpressionConfigAttribute findConfigAttribute (Collection < ConfigAttribute > attributes) {the for (ConfigAttribute attribute : attributes) { if (attribute instanceof WebExpressionConfigAttribute) { return (WebExpressionConfigAttribute) attribute; } } return null; }
Copy the code

This is the end of the authentication.