spring security授权过滤器FilterSecurityInterceptor学习

发布时间 2023-05-21 19:39:12作者: 程序晓猿

在一个spring security应用中完成登录后再次访问资源时就会验证当前用户是否有权限访问资源,这个验证的

过程就是通过FilterSecurityInterceptor这个过滤器实现的。

一、spring security资源访问权限配置

首先FilterSecurityInterceptor这个过滤器最终还是会被配置到DefaultSecurityFilterChain中,所以可以猜测对资源的访问权限配置还是要通过HttpSecurity这个构建器来配置

1.1 使用ExpressionUrlAuthorizationConfigurer

创建一个配置类继承WebSecurityConfigurerAdapter

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    //用户配置,
    @Bean
    public UserDetailsService userDetailsService() {
        //在内存中配置用户
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("lyy").password("123").authorities("ROLE_P1").build());
        manager.createUser(User.withUsername("zs").password("456").authorities("ROLE_P2").build());
        return manager;
    }

    //密码加密方式配置
    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
    
    //安全配置
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        //匹配路径时越具体的路径要先匹配
        //放行登录页面,和处理登录请求的url
        http.authorizeRequests().antMatchers("/login","/login.html").permitAll()
                .antMatchers("/hello/test1").hasRole("P1")
                .anyRequest().permitAll()
            	.and()
                .formLogin().loginPage("/login.html").loginProcessingUrl("/login");
    }
    
}

上边的代码中,HttpSecurity#authorizeRequests()方法的返回值是一个ExpressionUrlAuthorizationConfigurer,所以后边控制资源访问权限的antMatchers,hasRole都是这个类的方法,通过这个configurer来对授权过滤器

FilterSecurityInterceptor进行配置。

1.2 使用UrlAuthorizationConfigurer

HttpSecurity中并没有提供使用此configurer的方法,所以使用它时需要自己创建configurer然后使用apply方法添加

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    //用户配置,
    @Bean
    public UserDetailsService userDetailsService() {
        //在内存中配置用户
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("lyy").password("123").authorities("ROLE_P1").build());
        manager.createUser(User.withUsername("zs").password("456").authorities("ROLE_P2").build());
        return manager;
    }

    //密码加密方式配置
    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    //安全配置
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        //自己创建UrlAuthorizationConfigurer然后apply到http中
        http.apply(new UrlAuthorizationConfigurer<>(null))
                .getRegistry().antMatchers("/login", "/login.html").anonymous()
                .antMatchers("/hello/test1").hasRole("P1")
                .and().formLogin().loginPage("/login.html").loginProcessingUrl("/login");
    }
}

这个configurer的功能没有上边哪个功能强大,但为了理解上边configurer的源码我们需要先看懂这个configer,它的源码相对来说比ExpressionUrlAuthorizationConfigurer简单。

二、FilterSecurityInterceptor的处理流程

先从整体上看下此过滤器处理认证的整个流程,先来简单看下这个类的源码,一个过滤器肯定要从doFilter方法开始看

public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements
    Filter {
    private FilterInvocationSecurityMetadataSource securityMetadataSource;
    private boolean observeOncePerRequest = true;
    
    public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
        //创建了一个FilterInvocation对象,
        //这个对象其实就是对request,response,chain这三个对象进行了一个封装,
        //内部保存了这三个属性
		FilterInvocation fi = new FilterInvocation(request, response, chain);
	    //执行了invoke方法
        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 && observeOncePerRequest) {
				fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
			}
			//在这个父类的beforeInvocation方法中完成了权限校验
			InterceptorStatusToken token = super.beforeInvocation(fi);

			try {
                 //这里是执行后续逻辑。
				fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
			}
			finally {
				super.finallyInvocation(token);
			}

			super.afterInvocation(token, null);
		}
	}
}

从上边可以看出授权的主要逻辑是在父类AbstractSecurityInterceptor#beforeInvocation中,所以继续看下这个类。

public abstract class AbstractSecurityInterceptor implements InitializingBean,
ApplicationEventPublisherAware, MessageSourceAware {
    
    //这个方法中完成对资源的授权
    //这个object实际上就是FilterInvocation对象,里边封装了request,response,chain
    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());
		}
		
        //这里是根据object对象中的request获取到当前request访问的资源需要哪些权限才能访问。
        //ConfigAttribute是一个接口,用来对资源的访问权限进行封装,这个后边再具体看。
        //这里先理解成attributes代表了当前要访问的资源需要哪些权限才能访问,这个是我们自己配置的,
        // 像1.1和1.2那样,针对/hello/test1这个资源,获取到的attributes中只有一个元素 ROLE_P1
		Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
				.getAttributes(object);
		// 对attributes进行校验
		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);
		}

		if (SecurityContextHolder.getContext().getAuthentication() == null) {
			credentialsNotFound(messages.getMessage(
					"AbstractSecurityInterceptor.authenticationNotFound",
					"An Authentication object was not found in the SecurityContext"),
					object, attributes);
		}
		//这个方法会检查当前的authentication对象是否是已经认证过的,如果没有
         //它会调用认证管理器尝试进行一次认证
		Authentication authenticated = authenticateIfRequired();

		// Attempt authorization
		try {
            //这里是真正的认证逻辑,调用决策管理器的决策方法来授权
			this.accessDecisionManager.decide(authenticated, object, attributes);
		}
		catch (AccessDeniedException accessDeniedException) {
			publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
					accessDeniedException));

			throw accessDeniedException;
		}
		//后边的这些可以先不看,不属于主线逻辑
		if (debug) {
			logger.debug("Authorization successful");
		}

		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) {
			if (debug) {
				logger.debug("RunAsManager did not change Authentication object");
			}

			// 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);
		}
	}
}

