(1). 概述

前面对认证部份的源码进行了剖析,在这里将对,鉴权部份进行剖析,那么,如何找到鉴权的入口呢?带着这个疑问,我们来看源码.

(2). WebSecurityConfigurerAdapter

在前面的分析,我们知道,HttpSecurity内部持有SecurityConfigurer集合,HttpSecurity会对这个集合进行初始化(init/configure).

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
		// ***********************************************************************************************************
		// 通过:HttpSecurity配置权限
		// ***********************************************************************************************************
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
		registry
				// 允许/login.html不需要认证.
				.antMatchers("/login.html").permitAll()
				// ***************************************************************
				// 配置hello请求,需要具备有ADMIN角色,才能放行.
				// ***************************************************************
				.antMatchers("/hello").hasRole("ADMIN")
				
				// 所有请求都要登录之后,才能访问
				.anyRequest().authenticated();
    } // end configure
}		

(3). ExpressionUrlAuthorizationConfigurer

从代码中,能分析出来,整个鉴权是交给了:FilterSecurityInterceptor,在FilterSecurityInterceptor内部又持有:AuthenticationManager/AccessDecisionManager.

// ExpressionUrlAuthorizationConfigurer 继承于 AbstractInterceptUrlConfigurer
public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>>
		extends AbstractInterceptUrlConfigurer<ExpressionUrlAuthorizationConfigurer<H>, H> {
			
} // end ExpressionUrlAuthorizationConfigurer


abstract class AbstractInterceptUrlConfigurer<C extends AbstractInterceptUrlConfigurer<C, H>, H extends HttpSecurityBuilder<H>>
		extends AbstractHttpConfigurer<C, H> {
    
	
	@Override
	public void configure(H http) throws Exception {
		FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource(http);
		if (metadataSource == null) {
			return;
		}
		
		// ************************************************************************************************
		// 1.创建:FilterSecurityInterceptor
		// ************************************************************************************************
		FilterSecurityInterceptor securityInterceptor = createFilterSecurityInterceptor(http, metadataSource, http.getSharedObject(AuthenticationManager.class));
		if (filterSecurityInterceptorOncePerRequest != null) {
			securityInterceptor.setObserveOncePerRequest(filterSecurityInterceptorOncePerRequest);
		}
		
		securityInterceptor = postProcess(securityInterceptor);
		// 
		http.addFilter(securityInterceptor);
		http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor);
	} // end configure
	
	
	private FilterSecurityInterceptor createFilterSecurityInterceptor(H http, FilterInvocationSecurityMetadataSource metadataSource, AuthenticationManager authenticationManager) throws Exception {
		FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor();
		securityInterceptor.setSecurityMetadataSource(metadataSource);
		// 配置AuthenticationManager/AccessDecisionManager
		securityInterceptor.setAccessDecisionManager(getAccessDecisionManager(http));
		securityInterceptor.setAuthenticationManager(authenticationManager);
		securityInterceptor.afterPropertiesSet();
		return securityInterceptor;
	} // end createFilterSecurityInterceptor
	
} // end AbstractInterceptUrlConfigurer

(4). 查看FilterSecurityInterceptor的类关系

org.springframework.security.access.intercept.AbstractSecurityInterceptor
	org.springframework.security.web.access.intercept.FilterSecurityInterceptor

(5). FilterSecurityInterceptor.doFilter

由于FilterSecurityInterceptor实现了Filter,所以,关心doFilter即可

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
	// 1. 直接把ServletRequest/ServletResponse/FilterChain 包装成:FilterInvocation对象.
	FilterInvocation fi = new FilterInvocation(request, response, chain);
	// **********************************************************************************************
	// 2. 调用invoke方法
	// **********************************************************************************************
	invoke(fi);
}

(6). FilterSecurityInterceptor.invoke


private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";
// **********************************************************************************************
// 1. FilterInvocationSecurityMetadataSource有点类似于SpringMVC中的:HandlerMapping,保存着所有的请求映射信息
//     /hello --> hasRole("ADMIN")
// **********************************************************************************************
private FilterInvocationSecurityMetadataSource securityMetadataSource;
private boolean observeOncePerRequest = true;


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 && observeOncePerRequest) {
			fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
		}
		
		// **************************************************************************************
		// 委托给父类(AbstractSecurityInterceptor)
		// **************************************************************************************
		InterceptorStatusToken token = super.beforeInvocation(fi);

		try {
			// 继续向下一个调用链传递调用.
			fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
		} finally {
			// 清场操作
			super.finallyInvocation(token);
		}
		
		super.afterInvocation(token, null);
	}
}

(7). AbstractSecurityInterceptor.beforeInvocation

