Source code analysis of UsernamePasswordAuthenticationFilter

Keywords: Session Database

This is the construction method of UsernamePasswordAuthenticationFilter and the main content of the attemptAuthentication method is in attemptAuthentication

public UsernamePasswordAuthenticationFilter() {
    super(new AntPathRequestMatcher("/login", "POST"));
}

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: AntPathRequestMatcher("/login","POST"), default login page and request method

public UsernamePasswordAuthenticationFilter() {
    super(new AntPathRequestMatcher("/login", "POST"));
}

Step: in the attemptAuthentication(HttpServletRequest request,HttpServletResponse response) method, encapsulate the intercepting user and password as a UsernamePasswordAuthenticationToken object, assign the user name to the principle of the object, and assign the password to the credentials of the object, Collection = null (because the valid role of the resource has not been obtained at this time), setauthentication (false) (because the user is actually verified to be a valid user at this time)

public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
    super((Collection)null);
    this.principal = principal;
    this.credentials = credentials;
    this.setAuthenticated(false);
}

Step: call setDetails of the instance object of UsernamePasswordAuthenticationToken, pass the return value of the bulidDetails method that creates the real column of WebAuthenticationDetailsScource as a parameter to setDetails, and pass the request as a parameter to the buildDetails method of WebAuthenticationDetailsScourse. At this time, get the request to get the sessionID and client ip

public class WebAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> {
    public WebAuthenticationDetailsSource() {
    }

    public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
        return new WebAuthenticationDetails(context);
    }
}

public WebAuthenticationDetails(HttpServletRequest request) {
    this.remoteAddress = request.getRemoteAddr();
    HttpSession session = request.getSession(false);
    this.sessionId = session != null ? session.getId() : null;
}

Step: call the authentication() method of the AuthenticationManager object (in this case, the implementation class of the interface, ProviderManager) and pass the encapsulated authentication object as a parameter

 

protected AuthenticationManager getAuthenticationManager() {
    return this.authenticationManager;
}

 

Inner loop traverses the set of authentication providers

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    Class<? extends Authentication> toTest = authentication.getClass();
    AuthenticationException lastException = null;
    AuthenticationException parentException = null;
    Authentication result = null;
    Authentication parentResult = null;
    boolean debug = logger.isDebugEnabled();
    Iterator var8 = this.getProviders().iterator();

    while(var8.hasNext()) {
        AuthenticationProvider provider = (AuthenticationProvider)var8.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 (InternalAuthenticationServiceException | AccountStatusException var13) {
                this.prepareException(var13, authentication);
                throw var13;
            } catch (AuthenticationException var14) {
                lastException = var14;
            }
        }
    }
}

 

, traverse the supports (authentication,) method of the authentication provider to find the supported authentication provider. At this time, the implementation class of the authentication provider is DaoAuthenticationProvider

public boolean supports(Class<?> authentication) {
    return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}

If it is found, the authentication(authentication) method of the authentication provider is called. This method actually calls the authentication method of AbstractUserDetailAuthenticationProvider, the parent class of DaoAuthenticationProvider

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
        return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
    });
    String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
    boolean cacheWasUsed = true;
    UserDetails user = this.userCache.getUserFromCache(username);
    if (user == null) {
        cacheWasUsed = false;

        try {
            user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
        } catch (UsernameNotFoundException var6) {
            this.logger.debug("User '" + username + "' not found");
            if (this.hideUserNotFoundExceptions) {
                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            }

            throw var6;
        }

        Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
    }

    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);
}

, the implementation class of UserCache is getUserFromCache(username) of SpringCacheBaseUserCache

public UserDetails getUserFromCache(String username) {
    ValueWrapper element = username != null ? this.cache.get(username) : null;
    if (logger.isDebugEnabled()) {
        logger.debug("Cache hit: " + (element != null) + "; username: " + username);
    }

    return element == null ? null : (UserDetails)element.get();
}

, this method calls the get(key) method of abstractvalueadaptcache, which is the parent class of ConcurrentMapCache,

@Nullable
public ValueWrapper get(Object key) {
    Object value = this.lookup(key);
    return this.toValueWrapper(value);
}

The get(key) method calls the lookup method of ConcurrentMapCache,

@Nullable
protected Object lookup(Object key) {
    return this.store.get(key);
}

Louup method will call the get method of concurrentmap < object in ConcurrentMapCache, Object > store, then encapsulate it into ValueWrapper, return it to getUserFromCache method, then execute valueWrapper.get to get UserDetails value, and the whole process will take user entity class from cache.

@Nullable
protected Object lookup(Object key) {
    return this.store.get(key);
}

Step: if the user does not exist in the cache, then call retrieveUserUser (username, usernamePasswordAuthenticationToken) and retrieve[to retrieve user information from the database, then call check(userDetails) of the implementation class DefaultPreAuthenticationChecks of UserDetailsChecher to verify whether the user is not locked, whether it is unavailable, and whether it has not expired.

public void check(UserDetails user) {
    if (!user.isAccountNonLocked()) {
        AbstractUserDetailsAuthenticationProvider.this.logger.debug("User account is locked");
        throw new LockedException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
    } else if (!user.isEnabled()) {
        AbstractUserDetailsAuthenticationProvider.this.logger.debug("User account is disabled");
        throw new DisabledException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
    } else if (!user.isAccountNonExpired()) {
        AbstractUserDetailsAuthenticationProvider.this.logger.debug("User account is expired");
        throw new AccountExpiredException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
    }
}

If the above audit passes, continue to match the password, and call additionalAuthenticationChecks(UserDetails,usernamePasswordAuthenticationToken)

protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    if (authentication.getCredentials() == null) {
        this.logger.debug("Authentication failed: no credentials provided");
        throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
    } else {
        String presentedPassword = authentication.getCredentials().toString();
        if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
            this.logger.debug("Authentication failed: password does not match stored value");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }
    }
}

If it passes, call the check method of DefaultPostAuthenticationChecks, another implementation class of UserDetailsChecks, to continue to check the password and verify whether it has expired.

public void check(UserDetails user) {
    if (!user.isCredentialsNonExpired()) {
        AbstractUserDetailsAuthenticationProvider.this.logger.debug("User account credentials have expired");
        throw new CredentialsExpiredException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired"));
    }
}

At this time, if all pass, it means that the user is logged in for the first time and is a valid user, then the user information will be added to the cache

if (!cacheWasUsed) {
    this.userCache.putUserInCache(user);
}

Published 29 original articles, won praise 17, visited 70000+
Private letter follow

Posted by joshmmo on Thu, 05 Mar 2020 02:27:56 -0800