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

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/1/5/160c51a0c4a662d2~tplv-t2oaga2asx-image.image

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.

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/1/5/160c51a0c4868b1d~tplv-t2oaga2asx-image.image

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:

  1. Check whether the filter can process the current request. If not, pass the request to the next filter
  2. Calling abstract methodsattemptAuthenticationFor validation, the method is subclassedUsernamePasswordAuthenticationFilterimplementation
  3. After successful authentication, call back some session-related methods.
  4. 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
  1. The method of authenticating the request must bePOST
  2. Get username and password from request
  3. encapsulationAuthenticaitonThe implementation of the classUsernamePasswordAuthenticationToken, (UsernamePasswordAuthenticationTokenCall the two-argument constructor setAuthenticated(false))
  4. callAuthenticationManagerauthenticateMethods 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
  1. judgeAuthentication in SecurityContextHolderIf no, the value is null.
  2. Current if emptySecurityContextHolderAdd an anonymousAnonymousAuthenticationTokenanonymousUserAnonymousAuthenticationToken)

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
  1. Before invocation important
  2. Request a real /persons service
  3. 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

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/1/5/160c51a0b928457e~tplv-t2oaga2asx-image.image

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.

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/1/5/160c51a0bbef6204~tplv-t2oaga2asx-image.image

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
  1. Call the AccessDecisionVoter to vote
  2. As long as there is an ACCESS_GRANTED vote, the approval will be given directly.
  3. If the vote does not passdeny++Finally judgeIf (deny > 0throwAccessDeniedException(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.

Sequence diagram

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/1/5/160c51a0c490ea00~tplv-t2oaga2asx-image.image