2.1 ConfigAttribute的获取

上边在梳理处理流程时提到了这一点

AbstractSecurityInterceptor部分源码

Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
				.getAttributes(object);

获取到的attributes就代表了当前资源哪些权限可以访问,比如针对1.1和1.2的配置获取到的就是ROLE_P1

先来看下当前类AbstractSecurityInterceptor#obtainSecurityMetadataSource方法

public abstract SecurityMetadataSource obtainSecurityMetadataSource();

这是一个抽象方法,具体实现是在子类FilterSecurityInterceptor

public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements
    Filter {
    
    private FilterInvocationSecurityMetadataSource securityMetadataSource;
    
    //它返回了自己的一个属性
    public SecurityMetadataSource obtainSecurityMetadataSource() {
		return this.securityMetadataSource;
	}
}

所以this.obtainSecurityMetadataSource()先获取到FilterInvocationSecurityMetadataSource,再传入object也就是最开始创建的FilterInvocation对象调用getAttributes方法来获取当前request请求的资源需要什么权限才能访问。

接着看下FilterInvocationSecurityMetadataSource

public interface FilterInvocationSecurityMetadataSource extends SecurityMetadataSource {
}

它也是一个接口,实现类有两个,当使用UrlAuthorizationConfigurer左配置时最终配置进去的实现类是

DefaultFilterInvocationSecurityMetadataSource

我们需要重点看下上边提到的getAttributes方法

public class DefaultFilterInvocationSecurityMetadataSource implements
		FilterInvocationSecurityMetadataSource {

	protected final Log logger = LogFactory.getLog(getClass());
	
    //这个map很重要,key:请求的匹配器,value:某个请求需要哪些权限才能访问
    // 例如 1.2的配置,在这个map中会有一个key能够匹配/hello/test1,而value是只有一个元素ROLE_P1
    // 的集合
	private final Map<RequestMatcher, Collection<ConfigAttribute>> requestMap;
    //构造方法,map是通过构造方法传进来的
    public DefaultFilterInvocationSecurityMetadataSource(
			LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap) {

		this.requestMap = requestMap;
	}
    
    //这就是上边获取ConfigAttribute时调用的方法
    public Collection<ConfigAttribute> getAttributes(Object object) {
        //从object中获取到request
		final HttpServletRequest request = ((FilterInvocation) object).getRequest();
        //拿到上边map中的所有key进行遍历,如果找到一个key能够匹配request就会返回
        //所以RequestMatcher在这个map中的顺序很重要,顺序靠前的就会被先匹配。
		for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap
				.entrySet()) {
			if (entry.getKey().matches(request)) {
				return entry.getValue();
			}
		}
        //找不到匹配的就返回null
		return null;
	}

}

到这里ConfigAttribute的获取就介绍完了。

2.2 决策管理器AccessDecisionManager

上边在讲解AbstractSecurityInterceptor#beforeInvocation方法时提到了真正对资源的授权是通过

AccessDecisionManager完成的,来看下这个的源码,它是一个接口

public interface AccessDecisionManager {

    //决策方法,入参有用户的authentication,object实际是FilterInvocation,ConfigAttribute
	void decide(Authentication authentication, Object object,
			Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
			InsufficientAuthenticationException;

	//此管理器是否支持这次授权
	boolean supports(ConfigAttribute attribute);


	boolean supports(Class<?> clazz);
}

AccessDecisionManager并不直接处理授权,它通过调用内部维护的投票器AccessDecisionVoter来授权,每个投票器的投票结果有通过,弃权,拒绝三种情况。所以针对多个投票器的结果就会有 一票通过,一票拒绝,少数服从多数这几种授权模式,对应的就是AccessDecisionManager的几个实现类。我们先来看下springsecurity默认使用的实现类AffirmativeBased,它采用的是一票通过模式

public class AffirmativeBased extends AbstractAccessDecisionManager {

    //通过构造方法传入投票器集合
	public AffirmativeBased(List<AccessDecisionVoter<? extends Object>> decisionVoters) {
		super(decisionVoters);
	}
    
