Spring Security中一般有两种方式来对访问进行鉴权,第一种是在HttpSecurity上配置路径和相应访问所需的权限,第二种是在方法上添加注解来定义接口的访问权限


一、注解定义权限


1、让鉴权注解生效

要能通过在方法上添加注解来控制访问权限,首先就是要让注解可以被识别到。在Spring Security中是通过MethodInterceptor来实现对方法执行的拦截以及解析方法上的注解的。

有以下两种方式来添加各个MethodInterceptor


方式1:让注解生效的XML解析器

在继续前我们先看下MethodSecurityBeanDefinitionParser,该类本身是一个BeanDefinitionParser类型,可见其作用是解析Bean的定义。然而,这个类是怎么被使用的呢,这就需要看spring-security-config下的META-INF\spring.handlers文件了。该文件的配置如下所示:

http\://www.springframework.org/schema/security
  =org.springframework.security.config.SecurityNamespaceHandler

我们知道这个配置的是用于解析xml命名空间的解析器,对于以前通过spring-security.xml来配置的时候,这个就可以用于解析<method-security>元素了。例如有以下配置

<method-security jsr250-enabled="true", secured-enabled="true">

在被MethodSecurityBeanDefinitionParser解析后就能知道是否要启用jsr250注解和secured注解了,如果开启了则会创建 Jsr250AuthorizationMethodInterceptor和SecuredAuthorizationMethodInterceptor的BeanDefinition信息,从而被添加到IOC容器。

public final class SecurityNamespaceHandler 
  implements NamespaceHandler {

  @Override
	public void init() {
		loadParsers();
	}

  //加载解析器
	private void loadParsers() {
		// Parsers
		this.parsers.put(Elements.LDAP_PROVIDER, new LdapProviderBeanDefinitionParser());
		this.parsers.put(Elements.LDAP_SERVER, new LdapServerBeanDefinitionParser());
		this.parsers.put(Elements.LDAP_USER_SERVICE, new LdapUserServiceBeanDefinitionParser());
		this.parsers.put(Elements.USER_SERVICE, new UserServiceBeanDefinitionParser());
		this.parsers.put(Elements.JDBC_USER_SERVICE, new JdbcUserServiceBeanDefinitionParser());
		this.parsers.put(Elements.AUTHENTICATION_PROVIDER, new AuthenticationProviderBeanDefinitionParser());
		this.parsers.put(Elements.GLOBAL_METHOD_SECURITY, new GlobalMethodSecurityBeanDefinitionParser());
    //注意:这里被添加进去的
		this.parsers.put(Elements.METHOD_SECURITY, new MethodSecurityBeanDefinitionParser());
		this.parsers.put(Elements.AUTHENTICATION_MANAGER, new AuthenticationManagerBeanDefinitionParser());
		this.parsers.put(Elements.METHOD_SECURITY_METADATA_SOURCE,
				new MethodSecurityMetadataSourceBeanDefinitionParser());
		if (ClassUtils.isPresent(FILTER_CHAIN_PROXY_CLASSNAME, getClass().getClassLoader())) {
			loadWebParsers();
		}
		if (ClassUtils.isPresent(MESSAGE_CLASSNAME, getClass().getClassLoader())) {
			loadWebSocketParsers();
		}
	}
}

更多细节可查阅MethodSecurityBeanDefinitionParser的源码


方式2:让注解生效的Configuration

上面有讲到采用xml文件配置时,会通过MethodSecurityBeanDefinitionParser来解析元素并创建对应的MethodInterceptor,从而可以拦截方法的执行来验证需要的权限。

除此以外,现在最常用的就是通过配置类来完成Bean的注册了,那么通过配置类是怎么做的呢,答案从@EnableMethodSecurity注解出发

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MethodSecuritySelector.class)
public @interface EnableMethodSecurity {
  	boolean prePostEnabled() default true;
  	boolean securedEnabled() default false;
  	boolean jsr250Enabled() default false;
  	boolean proxyTargetClass() default false;
  	AdviceMode mode() default AdviceMode.PROXY;
}

注解上Import了MethodSecuritySelector

final class MethodSecuritySelector implements ImportSelector {

