Bootstrap

Spring Security详解(四)认证之鉴权

4 鉴权

从Spring Security的过滤器链中,我们已经发现位于最后的FilterSecurityInterceptor是用来进行权限认证的,这一节将详细分析Spring Security是如何进行权限认证的。

4.1 FilterSecurityInterceptor

源码分析:

public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements
    Filter {
    private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";
    ...
        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 {
            //第一次调用此请求时,请执行安全检查
            if (fi.getRequest() != null && observeOncePerRequest) {
                fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
            }
			
            //before
            InterceptorStatusToken token = super.beforeInvocation(fi);

            try {
                //过滤器链
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            }
            finally {
                //finally
                super.finallyInvocation(token);
            }
			//after
            super.afterInvocation(token, null);
        }
    }
}

这段代码的主要逻辑就是调用了父类的beforeInvocation、开启filter链式调用、finallyInvocationafterInvocation方法。filter的链式调用就是获取配置好的filter,按照顺序依次进行调用,接下来重点看一下父类做了什么。

AbstractSecurityInterceptor

AbstractSecurityInterceptor是一个实现了对受保护对象的访问进行拦截的抽象类,先来看一下它的源码:

public abstract class AbstractSecurityInterceptor implements InitializingBean,
		ApplicationEventPublisherAware, MessageSourceAware {
	...
	//对返回值进行修改
	private AfterInvocationManager afterInvocationManager;
	//改变鉴权过后的Authentication
	private RunAsManager runAsManager = new NullRunAsManager();
	...
	
	protected InterceptorStatusToken beforeInvocation(Object object) {
		...
		//获取配置的权限信息
		Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
				.getAttributes(object);
		...
		//身份认证是否完成
		Authentication authenticated = authenticateIfRequired();
		try {
			//资源权限认证
			this.accessDecisionManager.decide(authenticated, object, attributes);
		}
		...
		// Attempt to run as a different user
		//修改保存在SecurityContext中的Authentication
		Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
				attributes);
		...
	}

	//请求完毕后进行清理
	protected void finallyInvocation(InterceptorStatusToken token) {
		if (token != null && token.isContextHolderRefreshRequired()) {
			...
			SecurityContextHolder.setContext(token.getSecurityContext());
		}
	}

	
	protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
		finallyInvocation(token); // continue to clean in this method for passivity
		if (afterInvocationManager != null) {
			// Attempt after invocation handling
			try {
                //重点
				returnedObject = afterInvocationManager.decide(token.getSecurityContext()
						.getAuthentication(), token.getSecureObject(), token
						.getAttributes(), returnedObject);
			}
			...
		}

		return returnedObject;
	}

	private Authentication authenticateIfRequired() {
		Authentication authentication = SecurityContextHolder.getContext()
				.getAuthentication();
		//判断身份认证是否完成
		if (authentication.isAuthenticated() && !alwaysReauthenticate) {
			return authentication;
		}
		//身份认证
		authentication = authenticationManager.authenticate(authentication);
		SecurityContextHolder.getContext().setAuthentication(authentication);
		return authentication;
	}
	...
}
  • beforeInvocation() 方法实现了对访问受保护对象的权限校验,它会从SecurityContextHolder获取Authentication,然后通过SecurityMetadataSource可以得知当前请求是否在请求受保护的资源。对于请求那些受保护的资源,如果Authentication.isAuthenticated()返回false或者AbstractSecurityInterceptoralwaysReauthenticate属性为true,那么将会使用其引用的AuthenticationManager再认证一次。然后就是利用AccessDecisionManager进行权限的检查;
  • finallyInvocation()方法用于实现受保护对象请求完毕后的一些清理工作,主要是如果在beforeInvocation()中改变了SecurityContext,则在finallyInvocation()中需要将其恢复为原来的SecurityContext
  • afterInvocation()方法实现了对返回结果的处理,在注入了AfterInvocationManager的情况下默认会调用其decide()方法。