    //此决策管理器的决策方法,实现自接口AccessDecisionManager
    public void decide(Authentication authentication, Object object,
			Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
		int deny = 0;
		//获取到所有的投票器然后循环
		for (AccessDecisionVoter voter : getDecisionVoters()) {
             //调用投票器的投票方法
			int result = voter.vote(authentication, object, configAttributes);

			if (logger.isDebugEnabled()) {
				logger.debug("Voter: " + voter + ", returned: " + result);
			}

			switch (result) {
             //通过
			case AccessDecisionVoter.ACCESS_GRANTED:
				return;//结束循环
			//拒绝并计数
			case AccessDecisionVoter.ACCESS_DENIED:
				deny++;

				break;

			default:
				break;
			}
		}
		//走到这里表示没有投票器同意,且至少有一个拒绝,所以抛出异常
		if (deny > 0) {
			throw new AccessDeniedException(messages.getMessage(
					"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
		}

		// To get this far, every AccessDecisionVoter abstained
        //所有投票器都弃权了,定义在父类中
		checkAllowIfAllAbstainDecisions();
	}
}

再接着看下父类的方法

public abstract class AbstractAccessDecisionManager implements AccessDecisionManager,
InitializingBean, MessageSourceAware {
    //维护的投票器列表
    private List<AccessDecisionVoter<? extends Object>> decisionVoters;

	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
	//这个用来处理全部投票器都弃权的情况
	private boolean allowIfAllAbstainDecisions = false;
    
    //所有投票器都弃权了
    protected final void checkAllowIfAllAbstainDecisions() {
		if (!this.isAllowIfAllAbstainDecisions()) {
			throw new AccessDeniedException(messages.getMessage(
					"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
		}
	}
    
    //只要有一个投票器支持这个ConfigAttribute此管理器就支持此次授权
    public boolean supports(ConfigAttribute attribute) {
		for (AccessDecisionVoter voter : this.decisionVoters) {
			if (voter.supports(attribute)) {
				return true;
			}
		}

		return false;
	}
}

到这里我们对决策管理器就有了比较清楚的认识,它通过调用内部的投票器来处理授权

2.3 AccessDecisionVoter 投票器

2.2说明了决策管理器通过调用投票器来完成授权

public interface AccessDecisionVoter<S> {
	// ~ Static fields/initializers
	// =====================================================================================

	int ACCESS_GRANTED = 1;同意
	int ACCESS_ABSTAIN = 0;弃权
	int ACCESS_DENIED = -1;拒绝


	boolean supports(ConfigAttribute attribute);
    
    boolean supports(Class<?> clazz);


	int vote(Authentication authentication, S object,
			Collection<ConfigAttribute> attributes);
}

我们先来看一个比较简单的,基于角色的实现RoleVoter

public class RoleVoter implements AccessDecisionVoter<Object> {

    //角色前缀
	private String rolePrefix = "ROLE_";



	public String getRolePrefix() {
		return rolePrefix;
	}

	/**
	 * Allows the default role prefix of <code>ROLE_</code> to be overridden. May be set
	 * to an empty value, although this is usually not desirable.
	 *
	 * @param rolePrefix the new prefix
	 */
	public void setRolePrefix(String rolePrefix) {
		this.rolePrefix = rolePrefix;
	}

    //是否支持某个ConfigAttribute的判断
	public boolean supports(ConfigAttribute attribute) {
		if ((attribute.getAttribute() != null)
				&& attribute.getAttribute().startsWith(getRolePrefix())) {
			return true;
		}
		else {
			return false;
		}
	}

	
	public boolean supports(Class<?> clazz) {
		return true;
	}

    //真正的决策方法
	public int vote(Authentication authentication, Object object,
			Collection<ConfigAttribute> attributes) {
		if (authentication == null) {
			return ACCESS_DENIED;
		}
		int result = ACCESS_ABSTAIN;//先默认是弃权
        //获取当前用户的权限集合,像上边第一章使用默认的用户配置时获取到的实际就是角色集合
		Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication);
		// 针对第一章的配置,这里的attributes就是一堆角色,表示能访问资源的角色集合
        //遍历
		for (ConfigAttribute attribute : attributes) {
             //此投票器是否是否支持此ConfigAttribute
			if (this.supports(attribute)) {
				result = ACCESS_DENIED;

				// 遍历用户的权限(角色)集合,尝试找到一个匹配的
				for (GrantedAuthority authority : authorities) {
					if (attribute.getAttribute().equals(authority.getAuthority())) {
						return ACCESS_GRANTED;
					}
				}
			}
		}

		return result;
	}

	Collection<? extends GrantedAuthority> extractAuthorities(
			Authentication authentication) {
        //获取用户的权限数据。
		return authentication.getAuthorities();
	}
}

到这里FilterSecurityInterceptor对授权的处理流程就分析清除了,接下来继续看下它是如何被配置出来的。

三、FilterSecurityInterceptor的配置方式

springsecurity中还是要使用SecurityConfigurer 来对FilterSecurityInterceptor进行配置,具体的配置方式就像1.1和1.2演示的那样,这一节我们来分析下配置的原理。

3.1 UrlAuthorizationConfigurer

这个配置的原理相对简单,先看下源码

public final class UrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>> extends
		AbstractInterceptUrlConfigurer<UrlAuthorizationConfigurer<H>, H> {
    //这个属性用来对资源的权限进行配置,1.2节就是通过调用下边的getRegistry方法
    //获取到这个对象,然后调用它上边的antMatchers方法对资源的访问权限进行控制。
	private final StandardInterceptUrlRegistry REGISTRY;
	
	public UrlAuthorizationConfigurer(ApplicationContext context) {
		this.REGISTRY = new StandardInterceptUrlRegistry(context);
	}
    
    public StandardInterceptUrlRegistry getRegistry() {
		return REGISTRY;
	}
}

所以先看下这个类的源码来看下指定访问权限时如何实现的

3.1.1 StandardInterceptUrlRegistry

这个类是UrlAuthorizationConfigurer中的内部类,它的继承体系是这样的:

StandardInterceptUrlRegistry --> AbstractInterceptUrlConfigurer.AbstractInterceptUrlRegistry(也是内部类)

---> AbstractConfigAttributeRequestMatcherRegistry ---> AbstractRequestMatcherRegistry

上边1.2的配置是从StandardInterceptUrlRegistry#antMatchers方法开始的,所以我们也从这个方法开始看,

这个方法定义在顶层父类AbstractRequestMatcherRegistry中

public abstract class AbstractRequestMatcherRegistry<C> {
    
    public C antMatchers(String... antPatterns) {
        // 其中RequestMatchers.antMatchers方法是把传进来的多个路径创建成RequestMatcher对象集合
		return chainRequestMatchers(RequestMatchers.antMatchers(antPatterns));
	}
    
    //这是一个抽象方法,由其子类AbstractConfigAttributeRequestMatcherRegistry来实现
    protected abstract C chainRequestMatchers(List<RequestMatcher> requestMatchers);
}

AbstractConfigAttributeRequestMatcherRegistry

public abstract class AbstractConfigAttributeRequestMatcherRegistry<C> extends
		AbstractRequestMatcherRegistry<C> {
    //这个属性很重要,维护了多个UrlMapping,UrlMapping是这个类的内部类
	private List<UrlMapping> urlMappings = new ArrayList<>();
    
    //内部类UrlMapping维护了一个RequestMatcher和多个ConfigAttribute的对应关系,
    //可以简单理解成一个RequestMatcher和多个角色的对应关系,即某个路径需要哪些角色才能访问
    static final class UrlMapping {
        
		private RequestMatcher requestMatcher;
		private Collection<ConfigAttribute> configAttrs;

		UrlMapping(RequestMatcher requestMatcher, Collection<ConfigAttribute> configAttrs) {
			this.requestMatcher = requestMatcher;
			this.configAttrs = configAttrs;
		}

		public RequestMatcher getRequestMatcher() {
			return requestMatcher;
		}

		public Collection<ConfigAttribute> getConfigAttrs() {
			return configAttrs;
		}
	}
    
    //这个方法就是父类antMatchers最终会调的方法
    protected final C chainRequestMatchers(List<RequestMatcher> requestMatchers) {
		this.unmappedMatchers = requestMatchers;
        //重点看这个
		return chainRequestMatchersInternal(requestMatchers);
	}
    //抽象方法,子类ExpressionInterceptUrlRegistry和StandardInterceptUrlRegistry实现,
    //这里分析的是StandardInterceptUrlRegistry,先看这个类中的实现
    protected abstract C chainRequestMatchersInternal(List<RequestMatcher> requestMatchers);
    
    //这个方法用来给上边的属性urlMappings添加值,后边会被子类调用
    final void addMapping(UrlMapping urlMapping) {
		this.unmappedMatchers = null;
		this.urlMappings.add(urlMapping);
	}
    
    //这个方法用来根据urlMappings构建出一个Map
    final LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> createRequestMap() {
		if (unmappedMatchers != null) {
			throw new IllegalStateException(
					"An incomplete mapping was found for "
							+ unmappedMatchers
							+ ". Try completing it with something like requestUrls().<something>.hasRole('USER')");
		}

        //创建出一个LinkedHashMap保存 RequestMatcher和对应的权限的映射关系
		LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>();
		for (UrlMapping mapping : getUrlMappings()) {
            //遍历urlMappings取出其中每个urlMapping对象
			RequestMatcher matcher = mapping.getRequestMatcher();
			Collection<ConfigAttribute> configAttrs = mapping.getConfigAttrs();
		   //加入map中
            requestMap.put(matcher, configAttrs);
		}
        //返回,这个map后边会使用到
		return requestMap;
	}
}

StandardInterceptUrlRegistry

public class StandardInterceptUrlRegistry
			extends
    ExpressionUrlAuthorizationConfigurer<H>.AbstractInterceptUrlRegistry<StandardInterceptUrlRegistry, AuthorizedUrl> {
    	@Override
		protected final AuthorizedUrl chainRequestMatchersInternal(
				List<RequestMatcher> requestMatchers) {
             //可以看到最终传入requestMatchers后new了一个AuthorizedUrl对象然后返回
			return new AuthorizedUrl(requestMatchers);
		}
}

可以看到最终new了一个AuthorizedUrl对象然后返回,就是说StandardInterceptUrlRegistry#antMatchers方法最终返回了一个AuthorizedUrl对象,然后我们会调用这个对象的hasRole等方法指定访问权限,AuthorizedUrl也是UrlAuthorizationConfigurer中的内部类

public class AuthorizedUrl {
		private final List<? extends RequestMatcher> requestMatchers;

		
		private AuthorizedUrl(List<? extends RequestMatcher> requestMatchers) {
			Assert.notEmpty(requestMatchers,
					"requestMatchers must contain at least one value");
			this.requestMatchers = requestMatchers;
		}

		public StandardInterceptUrlRegistry hasRole(String role) {
            //调用本类access方法,
            //UrlAuthorizationConfigurer.hasRole给传进来的role前边拼上前缀ROLE_
			return access(UrlAuthorizationConfigurer.hasRole(role));
		}
    
    	public StandardInterceptUrlRegistry access(String... attributes) {
            //addMapping是调用UrlAuthorizationConfigurer#addMapping方法
            //SecurityConfig.createList会把传入的字符串创建成SecurityConfig集合返回
            //SecurityConfig简单理解成对String类型的attribute进行了封装,也就是把角色包了一层
			addMapping(requestMatchers, SecurityConfig.createList(attributes));
            //返回REGISTRY用来支持链式调用
			return UrlAuthorizationConfigurer.this.REGISTRY;
		}
}

所以我们继续回到UrlAuthorizationConfigurer中看addMapping方法

public final class UrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>> extends
    AbstractInterceptUrlConfigurer<UrlAuthorizationConfigurer<H>, H> {
    
    //这个方法有两个参数,参数1:requestMatcher集合,参数2:ConfigAttribute集合
    private StandardInterceptUrlRegistry addMapping(
			Iterable<? extends RequestMatcher> requestMatchers,
			Collection<ConfigAttribute> configAttributes) {
        //遍历requestMatcher集合,针对每一个requestMatcher都创建一个UrlMapping对象
        //上边提到了UrlMapping用来封装了 requestMatcher-->多个configAttribute的对应关系,
        //可以简单理解成 某个路径-->有多个角色都可以访问此路径
		for (RequestMatcher requestMatcher : requestMatchers) {
		   //调用的是StandardInterceptUrlRegistry#addMaping方法
            REGISTRY.addMapping(new AbstractConfigAttributeRequestMatcherRegistry.UrlMapping(
					requestMatcher, configAttributes));
		}
		return REGISTRY;
	}
}

StandardInterceptUrlRegistry#addMaping方法,这个方法实现在父类AbstractConfigAttributeRequestMatcherRegistry中,上边提到过,用来给其中的属性urlMappings添加值,所以到这里我们就知道了这个urlMappings中添加的是 某个路径--->有多个角色都可以访问此路径 这种映射关系。

到这里StandardInterceptUrlRegistry#antMatchers和 AuthorizedUrl#hasRole方法就分析完了,它们俩用来指定某个资源的访问权限,这种资源和可以访问的权限的映射关系最终会被添加到

AbstractConfigAttributeRequestMatcherRegistry.urlMappings 中,后续会使用到它

3.1.2 UrlAuthorizationConfigurer#configur方法

它是一个SecurityConfigurer,所以一定会有一个configure方法,这个方法定义在它的父类AbstractInterceptUrlConfigurer中

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对象,
		//createMetadataSource是一个抽象方法,具体实现在子类UrlAuthorizationConfigurer中
		FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource(http);
		if (metadataSource == null) {
			return;
		}
		//这里创建了我们关心的FilterSecurityInterceptor
		FilterSecurityInterceptor securityInterceptor = createFilterSecurityInterceptor(
				http, metadataSource, http.getSharedObject(AuthenticationManager.class));
		if (filterSecurityInterceptorOncePerRequest != null) {
			securityInterceptor
					.setObserveOncePerRequest(filterSecurityInterceptorOncePerRequest);
		}
		//后处理可以先不关注
		securityInterceptor = postProcess(securityInterceptor);
		//把这个过滤器添加到HttpSecurity中,最终会添加到过滤器链中
		http.addFilter(securityInterceptor);
		http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor);
	}
	
