Security implements authentication and authorization based on filter chain. It supports different authentication mechanisms. Here we use user name and password authentication mechanism.
Spring Security provides the following built-in mechanisms to read user names and passwords from:
- Form Login
- Basic Authentication
- Digest Authentication
Username and password storage mechanism:
- Simple storage with in memory authentication
- Relational database with JDBC authentication
- Customizing the data store using UserDetailsService
- LDAP store with LDAP authentication
1, Certification flow chart
Figure from network
Analyze the certification process:
- When users submit their user name and password, UsernamePasswordAuthenticationFilter creates a UsernamePasswordAuthenticationToken, which is a type of Authentication by extracting HttpServletRequest from the user name and password.
- The filter submits the UsernamePasswordAuthenticationToken to the authentication manager for authentication
- If authentication fails, the SecurityContextHolder will be cleared. RememberMeServices.loginFail is called. If you remember that I have no configuration, this is an empty operation. Finally, AuthenticationFailureHandler is called.
- If the Authentication is successful, the Authentication manager returns an Authentication instance filled with information (including the permission information, identity information and details mentioned above, but the password is usually removed). The Authentication is set in the SecurityContextHolder. RememberMeServices.loginSuccess is called. If you remember that I have no configuration, this is an empty operation. ApplicationEventPublisher publishes an InteractiveAuthenticationSuccessEvent. Called in AuthenticationSuccessHandler.
2, Source code analysis
1. View UsernamePasswordAuthenticationFilter filter
UsernamePasswordAuthenticationFilter is mainly used for authentication. The default matching URL is / login and must be a POST request.
Seeing the source code, we should understand why the default is post request, the url is / login, and the parameter name of user name and password is it.
In the attemptAuthentication method, two main things are done:
- The filled user name and password are encapsulated in the UsernamePasswordAuthenticationToken
- Call the AuthenticationManager object to implement authentication
1.1 the user name and password are encapsulated in the UsernamePasswordAuthenticationToken
The filled user name and password are encapsulated in the UsernamePasswordAuthenticationToken.
1.2 calling the AuthenticationManager object to realize authentication
From the source code, the real authentication logic is the authenticate method on the AuthenticationManager interface. Next, look at the implementation class of AuthenticationManager and the authenticate method of ProviderManager class.
2. View ProviderManager class
The source code is as follows:
@Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // Gets the Authentication type passed in Class<? extends Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; AuthenticationException parentException = null; Authentication result = null; Authentication parentResult = null; int currentPosition = 0; int size = this.providers.size(); // Circular AuthenticationProvider object. Spring Security encapsulates an AuthenticationProvider object for each authentication, such as third-party login, username and password login, etc. for (AuthenticationProvider provider : getProviders()) { // 1. Judge whether the current authentication method is supported if (!provider.supports(toTest)) { continue; } if (logger.isTraceEnabled()) { logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)", provider.getClass().getSimpleName(), ++currentPosition, size)); } try { // 2. Call to find the supported AuthenticationProvider object for authentication logic result = provider.authenticate(authentication); if (result != null) { // 3. Execute the copy logic of authentication details copyDetails(authentication, result); break; } } catch (AccountStatusException | InternalAuthenticationServiceException ex) { // 4. If an AccountStatusException or InternalAuthenticationServiceException occurs, the exception event will be published through the Spring event publisher AuthenticationEventPublisher. prepareException(ex, authentication); // SEC-546: Avoid polling additional providers if auth failure is due to // invalid account status throw ex; } catch (AuthenticationException ex) { lastException = ex; } } if (result == null && this.parent != null) { // Allow the parent to try. try { parentResult = this.parent.authenticate(authentication); result = parentResult; } catch (ProviderNotFoundException ex) { // ignore as we will throw below if no other exception occurred prior to // calling parent and the parent // may throw ProviderNotFound even though a provider in the child already // handled the request } catch (AuthenticationException ex) { parentException = ex; lastException = ex; } } if (result != null) { if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) { // Authentication is complete. Remove credentials and other secret data // from authentication ((CredentialsContainer) result).eraseCredentials(); } // If the parent AuthenticationManager was attempted and successful then it // will publish an AuthenticationSuccessEvent // This check prevents a duplicate AuthenticationSuccessEvent if the parent // AuthenticationManager already published it if (parentResult == null) { this.eventPublisher.publishAuthenticationSuccess(result); } return result; } // Parent was null, or didn't authenticate (or throw an exception). if (lastException == null) { lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}")); } // If the parent AuthenticationManager was attempted and failed then it will // publish an AbstractAuthenticationFailureEvent // This check prevents a duplicate AbstractAuthenticationFailureEvent if the // parent AuthenticationManager already published it if (parentException == null) { prepareException(lastException, authentication); } throw lastException; }
2.1 viewing AbstractUserDetailsAuthenticationProvider class
AbstractUserDetailsAuthenticationProvider is an AuthenticationProvider object that authenticates a form user name password login.
The source code is as follows:
public Authentication authenticate(Authentication authentication) throws AuthenticationException { Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> { return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"); }); //1. Get the user name, which is encapsulated in UsernamePasswordAuthenticationToken String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName(); boolean cacheWasUsed = true; UserDetails user = this.userCache.getUserFromCache(username); if (user == null) { cacheWasUsed = false; try { //2. Obtain user information. Our own user implements the UserDetails interface 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); //3. Account status and password verification 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(); } //4. Encapsulate the UsernamePasswordAuthenticationToken again and return Authentication return this.createSuccessAuthentication(principalToReturn, authentication, user); }
2.1.1 get user object (UserDetails)
View the retrieveUser method.
- The UserDetails object is an authenticated user object within Spring Security. Our customized users can implement the UserDetails interface.
- Our SysUserService inherits the UserDetailsService class and reloads the loadUserByUsername method.
3.1.2 account status and password verification
1) Account status verification
2) Encryption verification
Use PasswordEncoder password parser to encrypt and parse the password.
BCryptPasswordEncoder is the most commonly used password parser in spring security.
It uses the BCrypt algorithm. The feature is that encryption uses dynamic salt sault, but decryption does not require salt. Because salt is in the ciphertext. In this way, different ciphertext can be encrypted for the same string by adding different salt each time.
Ciphertext such as: $2a 10 10 10lLi6A4xdn7lCWYgU5yIXoek9DeYC91mTf6d9nO4UUyB/Bv4QXq6.i
Where: $is a delimiter and meaningless; 2a is the bcrypt encryption version number; 10 is the value of cost; Then the first 22 digits are salt values; Then the string is the ciphertext of the password.
2.1.3 encapsulate UsernamePasswordAuthenticationToken object
View the createSuccessAuthentication method.
This time, the UsernamePasswordAuthenticationToken object is encapsulated. See its construction method for details.
3. View UsernamePasswordAuthenticationToken class
The source code is as follows:
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken { private static final long serialVersionUID = 510L; private final Object principal; private Object credentials; //Before the authentication is successful, the two parameter is called. public UsernamePasswordAuthenticationToken(Object principal, Object credentials) { super((Collection)null); this.principal = principal; this.credentials = credentials; this.setAuthenticated(false); } //After the authentication is successful, it calls the three parameter. public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { //Focus on the parent class super(authorities); this.principal = principal; this.credentials = credentials; super.setAuthenticated(true); // Mark certified } }
3.1 view super(authorities)
View the construction method of the parent class AbstractAuthenticationToken. The source code is as follows:
public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer { private final Collection<GrantedAuthority> authorities; // User's permission information collection private Object details; private boolean authenticated = false; public AbstractAuthenticationToken(Collection<? extends GrantedAuthority> authorities) { if (authorities == null) {//These are two parameters this.authorities = AuthorityUtils.NO_AUTHORITIES; } else {//Three parameters, look here Iterator var2 = authorities.iterator(); //1. Add user permission information GrantedAuthority a; do { if (!var2.hasNext()) { ArrayList<GrantedAuthority> temp = new ArrayList(authorities.size()); // Add permission, GrantedAuthority type temp.addAll(authorities); this.authorities = Collections.unmodifiableList(temp); return; } a = (GrantedAuthority)var2.next(); } while(a != null); //2. If there is no permission information, an exception will be thrown throw new IllegalArgumentException("Authorities collection cannot contain any null elements"); } } }
The main purpose here is to add user permission information, because our role class implements the GrantedAuthority interface. To obtain user information, role information has also been put into user information, so it is not difficult to understand here.
Therefore, permission information (of type GrantedAuthority) must be placed in the UserDetails object returned by our custom authentication business logic.
Now, the user information in the authentication process is encapsulated and authenticated. Next, let's look at the logic after successful authentication.
4. View doFilter method
Back to the original UsernamePasswordAuthenticationFilter filter, we didn't find the doFilter method, this class didn't, so go to the parent class!
View the doFilter method of the parent class AbstractAuthenticationProcessingFilter. The source code is as follows:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest)req; HttpServletResponse response = (HttpServletResponse)res; //1. Determine whether certification is required if (!this.requiresAuthentication(request, response)) { chain.doFilter(request, response); } else { if (this.logger.isDebugEnabled()) { this.logger.debug("Request is to process authentication"); } Authentication authResult; try { //2. Call subclass methods to obtain Authentication information and encapsulate it in Authentication authResult = this.attemptAuthentication(request, response); if (authResult == null) { return; } // 3. Session policy processing 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) { //5. Handling of authentication failure this.unsuccessfulAuthentication(request, response, var9); return; } if (this.continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } // 4. Successful authentication processing this.successfulAuthentication(request, response, chain, authResult); } }
Call subclass methods to obtain authentication information, which we have analyzed above.
4.1 viewing successful authentication methods
//Successful authentication, call successfulAuthentication 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 tocontain: " + authResult); } //1. Authentication succeeded. Store the authentication information in SecurityContext! SecurityContextHolder.getContext().setAuthentication(authResult); //2. Successfully log in and call rememberMeServices this.rememberMeServices.loginSuccess(request, response, authResult); if (this.eventPublisher != null) { this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass())); } this.successHandler.onAuthenticationSuccess(request, response, authResult); }
4.2 viewing the unsuccessfulAuthentication method
//Authentication failed, call unsuccessfulAuthentication protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { SecurityContextHolder.clearContext(); if (this.logger.isDebugEnabled()) { this.logger.debug("Authentication request failed: " + failed.toString(), failed); this.logger.debug("Updated SecurityContextHolder to contain null Authentication"); this.logger.debug("Delegating to authentication failure handler " + this.failureHandler); } this.rememberMeServices.loginFail(request, response); this.failureHandler.onAuthenticationFailure(request, response, failed); }
The above is the general process of the certification process.
- -- if you are hungry for knowledge, if you are foolish with an open mind.