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:
- Spring Security (1): Introduction to filters, interceptors, and slices of RESTful API interception
- Spring Security (2): The initial setup and basic principles of Spring Security
- Spring Security (three): user-defined authentication logic
- Spring Security (four): personalized user authentication 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
Certification process
- 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 anUsernamePasswordAuthenticationToken
object (unauthorized), its class isAuthentication
an implementation, the interface encapsulates the authentication information, and finallyAuthenticationManager
theauthenticate
method is called , as shown in the figure In the second step, its role is to manage the third stepAuthenticationProvider
.
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
authenticate
the class where the method is locatedProviderManager
, which implements theAuthenticationManager
interface. There is a for loop inside the method to get all of themAuthenticationProvider
. Why is the loop? Because different authentication logic is different, itAuthenticationManager
is 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 calledauthenticate
.
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 aretrieveUser
method, which isDaoAuthenticationProvider
implemented by the class . There is a sentence in this method, which isthis.getUserDetailsService().loadUserByUsername(username);
actually called theUserDetailsService
implementation of our interface to get itUserDetails
. 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
loadUserByUsername
method, and returns to the third step to get theUserDetails
object; continue to go down, performthis.preAuthenticationChecks.check(user);
a pre-check, the logic inside is to judgeUserDetails
some 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, theadditionalAuthenticationChecks
method will be called to perform additional checks. The additional check mainly checks whether the password matches. After the pre-check, there will be athis.postAuthenticationChecks.check(user);
post -check at the end . This check is to determine theUserDetails
last 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
authentication
anduser
create a Success ofAuthentication
one re-created inside theUsernamePasswordAuthenticationToken
(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
AbstractAuthenticationProcessingFilter
thedoFilter
method, after calling theattemptAuthentication
method and going through the above process to get the authentication successAuthentication
, thesuccessfulAuthentication
method will be called . The last step isthis.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 insidethis.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
- In fact, in the above
successfulAuthentication
method after successful authentication , this lineSecurityContextHolder.getContext().setAuthentication(authResult);
is toAuthentication
save the successful authentication toSecurityContext
the implementation classSecurityContextImpl
of the interface , and then save itSecurityContextHolder
. - So who will use
SecurityContextHolder
it? In factSecurityContextPersistenceFilter
, 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 sessionSecurityContext
, and if there is one , remove it from the session.SecurityContext
Take 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
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;
}