	//创建FilterSecurityInterceptor的方法
	private FilterSecurityInterceptor createFilterSecurityInterceptor(H http,
			FilterInvocationSecurityMetadataSource metadataSource,
			AuthenticationManager authenticationManager) throws Exception {
		FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor();
		//设置metadataSource,用来查询某个request哪些权限才能访问
		securityInterceptor.setSecurityMetadataSource(metadataSource);
		//设置决策管理器
		securityInterceptor.setAccessDecisionManager(getAccessDecisionManager(http));
		//设置认证管理器
		securityInterceptor.setAuthenticationManager(authenticationManager);
		securityInterceptor.afterPropertiesSet();
		return securityInterceptor;
	}
	
	private AccessDecisionManager getAccessDecisionManager(H http) {
		if (accessDecisionManager == null) {
			accessDecisionManager = createDefaultAccessDecisionManager(http);
		}
		return accessDecisionManager;
	}
	
	private AccessDecisionManager createDefaultAccessDecisionManager(H http) {
	    //getDecisionVoters这个方法由子类UrlAuthorizationConfigurer实现
		AffirmativeBased result = new AffirmativeBased(getDecisionVoters(http));
		return postProcess(result);
	}
}

UrlAuthorizationConfigurer中部分方法

public final class UrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>> extends
    AbstractInterceptUrlConfigurer<UrlAuthorizationConfigurer<H>, H> {
    
    @Override
	FilterInvocationSecurityMetadataSource createMetadataSource(H http) {
        //这里直接new了一个DefaultFilterInvocationSecurityMetadataSource对象,
        //构造方法传递了一个map,这个map在FilterSecurityInterceptor中用来根据请求
        //查询此请求哪些权限才能访问,这个对象的作用2.1节有介绍
        //REGISTRY.createRequestMap()创建了一个请求(RequestMatcher)
        //和权限(ConfigAttribute)的映射map,
        //这个方法定义在AbstractConfigAttributeRequestMatcherRegistry中,
		return new DefaultFilterInvocationSecurityMetadataSource(
				REGISTRY.createRequestMap());
	}
    
    //这个方法创建了两个投票前然后返回
    final List<AccessDecisionVoter<? extends Object>> getDecisionVoters(H http) {
		List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<AccessDecisionVoter<? extends Object>>();
		decisionVoters.add(new RoleVoter());
		decisionVoters.add(new AuthenticatedVoter());
		return decisionVoters;
	}
    
}

到这里使用UrlAuthorizationConfigurer对资源权限进行配置的流程就介绍完了,只要理解了使用时的流程:

(1) 先new 一个UrlAuthorizationConfigurer对象

(2) 调用getRegistry方法返回内部的StandardInterceptUrlRegistry

(3)调用StandardInterceptUrlRegistry#antMatchers方法返回AuthorizedUrl对象

(4)调用AuthorizedUrl#hasRole等方法添加权限

对照这个流程来看UrlAuthorizationConfigurer的源码就好理解了。

3.2 ExpressionUrlAuthorizationConfigurer

注意这个configurer提供基于表达式的权限控制,所以重点在表达式这三个字上,需要先来了解下spring对表达式的解析。

3.2.1 SpelExpressionParser的使用

spring提供了一个SpelExpressionParser 对象可以用来对表达式进行解析,我们先来了解下它的基本使用

	    SpelExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression("1+2");
        //执行表达式
        Object value = expression.getValue();
        //控制台输出表达式的计算结果
        System.out.println(value);

上边演示了SpelExpressionParser这个对象的基本使用,可以看到这个对象可以用来解析表达式,然后计算表达式。

接着来看下这个parser的其他用法,在表达式中使用变量,StandardEvaluationContext对象的使用

		SpelExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(" 'my name is ' + #username");
        //创建context,通过context给表达式中传入变量
        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setVariable("username","tom");
        //执行表达式
        Object value = expression.getValue(context);
        //控制台输出表达式的计算结果
        System.out.println(value);

接着来看下执行表达式时使用rootObject对象

public class MyTest {

    static class Person {
        public String username;
        public List<String> roles;

        public Person(String username, List<String> roles) {
            this.username = username;
            this.roles = roles;
        }

        public boolean hasRole(String role){
            return roles.contains(role);
        }
    }

    public static void main(String[] args) {
        SpelExpressionParser parser = new SpelExpressionParser();
        Expression expression1 = parser.parseExpression(" 'my name is ' + username");
        //创建context
        StandardEvaluationContext context = new StandardEvaluationContext();
        List<String> roles = new ArrayList<>();
        roles.add("P1");
        roles.add("P2");
        //创建rootObject
        Person person = new Person("张三",roles);
        context.setRootObject(person);
        //执行表达式,表达式中可以直接访问rootObject中的属性
        Object value = expression1.getValue(context);
        //控制台输出表达式的计算结果
        System.out.println(value);
        //创建表达式,表达式中可以直接调用rootObject中的方法
        Expression expression2 = parser.parseExpression("hasRole('P1')");
        Object res = expression2.getValue(context);
        //输出结果
        System.out.println(res);
    }
}

上边演示了表达式中直接调用rootObject中的方法,可以猜测spring security中的hasRole等表达式就是这样执行的。

我们先来回顾下1.1中提到的spring security基于表达式的权限控制配置

http.authorizeRequests().antMatchers("/hello/test1").hasRole("P1")

3.2.2 http.authorizeRequests方法分析

这个方法是HttpSecurity这个构建器中的一个方法,我们看下源码

public ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests()
			throws Exception {
		ApplicationContext context = getContext();
        //创建了一个ExpressionUrlAuthorizationConfigurer对象
        // getOrApply方法返回的是我们传入的这个对象,
        // 所以 getRegistry()实际是 ExpressionUrlAuthorizationConfigurer中的方法
		return getOrApply(new ExpressionUrlAuthorizationConfigurer<>(context))
				.getRegistry();
	}

3.2.3 ExpressionUrlAuthorizationConfigurer.getRegistry方法分析

直接看下ExpressionUrlAuthorizationConfigurer源码

public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>>
		extends
        AbstractInterceptUrlConfigurer<ExpressionUrlAuthorizationConfigurer<H>, H> {
    
    //这个类中提前定义好的几个表达式
	private static final String denyAll = "denyAll";
	private static final String anonymous = "anonymous";
	private static final String authenticated = "authenticated";
	private static final String fullyAuthenticated = "fullyAuthenticated";
	private static final String rememberMe = "rememberMe";

	private final ExpressionInterceptUrlRegistry REGISTRY;

	private SecurityExpressionHandler<FilterInvocation> expressionHandler;

	/**
	 * Creates a new instance
	 * @see HttpSecurity#authorizeRequests()
	 */
	public ExpressionUrlAuthorizationConfigurer(ApplicationContext context) {
	    //构造方法中创建了ExpressionInterceptUrlRegistry对象
		this.REGISTRY = new ExpressionInterceptUrlRegistry(context);
	}

	public ExpressionInterceptUrlRegistry getRegistry() {
	    //返回本类中的属性
		return REGISTRY;
	}
        
}

所以可以知道上边1.1使用此configurer进行权限控制时调用的antMatchers方法是ExpressionInterceptUrlRegistry中的,继续看这个类的源码

3.2.4 ExpressionInterceptUrlRegistry

ExpressionInterceptUrlRegistry是ExpressionUrlAuthorizationConfigurer中的内部类。这个类的继承体系和3.1.1中提到的StandardInterceptUrlRegistry一样,

所以antMatchers方法也在父类AbstractRequestMatcherRegistry中,其调用过程中的层层继承也和3.1.1一样,

最终会调用到ExpressionInterceptUrlRegistry#chainRequestMatchersInternal这个方法

public class ExpressionInterceptUrlRegistry
			extends
    ExpressionUrlAuthorizationConfigurer<H>.AbstractInterceptUrlRegistry<ExpressionInterceptUrlRegistry, AuthorizedUrl> {
    
    @Override
    protected final AuthorizedUrl chainRequestMatchersInternal(
        List<RequestMatcher> requestMatchers) {
        //创建了AuthorizedUrl,它是ExpressionUrlAuthorizationConfigurer的内部类,
        // 所以hasRole等方法就是调用这个AuthorizedUrl#hasRole
        return new AuthorizedUrl(requestMatchers);
    }
}

3.2.5 ExpressionUrlAuthorizationConfigurer.AuthorizedUrl

hasRole等方法就是调用这个AuthorizedUrl#hasRole,继续看下这个内部类的部分源码

public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>>
		extends
        AbstractInterceptUrlConfigurer<ExpressionUrlAuthorizationConfigurer<H>, H> {
    
    //内部类AuthorizedUrl
    public class AuthorizedUrl {
         //这个requestMatchers就是根据调用antMatchers方法时传入的路径生成的
		private List<? extends RequestMatcher> requestMatchers;
		private boolean not;
		// 看下这个hasRole方法的逻辑
		public ExpressionInterceptUrlRegistry hasRole(String role) {
			//调用自己的access方法
			// 可以猜测这个ExpressionUrlAuthorizationConfigurer.hasRole会根据传入的
			// 角色生成一个表达式字符串
			return access(ExpressionUrlAuthorizationConfigurer.hasRole(role));
		}
		
		public ExpressionInterceptUrlRegistry access(String attribute) {
			if (not) {
				attribute = "!" + attribute;
			}
			//这个SecurityConfig.createList会根据attribute字符串生成一个,
			// ConfigAttribute集合,和3.1节类似,ConfigAttribute封装了传入的attribute
			//interceptUrl是外部类ExpressionUrlAuthorizationConfigurer中的方法
			interceptUrl(requestMatchers, SecurityConfig.createList(attribute));
			//返回resgistry方便链式调用
			return ExpressionUrlAuthorizationConfigurer.this.REGISTRY;
		}
	}
	
	//这个方法根据传入的角色拼接一个字符串表达式,后边授权时会通过rootObject执行这个表达式,
	//像3.2.1那样
	private static String hasRole(String role) {
		Assert.notNull(role, "role cannot be null");
		if (role.startsWith("ROLE_")) {
			throw new IllegalArgumentException(
					"role should not start with 'ROLE_' since it is automatically inserted. Got '"
							+ role + "'");
		}
		return "hasRole('ROLE_" + role + "')";
	}
	
	
	private void interceptUrl(Iterable<? extends RequestMatcher> requestMatchers,
			Collection<ConfigAttribute> configAttributes) {
		//这个方法循环传入的requestMatchers集合,为每一个requestMatcher都执行
		//REGISTRY.addMapping方法,添加一个UrlMapping到
		// AbstractConfigAttributeRequestMatcherRegistry.urlMappings属性中
		// 这部分逻辑和3.1.1的类似
		for (RequestMatcher requestMatcher : requestMatchers) {
			REGISTRY.addMapping(new AbstractConfigAttributeRequestMatcherRegistry.UrlMapping(
					requestMatcher, configAttributes));
		}
	}
        
}

3.2.6 ExpressionUrlAuthorizationConfigurer#configure方法

此方法定义在父类AbstractInterceptUrlConfigurer中

abstract class AbstractInterceptUrlConfigurer{
    @Override
	public void configure(H http) throws Exception {
        //创建metadataSource对象,createMetadataSource是抽象方法,这次我们看下
        // ExpressionUrlAuthorizationConfigurer中的实现
		FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource(http);
		if (metadataSource == null) {
			return;
		}
         //创建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);
	}
    //这是创建filter的方法
    private FilterSecurityInterceptor createFilterSecurityInterceptor(H http,
			FilterInvocationSecurityMetadataSource metadataSource,
			AuthenticationManager authenticationManager) throws Exception {
		FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor();
		securityInterceptor.setSecurityMetadataSource(metadataSource);
        //这里设置决策管理器,注意看getAccessDecisionManager方法
		securityInterceptor.setAccessDecisionManager(getAccessDecisionManager(http));
		securityInterceptor.setAuthenticationManager(authenticationManager);
		securityInterceptor.afterPropertiesSet();
		return securityInterceptor;
	}
    //创建决策管理器
    private AccessDecisionManager getAccessDecisionManager(H http) {
		if (accessDecisionManager == null) {
			accessDecisionManager = createDefaultAccessDecisionManager(http);
		}
		return accessDecisionManager;
	}
    
    private AccessDecisionManager createDefaultAccessDecisionManager(H http) {
        //创建了一个决策管理器,传入投票器,投票器在授权过滤器中会被使用,第2章有相关描述
        // getDecisionVoters是抽象方法需要被子类实现,这次看下,
        // ExpressionUrlAuthorizationConfigurer中的实现
		AffirmativeBased result = new AffirmativeBased(getDecisionVoters(http));
		return postProcess(result);
	}
}

ExpressionUrlAuthorizationConfigurer

public class ExpressionUrlAuthorizationConfigurer {
    @Override
	final ExpressionBasedFilterInvocationSecurityMetadataSource createMetadataSource(
			H http) {
		//把AbstractConfigAttributeRequestMatcherRegistry里边的属性urlMapping
		//生成一个LinkedHashMap返回
		LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = REGISTRY
				.createRequestMap();
		if (requestMap.isEmpty()) {
			throw new IllegalStateException(
					"At least one mapping is required (i.e. authorizeRequests().anyRequest().authenticated())");
		}
		//创建一个ExpressionBasedFilterInvocationSecurityMetadataSource返回,
		//后边授权的时候会从这个对象中拿出ConfigAttribute,它内部封装了类似 hasRole('P1'),
		//这样的表达式
		//这个getExpressionHandler会返回一个SecurityExpressionHandler的对象,它在
		//执行表达式的时候会使用到
		return new ExpressionBasedFilterInvocationSecurityMetadataSource(requestMap,
				getExpressionHandler(http));
	}
	
	@Override
	@SuppressWarnings("rawtypes")
	final List<AccessDecisionVoter<? extends Object>> getDecisionVoters(H http) {
		List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<AccessDecisionVoter<? extends Object>>();
		//注意看这里是创建了一个WebExpressionVoter,和UrlAuthorizationConfigurer中,
		//的实现是不一样的。
		WebExpressionVoter expressionVoter = new WebExpressionVoter();
		expressionVoter.setExpressionHandler(getExpressionHandler(http));
		decisionVoters.add(expressionVoter);
		return decisionVoters;
	}
}

SecurityExpressionHandler的源码,它用来获取执行表达式时需要的ExpressionParser,EvaluationContext,

它俩的作用参考3.1节的示例

public interface SecurityExpressionHandler<T> extends AopInfrastructureBean {
	
	ExpressionParser getExpressionParser();

	
	EvaluationContext createEvaluationContext(Authentication authentication, T invocation);
}

3.2.7 WebExpressionVoter的分析

这个基于表达式的投票器是我们在使用ExpressionUrlAuthorizationConfigurer配置权限时最终处理授权的投票器我们来看下它是如何实现的,着重看vote方法

public class WebExpressionVoter implements AccessDecisionVoter<FilterInvocation> {
	private SecurityExpressionHandler<FilterInvocation> expressionHandler = new DefaultWebSecurityExpressionHandler();

	public int vote(Authentication authentication, FilterInvocation fi,
			Collection<ConfigAttribute> attributes) {
		assert authentication != null;
		assert fi != null;
		assert attributes != null;
		//这个方法可以简单理解成取attributes中的第一个元素然后强转成WebExpressionConfigAttribute,
        //实际上attributes中正常只会有且仅有一个WebExpressionConfigAttribute类型的元素
		WebExpressionConfigAttribute weca = findConfigAttribute(attributes);

		if (weca == null) {
			return ACCESS_ABSTAIN;
		}

        //获取EvaluationContext对象,这是执行表达式时要用到的,我们重点
        //看下它里边设置了哪个rootObject,授权的哪些表达式都定义在rootObject中
        // 这个方法定义在AbstractSecurityExpressionHandler#createEvaluationContext中
		EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,
				fi);
		ctx = weca.postProcess(ctx, fi);
		//执行表达式,表达式的结果转换为投票器的处理结果
		return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED
				: ACCESS_DENIED;
	}
}

3.2.7.1 rootObject的创建

AbstractSecurityExpressionHandler#createEvaluationContext方法中创建了rootObject

public final EvaluationContext createEvaluationContext(Authentication authentication,
			T invocation) {
        //这里就是创建了RootObject SecurityExpressionOperations是一个接口,
        //使用ExpressionUrlAuthorizationConfigurer配置时实现类是WebSecurityExpressionRoot
		SecurityExpressionOperations root = createSecurityExpressionRoot(authentication,
				invocation);
		StandardEvaluationContext ctx = createEvaluationContextInternal(authentication,
				invocation);
		ctx.setBeanResolver(br);
		ctx.setRootObject(root);

		return ctx;
}

WebSecurityExpressionRoot继承自SecurityExpressionRoot,授权表达式对应的哪些方法定义在这个类中

SecurityExpressionRoot源码

public abstract class SecurityExpressionRoot implements SecurityExpressionOperations {
	protected final Authentication authentication;
	private AuthenticationTrustResolver trustResolver;
	private RoleHierarchy roleHierarchy;
	private Set<String> roles;
	private String defaultRolePrefix = "ROLE_";

	//这是permitAll方法,它永远返回true,当我们配置时使用了
    // authorizeRequests().antMatchers("/login", "/login.html").permitAll()这样的配置,
    //授权过程最终执行表达式时就会走到这个方法,返回true然后允许访问
    public final boolean permitAll() {
		return true;
	}
    
    //这是hasRole方法,授权时最终会调用这个方法判断当期用户的角色
    public final boolean hasRole(String role) {
		return hasAnyRole(role);
	}

	public final boolean hasAnyRole(String... roles) {
		return hasAnyAuthorityName(defaultRolePrefix, roles);
	}

	private boolean hasAnyAuthorityName(String prefix, String... roles) {
		Set<String> roleSet = getAuthoritySet();
		for (String role : roles) {
			String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
			if (roleSet.contains(defaultedRole)) {
				return true;
			}
		}
		return false;
	}

}

3.2.7.2ExpressionUtils.evaluateAsBoolean 分析

这个方法最终执行了表达式

public final class ExpressionUtils {

	public static boolean evaluateAsBoolean(Expression expr, EvaluationContext ctx) {
		try {
		   //和3.2.1中讲解的类似
			return ((Boolean) expr.getValue(ctx, Boolean.class)).booleanValue();
		}
		catch (EvaluationException e) {
			throw new IllegalArgumentException("Failed to evaluate expression '"
					+ expr.getExpressionString() + "'", e);
		}
	}
}

以上这些就是对ExpressionUrlAuthorizationConfigurer的使用过程的分析

四 总结

本文记录了spring security中处理权限控制的过滤器FilterSecurityInterceptor的执行流程,

对此过滤器进行配置的两种方式 ExpressionUrlAuthorizationConfigurer和UrlAuthorizationConfigurer

的使用和源码分析。