  @Override
	public String[] selectImports(@NonNull AnnotationMetadata importMetadata) {
		if (!importMetadata.hasAnnotation(EnableMethodSecurity.class.getName())) {
			return new String[0];
		}
    //获取注解
		EnableMethodSecurity annotation = importMetadata.getAnnotations().get(EnableMethodSecurity.class).synthesize();
		List<String> imports = new ArrayList<>(Arrays.asList(this.autoProxy.selectImports(importMetadata)));
    //判断注解上的配置,也就是里面的几个属性
		if (annotation.prePostEnabled()) {
      //如果prePostEnabled设置为ture
      //则添加PrePostMethodSecurityConfiguration配置类
			imports.add(PrePostMethodSecurityConfiguration.class.getName());
		}
		if (annotation.securedEnabled()) {
      //如果securedEnabled设置为ture
      //则添加SecuredMethodSecurityConfiguration配置类
			imports.add(SecuredMethodSecurityConfiguration.class.getName());
		}
		if (annotation.jsr250Enabled()) {
      //如果jsr250Enabled设置为ture
      //则添加Jsr250MethodSecurityConfiguration配置类
			imports.add(Jsr250MethodSecurityConfiguration.class.getName());
		}
		return imports.toArray(new String[0]);
	}
}

可见,会根据我们对注解的设置而添加不同的配置类

  • 在PrePostMethodSecurityConfiguration中,完成了以下Bean的创建
  • PreFilterAuthorizationMethodInterceptor
  • PostFilterAuthorizationMethodInterceptor
  • AuthorizationManagerBeforeMethodInterceptor
  • AuthorizationManagerAfterMethodInterceptor
  • 在SecuredMethodSecurityConfiguration中,完成了以下Bean的创建
  • AuthorizationManagerBeforeMethodInterceptor
  • 在Jsr250MethodSecurityConfiguration中,完成了以下Bean的创建
  • AuthorizationManagerBeforeMethodInterceptor

其添加的MethodInterceptor与使用xml配置解析的结果是一样的,将拦截器添加到IOC后,就可以正常的拦截标记有相应注解的方法了。

入口清晰了,接下来就要看各个MethodInterceptor具体是如何工作的了。


2、MethodInterceptor的执行逻辑

首先我们需要知道,不同的MethodInterceptor与不同的AuthorizationManager是有绑定关系的。MethodInterceptor负责拦截方法的执行,解析需要的权限,AuthorizationManager则负责对权限的校验,下面我们选择几个看看


1)Jsr250MethodSecurity

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
final class Jsr250MethodSecurityConfiguration {

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	Advisor jsr250AuthorizationMethodInterceptor(ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
			ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
			ObjectProvider<ObservationRegistry> registryProvider) {
    //1. 直接实例化Jsr250AuthorizationManager
		Jsr250AuthorizationManager jsr250 = new Jsr250AuthorizationManager();
		defaultsProvider.ifAvailable((d) -> jsr250.setRolePrefix(d.getRolePrefix()));
    //2. 获取SecurityContext的持有策略
		SecurityContextHolderStrategy strategy = strategyProvider
				.getIfAvailable(SecurityContextHolder::getContextHolderStrategy);
        
		AuthorizationManager<MethodInvocation> manager = new DeferringObservationAuthorizationManager<>(
				registryProvider, jsr250);
        
    //4. 创建方法拦截器
		AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor
				.jsr250(manager);
		interceptor.setSecurityContextHolderStrategy(strategy);
        
		return interceptor;
	}
}

Jsr250AuthorizationManager

public final class Jsr250AuthorizationManager implements AuthorizationManager<MethodInvocation> {

  	private final Jsr250AuthorizationManagerRegistry registry 
      = new Jsr250AuthorizationManagerRegistry();
  
	@Override
	public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation methodInvocation) {
    //执行下面内部类方法获取匹配的AuthorizationManager
		AuthorizationManager<MethodInvocation> delegate = this.registry.getManager(methodInvocation);
    //使用匹配的AuthorizationManager执行鉴权
		return delegate.check(authentication, methodInvocation);
	}
  
  private final class Jsr250AuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry {

		@NonNull
		@Override
		AuthorizationManager<MethodInvocation> resolveManager(Method method, Class<?> targetClass) {
      //获得方法上的注解
			Annotation annotation = findJsr250Annotation(method, targetClass);
			if (annotation instanceof DenyAll) {
        //如果是DenyAll,则返回check方法返回
        //固定AuthorizationDecision的AuthorizationManager
				return (a, o) -> new AuthorizationDecision(false);
			}
			if (annotation instanceof PermitAll) {
        //如果是PermitAll,则返回check方法返回
        //固定AuthorizationDecision的AuthorizationManager
				return (a, o) -> new AuthorizationDecision(true);
			}
			if (annotation instanceof RolesAllowed) {
        //如果是RolesAllowed,则调用AuthorityAuthorizationManager
        //的hasAnyRole来判断是否具备权限
				RolesAllowed rolesAllowed = (RolesAllowed) annotation;
				return AuthorityAuthorizationManager.hasAnyRole(Jsr250AuthorizationManager.this.rolePrefix,
						rolesAllowed.value());
			}
			return NULL_MANAGER;
		}
	}
}