protected InterceptorStatusToken beforeInvocation(Object object) {
	Assert.notNull(object, "Object was null");
	final boolean debug = logger.isDebugEnabled();
	
	
	if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
		throw new IllegalArgumentException("Security invocation attempted for object " + object.getClass().getName() + " but AbstractSecurityInterceptor only configured to support secure objects of type: " + getSecureObjectClass());
	}

	// *************************************************************************************************
	// 1. 查询发起的请求是否在元数据里
	// SecurityMetadataSource 里记录着所有的URL请求与元信息
	// /login.html --> permitAll
	// /hello      --> hasRole("ADMIN")
	// /**         --> permitAll
	// *************************************************************************************************
	Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
	
	// 相当于请求的URL信息找不到的情况下
	if (attributes == null || attributes.isEmpty()) {
		if (rejectPublicInvocations) { // 抛异常
			throw new IllegalArgumentException("Secure object invocation " + object + " was denied as public invocations are not allowed via this interceptor. " + "This indicates a configuration error because the " + "rejectPublicInvocations property is set to 'true'");
		}

		if (debug) {
			logger.debug("Public object - authentication not attempted");
		}

		// 发布事件
		publishEvent(new PublicInvocationEvent(object));

		return null; // no further work post-invocation
	}

	if (debug) {
		logger.debug("Secure object: " + object + "; Attributes: " + attributes);
	}
	
	
	// 在UsernamePasswordAuthenticationFilter里就已经设置了Authentication,所以,这边能拿到:Authentication
	if (SecurityContextHolder.getContext().getAuthentication() == null) {
		credentialsNotFound(messages.getMessage( "AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"),object, attributes);
	}
	
	// ***************************************************************************
	// 2. 在鉴权之前,必须先有做过认证来着的,如果没有认证,则会调用:Authentication进行认证.
	// ***************************************************************************
	Authentication authenticated = authenticateIfRequired();

	// Attempt authorization
	try {
		// ******************************************************************************************
		// 3. 委托给访问决策管理进行决定(AccessDecisionManager),这里是重点,但是,暂时先不分析,后面我会留一节,专门分析这个接口:AccessDecisionManager
		// ******************************************************************************************
		this.accessDecisionManager.decide(authenticated, object, attributes);
	} catch (AccessDeniedException accessDeniedException) {
		publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException));
		throw accessDeniedException;
	}

	if (debug) {
		logger.debug("Authorization successful");
	}
	
	// 发布:AuthorizedEvent,所以,可以无缝嵌入做一些你想做的事情.
	if (publishAuthorizationSuccess) {
		publishEvent(new AuthorizedEvent(object, attributes, authenticated));
	}

	// Attempt to run as a different user
	Authentication runAs = this.runAsManager.buildRunAs(authenticated, object , attributes);

	if (runAs == null) {  // true
		if (debug) {
			logger.debug("RunAsManager did not change Authentication object");
		}
		
		// 通过	InterceptorStatusToken包裹着:Authentication
		// no further work post-invocation
		return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
	} else { 
		// *****************************************************************************
		// 没太理解这个逻辑是想做什么?
		// *****************************************************************************
		if (debug) {
			logger.debug("Switching to RunAs Authentication: " + runAs);
		}

		SecurityContext origCtx = SecurityContextHolder.getContext();
		SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
		SecurityContextHolder.getContext().setAuthentication(runAs);

		// need to revert to token.Authenticated post-invocation
		return new InterceptorStatusToken(origCtx, true, attributes, object);
	} 
}

(8). AbstractSecurityInterceptor.authenticateIfRequired

private Authentication authenticateIfRequired() {
	// 从上下文中获得:Authentication
	Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
     
	// 看下是否已经认证
	if (authentication.isAuthenticated() && !alwaysReauthenticate) {
		if (logger.isDebugEnabled()) {
			logger.debug("Previously Authenticated: " + authentication);
		}
		// 认证过了的情况下,直接返回:Authentication
		return authentication;
	}
     
	//  到了这一步:代表上下文中的Authentication对象,还未被认证,需要调用:AuthenticationManager进行认证,前面有分析过,在这里就不多分析了.
	authentication = authenticationManager.authenticate(authentication);

	// We don't authenticated.setAuthentication(true), because each provider should do
	// that
	if (logger.isDebugEnabled()) {
		logger.debug("Successfully Authenticated: " + authentication);
	}

	// 然后,再把Authentication设置到上下文中.
	SecurityContextHolder.getContext().setAuthentication(authentication);

	return authentication;
}

(9). 总结

从对FilterSecurityInterceptor的源码剖析来看,它的主要职责如下: