preface
This article is followed by the previous chapter Spring Security source analysis a: Spring Security authentication process further analysis of the Spring Security user name password login authorization is how to achieve;
The class diagram
Debugging process
Run the debug command to start github.com/longfeizhen… The project, the browser type http://localhost:8080/persons, user name, password, 123456;
Source code analysis
The following figure shows the invocation flow of filters during login authentication. The author has highlighted several filters that he considers important.
You can see the order of execution in the figure. To see a few more important for the author Filter processing logic, UsernamePasswordAuthenticationFilter, AnonymousAuthenticationFilter, ExceptionTranslationFilter, FilterSecurityInterceptor and related process are described below;
UsernamePasswordAuthenticationFilter
The calling process is, first call its parent class AbstractAuthenticationProcessingFilter. DoFilter () method, And then execute UsernamePasswordAuthenticationFilter. AttemptAuthentication validated () method;
AbstractAuthenticationProcessingFilter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; #1. Check whether the current filter can handle the current request, if not, pass to the next filter to handle if (! requiresAuthentication(request, response)) { chain.doFilter(request, response); return; } if (logger.isDebugEnabled()) { logger.debug("Request is to process authentication"); } Authentication authResult; Try {# 2. Abstract method implemented by subclasses UsernamePasswordAuthenticationFilter authResult = attemptAuthentication (request, response); if (authResult == null) { // return immediately as subclass has indicated that it hasn't completed // authentication return; } # 2. After the success of the certification, with some methods related to the session sessionStrategy. OnAuthentication (authResult, request, response); } catch (InternalAuthenticationServiceException failed) { logger.error( "An internal error occurred while trying to authenticate the user.", failed); # 3. The certification after the failure of some operating unsuccessfulAuthentication (request, response, failed); return; } catch (AuthenticationException failed) { // Authentication failed unsuccessfulAuthentication(request, response, failed); return; } // Authentication success if (continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } #3. Put the current authentication into SecurityContextHolder successfulAuthentication(Request, Response, Chain, authResult); }Copy the code
The execution process of the whole program is as follows:
- Check whether the filter can process the current request. If not, pass the request to the next filter
- Calling abstract methods
attemptAuthentication
For validation, the method is subclassedUsernamePasswordAuthenticationFilter
implementation - After successful authentication, call back some session-related methods.
- After successful authentication, related callback methods after successful authentication; After successful authentication, related callback methods after successful authentication;
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
+ authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);
rememberMeServices.loginSuccess(request, response, authResult);
// Fire event
if (this.eventPublisher ! =null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
authResult, this.getClass()));
}
successHandler.onAuthenticationSuccess(request, response, authResult);
}
Copy the code
1. Place the current successful Authentication into SecurityContextHolder. 2. Place the current successful Authentication into SecurityContextHolder. 3. Call other extensible handlers to continue handling the callback event after successful authentication. (implementation ` AuthenticationSuccessHandler ` interface)Copy the code
UsernamePasswordAuthenticationFilter
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { #1. The method of determining the request must be POST request if (postOnly &&! request.getMethod().equals("POST")) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); String username = obtainUsername(request); String password = obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); # 3. Build UsernamePasswordAuthenticationToken (two arguments constructor setAuthenticated (false)) UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password); // Allow subclasses to set the "details" property setDetails(request, authRequest); #4. Call AuthenticationManager for validation (subclass ProviderManager iterates through all AuthenticationProvider authentications) return this.getAuthenticationManager().authenticate(authRequest); }Copy the code
- The method of authenticating the request must be
POST
- Get username and password from request
- encapsulation
Authenticaiton
The implementation of the classUsernamePasswordAuthenticationToken
, (UsernamePasswordAuthenticationToken
Call the two-argument constructor setAuthenticated(false)) - call
AuthenticationManager
的authenticate
Methods to verify; May refer toProviderManagerPart;
AnonymousAuthenticationFilter
From the above filters can be seen in the execution sequence diagram AnonymousAuthenticationFilter filter is in UsernamePasswordAuthenticationFilter after filter, if it had no authentication filter in front of success, Spring Security is adding a current SecurityContextHolder Authenticaiton anonymous implementation class AnonymousAuthenticationToken;
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { #1. If none of the previous filters are approved, Is SecurityContextHolder Authentication is empty in the if (SecurityContextHolder getContext () getAuthentication () = = null) {# 2. For the current SecurityContextHolder add an anonymous AnonymousAuthenticationToken SecurityContextHolder. GetContext (). SetAuthentication ( createAuthentication((HttpServletRequest) req)); if (logger.isDebugEnabled()) { logger.debug("Populated SecurityContextHolder with anonymous token: '" + SecurityContextHolder.getContext().getAuthentication() + "'"); } } else { if (logger.isDebugEnabled()) { logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '" + SecurityContextHolder.getContext().getAuthentication() + "'"); } } chain.doFilter(req, res); } # 3. Create an anonymous AnonymousAuthenticationToken protected Authentication createAuthentication (it request) { AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key, principal, authorities); auth.setDetails(authenticationDetailsSource.buildDetails(request)); return auth; } /** * Creates a filter with a principal named "anonymousUser" and the single authority * "ROLE_ANONYMOUS". * * @param key the key to identify tokens created by this filter */ ##. Create a user called anonymousUser authorization for ROLE_ANONYMOUS public AnonymousAuthenticationFilter (String key) {this (key, "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); }Copy the code
- judge
Authentication in SecurityContextHolder
If no, the value is null. - Current if empty
SecurityContextHolder
Add an anonymousAnonymousAuthenticationToken
anonymousUserAnonymousAuthenticationToken
)
ExceptionTranslationFilter
ExceptionTranslationFilter exception handling filter, the filter used to handle the exception thrown in the process of system certification authorization (i.e., the next filter FilterSecurityInterceptor), mainly processing AuthenticationException and AccessDeniedException.
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; try { chain.doFilter(request, response); logger.debug("Chain processed normally"); } catch (IOException ex) { throw ex; } catch (Exception ex) { // Try to extract a SpringSecurityException from the stacktrace #. Distinguish the AuthenticationException Throwable [] causeChain = throwableAnalyzer. DetermineCauseChain (ex); RuntimeException ase = (AuthenticationException) throwableAnalyzer .getFirstThrowableOfType(AuthenticationException.class, causeChain); If (ase == null) {# if (ase == null) throwableAnalyzer.getFirstThrowableOfType( AccessDeniedException.class, causeChain); } if (ase ! = null) { handleSpringSecurityException(request, response, chain, ase); } else { // Rethrow ServletExceptions and RuntimeExceptions as-is if (ex instanceof ServletException) { throw (ServletException) ex; } else if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } // Wrap other Exceptions. This shouldn't actually happen // as we've already covered all the possibilities for doFilter throw new RuntimeException(ex); }}}Copy the code
FilterSecurityInterceptor
This filter is the last filter in the chain of authentication and authorization filters, after which the real/Persons service is requested
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(request, response, chain); invoke(fi); } public void invoke(FilterInvocation fi) throws IOException, ServletException { if ((fi.getRequest() ! = null) && (fi.getRequest().getAttribute(FILTER_APPLIED) ! = null) && observeOncePerRequest) { // filter already applied to this request and user wants us to observe // once-per-request handling, so don't re-do security checking fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } else { // first time this request being called, so perform security checking if (fi.getRequest() ! = null) { fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); } #1. Before InterceptorStatusToken Token = super.beforeInvocation(FI); Fin.getchain ().dofilter (fin.getrequest (), fin.getresponse ())); } finally { super.finallyInvocation(token); } #3. after Invocation super.afterInvocation(token, null); }}Copy the code
- Before invocation important
- Request a real /persons service
- after Invocation
Of the three parts, the most important is #1, which calls the AccessDecisionManager to verify that the currently authenticated user has access to the resource;
before invocation: AccessDecisionManager
protected InterceptorStatusToken beforeInvocation(Object object) { ... Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource() .getAttributes(object); . Authentication authenticated = authenticateIfRequired(); // Attempt authorization try { #1. Focus on this. The accessDecisionManager. Decide (authenticated, object, attributes). } catch (AccessDeniedException accessDeniedException) { publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,accessDeniedException)); throw accessDeniedException; }... }Copy the code
Authenticated is currently authenticated, but what are Object and Attributes?
What are attributes and objects?
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
Copy the code
debugging
We find that Object is the current request URL :/persons, so the getAttributes method uses the current access resource path to match our own defined matching rules.
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()// Use form login instead of default httpBasic
.loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)// The URL to jump to if the requested URL requires authentication
.loginProcessingUrl(SecurityConstants.DEFAULT_SIGN_IN_PROCESSING_URL_FORM)// Handle the custom login URL in the form
.and()
.authorizeRequests().antMatchers(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
SecurityConstants.DEFAULT_SIGN_IN_PROCESSING_URL_FORM,
SecurityConstants.DEFAULT_REGISTER_URL,
"/**/*.js"."/**/*.css"."/**/*.jpg"."/**/*.png"."/**/*.woff2")
.permitAll()// None of the above requests require authentication
.anyRequest()// The rest of the request
.authenticated()// Both require authentication
.and()
.csrf().disable()// Disable CSRD interception
;
}
Copy the code
0-7 Returns permitALL, which means no authentication is required. 8 returns authenticated, which means the current request requires authentication.
You can see that authenticated is AnonymousAuthentication and the user name is anonymousUser
How is AccessDecisionManager authorized?
By default, Spring Security uses the Decide method of AccessDecisionManager implemented by AffirmativeBased to implement authorization
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException { int deny = 0; AccessDecisionVoter = AccessDecisionVoter = AccessDecisionVoter getDecisionVoters()) { int result = voter.vote(authentication, object, configAttributes); if (logger.isDebugEnabled()) { logger.debug("Voter: " + voter + ", returned: " + result); } the switch (result) {# 1.1 so long as has the voter to vote for ACCESS_GRANTED, through direct return case AccessDecisionVoter. ACCESS_GRANTED: / / 1 return; @ # 1.2 so long as has the voter to vote for ACCESS_DENIED, record the case AccessDecisionVoter. ACCESS_DENIED: / / - 1 deny++; break; default: break; = = = = = = = = = = = = = = = = = = = = = = Directly not through throw new AccessDeniedException (messages. GetMessage (" AbstractAccessDecisionManager. AccessDenied ", "Access is denied")); } // To get this far, every AccessDecisionVoter abstained checkAllowIfAllAbstainDecisions(); }Copy the code
- Call the AccessDecisionVoter to vote
- As long as there is an ACCESS_GRANTED vote, the approval will be given directly.
- If the vote does not pass
deny++
Finally judgeIf (deny > 0
throwAccessDeniedException
(Unauthorized)
WebExpressionVoter.vote()
public int vote(Authentication authentication, FilterInvocation fi, Collection
attributes)
{
assertauthentication ! =null;
assertfi ! =null;
assertattributes ! =null;
WebExpressionConfigAttribute weca = findConfigAttribute(attributes);
if (weca == null) {
return ACCESS_ABSTAIN;
}
EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,
fi);
ctx = weca.postProcess(ctx, fi);
return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED
: ACCESS_DENIED;
}
Copy the code
To this location authentication current user information, FL current access to the resource path and Attributes the current resource path decision (that is, whether authentication is required). Left is to determine the role of the current user Authentication. The authorites whether access to the current resources fi access decisions.