其中有几个比较重要的参数,ConfigAttributeRunAsManagerAfterInvocationManager。接下来简单分析一下。

ConfigAttribute

AbstractSecurityInterceptorbeforeInvocation()方法内部在进行鉴权的时候使用的是注入的AccessDecisionManager的decide()方法进行的。decide()方法是需要接收一个受保护对象对应的ConfigAttribute集合的。一个ConfigAttribute可能只是一个简单的角色名称,具体将视AccessDecisionManager的实现者而定。

AbstractSecurityInterceptor将使用一个SecurityMetadataSource对象来获取与受保护对象关联的ConfigAttribute集合。ConfigAttribute将通过注解的形式定义在受保护的方法上,或者通过access属性定义在受保护的URL上。例如我们常见的@PreAuthorize("hasAuthority('ROLE_A')")就表示将ConfigAttribute ROLE_A应用在当前方法上。

对于默认的AccessDecisionManager的实现,上述配置意味着用户所拥有的权限中只要拥有一个GrantedAuthority与这两个ConfigAttribute中的一个进行匹配则允许进行访问。当然,严格的来说ConfigAttribute只是一个简单的配置属性而已,具体的解释将由AccessDecisionManager来决定。

那么ConfigAttribute是从哪里配置的呢?

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

而obtainSecurityMetadataSource是个抽象方法。

public abstract SecurityMetadataSource obtainSecurityMetadataSource();

AbstractSecurityInterceptor提供了两个常用实现类,FilterSecurityInterceptorMethodSecurityInterceptorMethodSecurityInterceptor将用于调用受保护的方法,而FilterSecurityInterceptor将用于受保护的Web请求。它们将解析不同的ConfigAttribute

FilterSecurityInterceptor

用来鉴定在HttpSecurity中配置的权限

public SecurityMetadataSource obtainSecurityMetadataSource() {
    return this.securityMetadataSource;
}

public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) {
    this.securityMetadataSource = newSource;
}

那么securityMetadataSource是什么时候被set进去的呢,这段逻辑在AbstractInterceptUrlConfigurer

public void configure(H http) throws Exception {
    //这里解析
    FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource(http);
    if (metadataSource == null) {
        return;
    }
    //这里调用
    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);
}

private FilterSecurityInterceptor createFilterSecurityInterceptor(H http,FilterInvocationSecurityMetadataSource metadataSource,AuthenticationManager authenticationManager) throws Exception {
    FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor();
    //这里将securityMetadataSource设置到子类中
    securityInterceptor.setSecurityMetadataSource(metadataSource);
    securityInterceptor.setAccessDecisionManager(getAccessDecisionManager(http));
    securityInterceptor.setAuthenticationManager(authenticationManager);
    securityInterceptor.afterPropertiesSet();
    return securityInterceptor;
}

createMetadataSource常用实现类在ExpressionUrlAuthorizationConfigurer

final ExpressionBasedFilterInvocationSecurityMetadataSource createMetadataSource(
    H http) {
    LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = REGISTRY
        .createRequestMap();
   //...
    return new ExpressionBasedFilterInvocationSecurityMetadataSource(requestMap,
                                                                     getExpressionHandler(http));
}

ExpressionUrlAuthorizationConfigurer配置详解 里已经说明,是用来配置Secirity,也就是说这里的configure方法来源于自己的配置。

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/", "/home").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/login")
            .failureUrl("/error")
            .permitAll()
            .and()
            .logout()
            .permitAll();	
    }

}

getAttributes()则是在DefaultFilterInvocationSecurityMetadataSource。这里比较简单就不详细说明了。

MethodSecurityInterceptor

用来鉴定方法注解@PreAuthorize的权限

public SecurityMetadataSource obtainSecurityMetadataSource() {
    return this.securityMetadataSource;
}

public void setSecurityMetadataSource(MethodSecurityMetadataSource newSource) {
    this.securityMetadataSource = newSource;
}

而它是在什么时候set进去的呢?这段逻辑在GlobalMethodSecurityConfiguration,当配置了@EnableGlobalMethodSecurity注解时会根据配置的不同加载不同的SecurityMetadataSource

这里贴一下setSecurityMetadataSource的代码,只保留了常用的一个@EnableGlobalMethodSecurity(prePostEnabled = true)

public MethodSecurityMetadataSource methodSecurityMetadataSource() {
    List<MethodSecurityMetadataSource> sources = new ArrayList<>();
    //...
    //..
    if (prePostEnabled()) {
        sources.add(new PrePostAnnotationSecurityMetadataSource(attributeFactory));
    }
    //..
    return new DelegatingMethodSecurityMetadataSource(sources);

然后getAttributes时根据不同的配置生成不同的ConfigAttribute

PrePostAnnotationSecurityMetadataSource#getAttributes

public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
   //...
    PreAuthorize preAuthorize = findAnnotation(method, targetClass,PreAuthorize.class); 
    ArrayList<ConfigAttribute> attrs = new ArrayList<>(2);
	//...
    PreInvocationAttribute pre = attributeFactory.createPreInvocationAttribute(
        preFilterAttribute, filterObject, preAuthorizeAttribute);

    if (pre != null) {
        attrs.add(pre);
    }
    return attrs;
}

RunAsManager

在某些情况下你可能会想替换保存在SecurityContext中的Authentication。这由AccessDecisionManager调用RunAsManager来处理。在AbstractSecurityInterceptorbeforeInvocation()方法体中,在AccessDecisionManager鉴权成功后,将通过RunAsManager在现有Authentication基础上构建一个新的Authentication,如果新的Authentication不为空则将产生一个新的SecurityContext,并把新产生的Authentication存放在其中。

Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
                                                    attributes);
if (runAs == null) {
    // no further work post-invocation
    return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
                                      attributes, object);
}
else {
    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);
}

这样在请求受保护资源时从SecurityContext中获取到的Authentication就是新产生的Authentication。待请求完成后会在finallyInvocation()中将原来的SecurityContext重新设置给SecurityContextHolderAbstractSecurityInterceptor默认持有的是一个对RunAsManager进行空实现的NullRunAsManager

final class NullRunAsManager implements RunAsManager {

    public Authentication buildRunAs(Authentication authentication, Object object,
                                     Collection<ConfigAttribute> config) {
        return null;
    }

    public boolean supports(ConfigAttribute attribute) {
        return false;
    }

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

此外,Spring Security对RunAsManager还有一个非空实现类RunAsManagerImpl.

public class RunAsManagerImpl implements RunAsManager, InitializingBean {

    private String key;
    private String rolePrefix = "ROLE_";
    ...
    public Authentication buildRunAs(Authentication authentication, Object object,
                                         Collection<ConfigAttribute> attributes) {
        List<GrantedAuthority> newAuthorities = new ArrayList<>();

        for (ConfigAttribute attribute : attributes) {
            if (this.supports(attribute)) {
                GrantedAuthority extraAuthority = new SimpleGrantedAuthority(
                    getRolePrefix() + attribute.getAttribute());
                newAuthorities.add(extraAuthority);
            }
        }

        if (newAuthorities.size() == 0) {
            return null;
        }

        // Add existing authorities
        newAuthorities.addAll(authentication.getAuthorities());

        return new RunAsUserToken(this.key, authentication.getPrincipal(),
                                  authentication.getCredentials(), newAuthorities,
                                  authentication.getClass());
    }

    public boolean supports(ConfigAttribute attribute) {
        return attribute.getAttribute() != null
            && attribute.getAttribute().startsWith("RUN_AS_");
    }
}

其在构造新的Authentication时是这样的逻辑:如果受保护对象对应的ConfigAttribute中拥有以"RUN_AS_ "开头的配置属性,则在该属性前加上"ROLE_ ",然后再把它作为一个GrantedAuthority赋给将要创建的Authentication(如ConfigAttribute中拥有一个"RUN_AS_ADMIN"的属性,则将构建一个"RUN_AS_ADMIN"的GrantedAuthority),最后再利用原Authentication的principal、权限等信息构建一个新的Authentication进行返回;如果不存在任何以“RUN_AS_”开头的ConfigAttribute,则直接返回null。

AfterInvocationManager

在请求受保护的对象完成以后,可以通过afterInvocation()方法对返回值进行修改。AbstractSecurityInterceptor把对返回值进行修改的控制权交给其所持有的AfterInvocationManager,以根据需要实际修改对象。和AuthenticationProvider相似,AfterInvocationManager接口的实现类AfterInvocationProviderManager维护了一个AfterInvocationProvider列表,依次委托给AfterInvocationProvider接口的实现类进行修改返回值。

public class AfterInvocationProviderManager implements AfterInvocationManager,
InitializingBean {
    private List<AfterInvocationProvider> providers;

	...

    public Object decide(Authentication authentication, Object object,
                         Collection<ConfigAttribute> config, Object returnedObject)
        throws AccessDeniedException {

        Object result = returnedObject;
		//循环调用decide方法
        for (AfterInvocationProvider provider : providers) {
            result = provider.decide(authentication, object, config, result);
        }

        return result;
    }
    ...
}

需要注意的是AfterInvocationManager需要在受保护对象成功被访问后才能执行。

AfterInvocationProvider提供了一个实现类PostInvocationAdviceProvider:

public class PostInvocationAdviceProvider implements AfterInvocationProvider {

	public Object decide(Authentication authentication, Object object,
			Collection<ConfigAttribute> config, Object returnedObject)
			throws AccessDeniedException {

		PostInvocationAttribute pia = findPostInvocationAttribute(config);

		if (pia == null) {
			return returnedObject;
		}

		return postAdvice.after(authentication, (MethodInvocation) object, pia,
				returnedObject);
	}
}

这里就不往下深究了,如果遇到需要修改返回值的情况在回来补上。总的来说,AfterInvocationManager可以选择对返回值进行修改、不修改或抛出异常(如:后置权限鉴定不通过)。

FilterSecurityInterceptor上面已经分析过,接下来看一下MethodSecurityInterceptor部分源码:

public class MethodSecurityInterceptor extends AbstractSecurityInterceptor implements
		MethodInterceptor {
	public Object invoke(MethodInvocation mi) throws Throwable {
		InterceptorStatusToken token = super.beforeInvocation(mi);

		Object result;
		try {
			result = mi.proceed();
		}
		finally {
			super.finallyInvocation(token);
		}
		return super.afterInvocation(token, result);
	}
}

比较简单,就是运用了Spring Aop思想。

以下是Spring Security官方文档提供的一张关于AbstractSecurityInterceptor相关关系的图。
这里写图片描述

接下来就是重中之重的AccessDecisionManager

4.2 AccessDecisionManager

AccessDecisionManager负责鉴定用户是否有访问对应资源(方法或URL)的权限。AccessDecisionManager是一个接口,其中只定义了三个方法,其定义如下。

public interface AccessDecisionManager {
    /**
     * 通过传递的参数来决定用户是否有访问对应受保护对象的权限
     *
     * @param authentication 当前正在请求受包含对象的Authentication
     * @param object 受保护对象,其可以是一个MethodInvocation、JoinPoint或FilterInvocation。
     * @param configAttributes 与正在请求的受保护对象相关联的配置属性
     **/
    void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)throws AccessDeniedException, InsufficientAuthenticationException;

    //表示当前AccessDecisionManager是否支持对应的ConfigAttribute
    boolean supports(ConfigAttribute attribute);

    //表示当前AccessDecisionManager是否支持对应的受保护对象类型
    boolean supports(Class<?> clazz);

}

decide()方法用于决定authentication是否符合受保护对象要求的configAttributessupports(ConfigAttribute attribute)方法是用来判断AccessDecisionManager是否能够处理对应的ConfigAttribute的。supports(Class<?> clazz)方法用于判断配置的AccessDecisionManager是否支持对应的受保护对象类型。

4.2.1 实现

Spring Security已经内置了几个基于投票的AccessDecisionManager,当然如果需要你也可以实现自己的AccessDecisionManager。以下是Spring Security官方文档提供的一个图,其展示了与基于投票的AccessDecisionManager实现相关的类。

这里写图片描述

使用这种方式,一系列的AccessDecisionVoter将会被AccessDecisionManager用来对Authentication是否有权访问受保护对象进行投票,然后再根据投票结果来决定是否要抛出AccessDeniedException

Spring Security内置了三个基于投票的AccessDecisionManager实现类,它们分别是AffirmativeBasedConsensusBasedUnanimousBased

AffirmativeBased的逻辑是这样的:

  1. 只要有AccessDecisionVoter的投票为ACCESS_GRANTED则同意用户进行访问;
  2. 如果全部弃权也表示通过;
  3. 如果没有一个人投赞成票,但是有人投反对票,则将抛出AccessDeniedException

ConsensusBased的逻辑是这样的:

  1. 如果赞成票多于反对票则表示通过。
  2. 反过来,如果反对票多于赞成票则将抛出AccessDeniedException
  3. 如果赞成票与反对票相同且不等于0,并且属性allowIfEqualGrantedDeniedDecisions的值为true,则表示通过,否则将抛出异常AccessDeniedException。参数allowIfEqualGrantedDeniedDecisions的值默认为true。
  4. 如果所有的AccessDecisionVoter都弃权了,则将视参数allowIfAllAbstainDecisions的值而定,如果该值为true则表示通过,否则将抛出异常AccessDeniedException。参数allowIfAllAbstainDecisions的值默认为false。

UnanimousBased的逻辑与另外两种实现有点不一样,另外两种会一次性把受保护对象的配置属性全部传递给AccessDecisionVoter进行投票,而UnanimousBased会一次只传递一个ConfigAttributeAccessDecisionVoter进行投票。这也就意味着如果我们的AccessDecisionVoter的逻辑是只要传递进来的ConfigAttribute中有一个能够匹配则投赞成票,但是放到UnanimousBased中其投票结果就不一定是赞成了。UnanimousBased的逻辑具体来说是这样的:

  1. 如果受保护对象配置的某一个ConfigAttribute被任意的AccessDecisionVoter反对了,则将抛出AccessDeniedException
  2. 如果没有反对票,但是有赞成票,则表示通过。
  3. 如果全部弃权了,则将视参数allowIfAllAbstainDecisions的值而定,true则通过,false则抛出AccessDeniedException

4.2.2 AccessDecisionVoter

AccessDecisionVoter是一个接口,主要定义了三个方法:

public interface AccessDecisionVoter<S> {

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

vote()方法的返回结果会是AccessDecisionVoter中定义的三个常量之一。ACCESS_GRANTED表示同意,ACCESS_DENIED表示拒绝,ACCESS_ABSTAIN表示弃权。如果一个AccessDecisionVoter不能判定当前Authentication是否拥有访问对应受保护对象的权限,则其vote()方法的返回值应当为弃权ACCESS_ABSTAIN

AccessDecisionVoter提供了很多实现类,这里只拿出比较常用的进行分析。

RoleVoter

public class RoleVoter implements AccessDecisionVoter<Object> {

	private String rolePrefix = "ROLE_";

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

		for (ConfigAttribute attribute : attributes) {
			if (this.supports(attribute)) {
				result = ACCESS_DENIED;

				// Attempt to find a matching granted authority
				for (GrantedAuthority authority : authorities) {
					if (attribute.getAttribute().equals(authority.getAuthority())) {
						return ACCESS_GRANTED;
					}
				}
			}
		}

		return result;
	}

	Collection<? extends GrantedAuthority> extractAuthorities(
			Authentication authentication) {
		return authentication.getAuthorities();
	}
}

RoleVoterSpring Security内置的一个AccessDecisionVoter,支持以"ROLE_ "开头的ConfigAttribute,当用户拥有的权限中有一个或多个能匹配受保护对象配置的ConfigAttribute时其将投赞成票;如果用户拥有的权限中没有一个能匹配受保护对象配置ConfigAttribute,则投反对票;如果受保护对象配置的ConfigAttribute中没有以“ROLE_ ”开头的,则将弃权。

AuthenticatedVoter

AuthenticatedVoter也是Spring Security内置的一个AccessDecisionVoter实现。其主要用来区分匿名用户、通过Remember-Me认证的用户和完全认证的用户。完全认证的用户是指由系统提供的登录入口进行成功登录认证的用户。

public class AuthenticatedVoter implements AccessDecisionVoter<Object> {
	
	public static final String IS_AUTHENTICATED_FULLY = "IS_AUTHENTICATED_FULLY";
	public static final String IS_AUTHENTICATED_REMEMBERED = "IS_AUTHENTICATED_REMEMBERED";
	public static final String IS_AUTHENTICATED_ANONYMOUSLY = "IS_AUTHENTICATED_ANONYMOUSLY";

	private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();


	public boolean supports(ConfigAttribute attribute) {
		if ((attribute.getAttribute() != null)
				&& (IS_AUTHENTICATED_FULLY.equals(attribute.getAttribute())
						|| IS_AUTHENTICATED_REMEMBERED.equals(attribute.getAttribute()) || IS_AUTHENTICATED_ANONYMOUSLY
							.equals(attribute.getAttribute()))) {
			return true;
		}
		else {
			return false;
		}
	}


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

	public int vote(Authentication authentication, Object object,
			Collection<ConfigAttribute> attributes) {
		int result = ACCESS_ABSTAIN;

		for (ConfigAttribute attribute : attributes) {
			if (this.supports(attribute)) {
				result = ACCESS_DENIED;

				if (IS_AUTHENTICATED_FULLY.equals(attribute.getAttribute())) {
					if (isFullyAuthenticated(authentication)) {
						return ACCESS_GRANTED;
					}
				}

				if (IS_AUTHENTICATED_REMEMBERED.equals(attribute.getAttribute())) {
					if (authenticationTrustResolver.isRememberMe(authentication)
							|| isFullyAuthenticated(authentication)) {
						return ACCESS_GRANTED;
					}
				}

				if (IS_AUTHENTICATED_ANONYMOUSLY.equals(attribute.getAttribute())) {
					if (authenticationTrustResolver.isAnonymous(authentication)
							|| isFullyAuthenticated(authentication)
							|| authenticationTrustResolver.isRememberMe(authentication)) {
						return ACCESS_GRANTED;
					}
				}
			}
		}

		return result;
	}
}

AuthenticatedVoter可以处理的ConfigAttributeIS_AUTHENTICATED_FULLYIS_AUTHENTICATED_REMEMBEREDIS_AUTHENTICATED_ANONYMOUSLY。如果ConfigAttribute不在这三者范围之内,则将弃权。否则将视ConfigAttribute而定,如果ConfigAttributeIS_AUTHENTICATED_ANONYMOUSLY,则不管用户是匿名的还是已经认证的都将投赞成票;如果是IS_AUTHENTICATED_REMEMBERED则仅当用户是由Remember-Me自动登录,或者是通过登录入口进行登录认证时才会投赞成票,否则将投反对票;而当ConfigAttribute为IS_AUTHENTICATED_FULLY时仅当用户是通过登录入口进行登录的才会投赞成票,否则将投反对票。

AuthenticatedVoter是通过AuthenticationTrustResolverisAnonymous()方法和isRememberMe()方法来判断SecurityContextHolder持有的Authentication是否为AnonymousAuthenticationTokenRememberMeAuthenticationToken的,即是否为IS_AUTHENTICATED_ANONYMOUSLYIS_AUTHENTICATED_REMEMBERED

当然,用户也可以通过实现AccessDecisionVoter来实现自己的投票逻辑。

总结

到这里有关Spring Security认证和鉴权都介绍完了。

参考 https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#RunAsManager

;