可见,其最终依托的是 AuthorityAuthorizationManager,接着看些拦截器里面都做了啥 AuthorizationManagerBeforeMethodInterceptor

public final class AuthorizationManagerBeforeMethodInterceptor
		implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
	
    private Supplier<Authentication> authentication = getAuthentication(
			SecurityContextHolder.getContextHolderStrategy());
    
    @Override
	public Object invoke(MethodInvocation mi) throws Throwable {
        //进行鉴权
		attemptAuthorization(mi);
		return mi.proceed();
	}
    
	private void attemptAuthorization(MethodInvocation mi) {
		this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi));
        //继续鉴权(调用上面的Jsr250AuthorizationManager)
		AuthorizationDecision decision = this.authorizationManager.check(this.authentication, mi);
		this.eventPublisher.publishAuthorizationEvent(this.authentication, mi, decision);
		//根据响应的结果来决定抛出异常还是顺利执行
        if (decision != null && !decision.isGranted()) {
			this.logger.debug(LogMessage.of(() -> "Failed to authorize " + mi + " with authorization manager "
					+ this.authorizationManager + " and decision " + decision));
			throw new AccessDeniedException("Access Denied");
		}
		this.logger.debug(LogMessage.of(() -> "Authorized method invocation " + mi));
	}
}

既然上面用到了AuthorityAuthorizationManager,那么我们就看看AuthorityAuthorizationManager是怎么做的

public final class AuthorityAuthorizationManager<T> 
		implements AuthorizationManager<T> {

	public static <T> AuthorityAuthorizationManager<T> hasAnyRole(String rolePrefix, String[] roles) {
		Assert.notNull(rolePrefix, "rolePrefix cannot be null");
		Assert.notEmpty(roles, "roles cannot be empty");
		Assert.noNullElements(roles, "roles cannot contain null values");
    //将角色名转换成待"ROLE_"前缀的值
		return hasAnyAuthority(toNamedRolesArray(rolePrefix, roles));
	}
  
	public static <T> AuthorityAuthorizationManager<T> hasAnyAuthority(String... authorities) {
		Assert.notEmpty(authorities, "authorities cannot be empty");
		Assert.noNullElements(authorities, "authorities cannot contain null values");
        //新建实例并返回
		return new AuthorityAuthorizationManager<>(authorities);
	}

	//执行鉴权
	@Override
	public AuthorizationDecision check(Supplier<Authentication> authentication, T object) {
		boolean granted = isGranted(authentication.get());
		return new AuthorityAuthorizationDecision(granted, this.authorities);
	}
  
	private boolean isGranted(Authentication authentication) {
		return authentication != null 
    	&& authentication.isAuthenticated() 
      && isAuthorized(authentication);
	}
  
	private boolean isAuthorized(Authentication authentication) {
  	//具备的权限
		Set<String> authorities = AuthorityUtils.authorityListToSet(this.authorities);
    //获得具有继承关系的权限并判断【默认是NullRoleHierarchy,即没有继承关系】
		for (GrantedAuthority grantedAuthority : getGrantedAuthorities(authentication)) {
			if (authorities.contains(grantedAuthority.getAuthority())) {
				return true;
			}
		}
		return false;
	}
  
	private Collection<? extends GrantedAuthority> getGrantedAuthorities(Authentication authentication) {
		return this.roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities());
	}
}

其实就是对认证完填充后的权限值进行对比


2)SecuredMethodSecurity

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
final class SecuredMethodSecurityConfiguration {

    //这里的逻辑和上面的很相似,主要区别在于这里
    //使用的是SecuredAuthorizationManager
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	Advisor securedAuthorizationMethodInterceptor(ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
			ObjectProvider<ObservationRegistry> registryProvider) {
    	//创建鉴权管理器
		SecuredAuthorizationManager secured = new SecuredAuthorizationManager();
		
        SecurityContextHolderStrategy strategy = strategyProvider
				.getIfAvailable(SecurityContextHolder::getContextHolderStrategy);
		AuthorizationManager<MethodInvocation> manager = new DeferringObservationAuthorizationManager<>(
				registryProvider, secured);
        //创建方法拦截器【与上面一致】
		AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor
				.secured(manager);
		interceptor.setSecurityContextHolderStrategy(strategy);
		return interceptor;
	}

}

SecuredAuthorizationManager

public final class SecuredAuthorizationManager 
    implements AuthorizationManager<MethodInvocation> {

	private final SecuredAuthorizationManagerRegistry registry 
    		= new SecuredAuthorizationManagerRegistry();
  
	@Override
	public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation mi) {
		AuthorizationManager<MethodInvocation> delegate = this.registry.getManager(mi);
		return delegate.check(authentication, mi);
	}
    
	private static final class SecuredAuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry {

		@NonNull
		@Override
		AuthorizationManager<MethodInvocation> resolveManager(Method method, Class<?> targetClass) {
			Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
			Secured secured = findSecuredAnnotation(specificMethod);
            //上面因为是判断角色所以调了hasAnyRole方法
            //这里是判断权限所以用了hasAnyAuthority
			return (secured != null) ? AuthorityAuthorizationManager.hasAnyAuthority(secured.value()) : NULL_MANAGER;
		}

		private Secured findSecuredAnnotation(Method method) {
			Secured secured = AuthorizationAnnotationUtils.findUniqueAnnotation(method, Secured.class);
			return (secured != null) ? secured
					: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), Secured.class);
		}
	} 
}

与Jsr250MethodSecurity的区别只在于一个验证角色,一个验证权限,其他的都基本一样,不再赘述


3)PrePostMethodSecurity

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
final class PrePostMethodSecurityConfiguration {

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	Advisor preFilterAuthorizationMethodInterceptor(ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
			ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
			ObjectProvider<SecurityContextHolderStrategy> strategyProvider, ApplicationContext context) {
		//创建拦截器
        PreFilterAuthorizationMethodInterceptor preFilter = new PreFilterAuthorizationMethodInterceptor();
		strategyProvider.ifAvailable(preFilter::setSecurityContextHolderStrategy);
		//设置表达式解析器【@PreFilter注解中的el表达式】
        preFilter.setExpressionHandler(
				expressionHandlerProvider.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, context)));
		return preFilter;
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	Advisor preAuthorizeAuthorizationMethodInterceptor(ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
			ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
			ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
			ObjectProvider<AuthorizationEventPublisher> eventPublisherProvider,
			ObjectProvider<ObservationRegistry> registryProvider, ApplicationContext context) {
        //创建对应的前置鉴权器
		PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
        //设置表达式解析器
		manager.setExpressionHandler(
				expressionHandlerProvider.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, context)));
        //创建方法执行前鉴权拦截器
		AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor
				.preAuthorize(manager(manager, registryProvider));
		strategyProvider.ifAvailable(preAuthorize::setSecurityContextHolderStrategy);
		eventPublisherProvider.ifAvailable(preAuthorize::setAuthorizationEventPublisher);
		return preAuthorize;
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	Advisor postAuthorizeAuthorizationMethodInterceptor(ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
			ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
			ObjectProvider<SecurityContextHolderStrategy> strategyProvider,
			ObjectProvider<AuthorizationEventPublisher> eventPublisherProvider,
			ObjectProvider<ObservationRegistry> registryProvider, ApplicationContext context) {
		PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
		manager.setExpressionHandler(
				expressionHandlerProvider.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, context)));
		AuthorizationManagerAfterMethodInterceptor postAuthorize = AuthorizationManagerAfterMethodInterceptor
				.postAuthorize(manager(manager, registryProvider));
		strategyProvider.ifAvailable(postAuthorize::setSecurityContextHolderStrategy);
		eventPublisherProvider.ifAvailable(postAuthorize::setAuthorizationEventPublisher);
		return postAuthorize;
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	Advisor postFilterAuthorizationMethodInterceptor(ObjectProvider<GrantedAuthorityDefaults> defaultsProvider,
			ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
			ObjectProvider<SecurityContextHolderStrategy> strategyProvider, ApplicationContext context) {
		PostFilterAuthorizationMethodInterceptor postFilter = new PostFilterAuthorizationMethodInterceptor();
		strategyProvider.ifAvailable(postFilter::setSecurityContextHolderStrategy);
		postFilter.setExpressionHandler(
				expressionHandlerProvider.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, context)));
		return postFilter;
	}

	private static MethodSecurityExpressionHandler defaultExpressionHandler(
			ObjectProvider<GrantedAuthorityDefaults> defaultsProvider, ApplicationContext context) {
		DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
		defaultsProvider.ifAvailable((d) -> handler.setDefaultRolePrefix(d.getRolePrefix()));
		handler.setApplicationContext(context);
		return handler;
	}

	static <T> AuthorizationManager<T> manager(AuthorizationManager<T> delegate,
			ObjectProvider<ObservationRegistry> registryProvider) {
		return new DeferringObservationAuthorizationManager<>(registryProvider, delegate);
	}

}

对于注解这种鉴权方式,其整体的逻辑就是创建方法的拦截器,解析注解然后根据注解的含义使用对应的AuthorizationManager实现类进行角色或者权限的校验。

以下展示了Spring Security中的一些AuthorizationManager

Spring Security鉴权逻辑_拦截器

  • AuthenticatedAuthorizationManager<T> : 用于确定当前用户是否经过了身份验证【authenticated、fullyAuthenticated、rememberMe、anonymous、
  • AuthorityAuthorizationManager<T> :通过评估Authentication是否包含指定的权限来确定当前用户是否已被授权【hasRole、hasAuthority、hasAnyRole、hasAnyAuthority
  • Jsr250AuthorizationManager<MethodInvocation>:它可以通过评估身份验证是否包含来自JSR-250安全注释的指定权限来确定身份验证是否可以调用MethodInvocation【DenyAll、PermitAll、RolesAllowed
  • SecuredAuthorizationManager<MethodInvocation>:它可以通过评估身份验证是否包含Spring Security的Secured注释中的指定权限来确定身份验证是否可以调用MethodInvocation【Secured
  • PreAuthorizeAuthorizationManager<MethodInvocation>:它可以通过计算PreAuthorize 注释中的表达式来确定Authentication是否可以调用MethodInvocation。
  • PostAuthorizeAuthorizationManager<MethodInvocationResult>:它可以通过计算来自PostAuthorize注释的表达式来确定一个Authentication是否可以从调用的MethodInvocation返回结果。
  • MethodExpressionAuthorizationManager<MethodInvocation>:基于表达式的AuthorizationManager,通过根据MethodInvocation计算提供的表达式来确定访问。
  • WebExpressionAuthorizationManager<RequestAuthorizationContext>:基于表达式的AuthorizationManager,通过计算提供的表达式来确定访问。

在包org.springframework.security.authorization下涉及了鉴权的代码,主要的拦截器有以下几个

  • AuthorizationManagerBeforeMethodInterceptor
  • AuthorizationManagerAfterMethodInterceptor
  • PreFilterAuthorizationMethodInterceptor
  • PostFilterAuthorizationMethodInterceptor
  • AuthorizationManagerBeforeReactiveMethodInterceptor
  • AuthorizationManagerAfterReactiveMethodInterceptor
  • PreFilterAuthorizationReactiveMethodInterceptor
  • PostFilterAuthorizationReactiveMethodInterceptor


二、HttpSecurity上配置权限

我们可以通过如下的方式对Http请求进行权限配置

@Configuration
public class Configuration {
    @Bean
    public SecurityFilterChain configure(HttpSecurity http) throws Exception {

        return http
                //授权请求配置(以前版本是authorizeRequests())
                .authorizeHttpRequests()
                    //匹配的路径都可以访问
                    .requestMatchers("/auth/code","/error").permitAll()
                    //具备某个角色的用户才可以访问的路径
                    .requestMatchers("/admin/**").hasAnyRole("admin") 
                    //具备某个权限的用户才可以访问的路径
                    .requestMatchers("/show/**").hasAnyAuthority("P001") 
            		//其他的请求都需要已登录
                    .anyRequest().authenticated()
                .and()
                .build() ;
    }
}

当执行HttpSecurity的authorizeHttpRequests()方法时,其内部会创建一个AuthorizeHttpRequestsConfigurer的实例对象,同时返回调用该对象getRegistry()方法返回的AuthorizationManagerRequestMatcherRegistry进行后续的配置。

AuthorizeHttpRequestsConfigurer在被执行configure方法时会创建AuthorizationFilter过滤器,并添加到HttpSecurity中。而在创建AuthorizationFilter实例时,需要传递一个AuthorizationManager对象作为构造参数。


1)AuthorizationFilter

首先看下AuthorizationFilter内部都做了些啥

public class AuthorizationFilter extends GenericFilterBean {
  
	@Override
	public void doFilter(ServletRequest servletRequest, 
                       ServletResponse servletResponse, 
                       FilterChain chain)
			throws ServletException, IOException {

		HttpServletRequest request = (HttpServletRequest) servletRequest;
		HttpServletResponse response = (HttpServletResponse) servletResponse;

		if (this.observeOncePerRequest && isApplied(request)) {
			chain.doFilter(request, response);
			return;
		}

		if (skipDispatch(request)) {
			chain.doFilter(request, response);
			return;
		}

		String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
		request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
		try {
      //重点:获取到当前认证后的Authentication
      //调用authorizationManager的check方法
      //根据返回的结果决定抛出异常还是继续执行后续的过滤器
			AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, request);
			this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, request, decision);
			if (decision != null && !decision.isGranted()) {
				throw new AccessDeniedException("Access Denied");
			}
			chain.doFilter(request, response);
		}
		finally {
			request.removeAttribute(alreadyFilteredAttributeName);
		}
	}
}

其逻辑和上面介绍的方法拦截器是一样的,重点还是看其使用的authorizationManager是如何完成鉴权的。


2)AuthorizationManager

至于AuthorizationManager是如何被创建的,我想通过以下的图来大体说明mxCell

Spring Security鉴权逻辑_拦截器_02

鉴权是通过AuthorizationManager实现,在使用时会有不同的鉴权要求,也就会有不同鉴权形式的AuthorizationManager。

首先我们配置要鉴权的请求匹配规则,然后给这规则关联上对应的AuthorizationManager。请求的匹配由AuthorizationManagerRequestMatcherRegistry负责,请求路径的鉴权形式由AuthorizedUrl配置并创建匹配的AuthorizationManager,最终再由AuthorizeHttpRequestsConfigurer来触发构建出AuthorizationManager并创建AuthorizationFilter

主要有以下两个AuthorizationManager

  • AuthorityAuthorizationManager:主要是通过已认证的Authentication中保存的GrantedAuthority数据来匹配判断,包含角色和一般权限值的匹配
  • AuthenticatedAuthorizationManager:用于确定当前用户是否经过身份验证,其内部依赖具体的AbstractAuthorizationStrategy去判断。
  • AuthenticatedAuthorizationStrategy:判断是否是成功登录的非匿名用户
  • RememberMeAuthorizationStrategy:指示传递的身份验证令牌是否表示已记住的用户(即不是已完全身份验证的用户)。
  • FullyAuthenticatedAuthorizationStrategy:判断是否是成功登录的非匿名用户,且不是来自”记住我的用户“
  • AnonymousAuthorizationStrategy:指示传递的身份验证令牌是否表示匿名用户


需要说明是是,这里介绍的都是Spring Security6.0版本的实现,在先前版本中的AccessDecisionVoter、AccessDecisionManager相关的方式已经不推荐了,被AuthorizationManager取代。


3)ExceptionTranslationFilter

鉴权可能失败,我们看AuthorizationFilter和各个方法拦截器,在鉴权失败后都是抛出AccessDeniedException异常,那么该异常是在哪里被处理,以及按照什么策略进行处理的呢,关于这点可以从ExceptionTranslationFilter说起。

public class ExceptionTranslationFilter extends GenericFilterBean 
			implements MessageSourceAware {
  //访问拒绝处理器
	private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
	//认证入口
	private AuthenticationEntryPoint authenticationEntryPoint;
	//身份验证信任解析
	private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
	//异常分析
	private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
  
  @Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
	}

	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
    //重点:捕获了后续代码的执行,从而可对抛出的异常进行分析
		try {
			chain.doFilter(request, response);
		}
		catch (IOException ex) {
			throw ex;
		}
		catch (Exception ex) {
			// Try to extract a SpringSecurityException from the stacktrace
			Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
      //分析异常是否是AuthenticationException
			RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer
					.getFirstThrowableOfType(AuthenticationException.class, causeChain);
			if (securityException == null) {
      	//分析异常是否是AccessDeniedException
				securityException = (AccessDeniedException) this.throwableAnalyzer
						.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
			}
			if (securityException == null) {
      	//如果不是AuthenticationException或AccessDeniedException则重新抛出
				rethrow(ex);
			}
			if (response.isCommitted()) {
				throw new ServletException("Unable to handle the Spring Security Exception "
						+ "because the response is already committed.", ex);
			}
      //处理AuthenticationException或AccessDeniedException
			handleSpringSecurityException(request, response, chain, securityException);
		}
	}
	private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response,
			FilterChain chain, RuntimeException exception) throws IOException, ServletException {
		if (exception instanceof AuthenticationException) {
			handleAuthenticationException(request, response, chain, (AuthenticationException) exception);
		}
		else if (exception instanceof AccessDeniedException) {
			handleAccessDeniedException(request, response, chain, (AccessDeniedException) exception);
		}
	}
	private void handleAuthenticationException(HttpServletRequest request, HttpServletResponse response,
			FilterChain chain, AuthenticationException exception) throws ServletException, IOException {
		this.logger.trace("Sending to authentication entry point since authentication failed", exception);
    //开始发起认证
		sendStartAuthentication(request, response, chain, exception);
	}
	protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
			AuthenticationException reason) throws ServletException, IOException {
		// SEC-112: Clear the SecurityContextHolder's Authentication, as the
		// existing Authentication is no longer considered valid
		SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
		this.securityContextHolderStrategy.setContext(context);
		this.requestCache.saveRequest(request, response);
    //触发authenticationEntryPoint发起认证操作
		this.authenticationEntryPoint.commence(request, response, reason);
	}
  
	private void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response,
			FilterChain chain, AccessDeniedException exception) throws ServletException, IOException {
		Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
    //判断是否是匿名用户
		boolean isAnonymous = this.authenticationTrustResolver.isAnonymous(authentication);
    //如果是匿名或者"记住我的用户token"
		if (isAnonymous || this.authenticationTrustResolver.isRememberMe(authentication)) {
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.format("Sending %s to authentication entry point since access is denied",
						authentication), exception);
			}
			sendStartAuthentication(request, response, chain,
					new InsufficientAuthenticationException(
							this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication",
									"Full authentication is required to access this resource")));
		} else {
			if (logger.isTraceEnabled()) {
				logger.trace(
						LogMessage.format("Sending %s to access denied handler since access is denied", authentication),
						exception);
			}
      //使用认证被拒绝的处理器进行处理
			this.accessDeniedHandler.handle(request, response, exception);
		}
	}
}

其整体的逻辑还是比较清晰,捕获后续的异常,对异常进行解析,然后根据异常来决定触发登录操作还是执行某些逻辑。

这里有两个类型以及对应的实现类可以关注下

  • AuthenticationEntryPoint:用于启动身份验证方案
  • LoginUrlAuthenticationEntryPoint
  • Http403ForbiddenEntryPoint
  • HttpStatusEntryPoint
  • BasicAuthenticationEntryPoint
  • DigestAuthenticationEntryPoint
  • DelegatingAuthenticationEntryPoint
  • AccessDeniedHandler:处理AccessDeniedException异常
  • AccessDeniedHandlerImpl
  • InvalidSessionAccessDeniedHandler
  • ObservationMarkingAccessDeniedHandler
  • DelegatingAccessDeniedHandler
  • CompositeAccessDeniedHandler
  • RequestMatcherDelegatingAccessDeniedHandler

在很多场景下我们都可能需要自定义AuthenticationEntryPoint与AccessDeniedHandler的实现。

通过以下的配置就可以让ExceptionTranslationFilter生效

@Configuration
public class Configuration {
    @Bean
    public SecurityFilterChain configure(HttpSecurity http) throws Exception {
        return http
                .exceptionHandling()
                    .accessDeniedHandler(/*自定义的AccessDeniedHandler*/)
                    .authenticationEntryPoint(/*自定义的AuthenticationEntryPoint*/)
                    .and()
                .build() ;
    }
}


以上便是Spring Security 6 的整体鉴权逻辑,只要抓住AuthorizationFilter和AuthorizationManager进行推导就容易理解了。