文章目录
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链式调用、finallyInvocation
和afterInvocation
方法。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
或者AbstractSecurityInterceptor
的alwaysReauthenticate
属性为true
,那么将会使用其引用的AuthenticationManager
再认证一次。然后就是利用AccessDecisionManager
进行权限的检查;finallyInvocation()
方法用于实现受保护对象请求完毕后的一些清理工作,主要是如果在beforeInvocation()
中改变了SecurityContext
,则在finallyInvocation()
中需要将其恢复为原来的SecurityContext
。afterInvocation()
方法实现了对返回结果的处理,在注入了AfterInvocationManager
的情况下默认会调用其decide()
方法。
其中有几个比较重要的参数,ConfigAttribute
,RunAsManager
,AfterInvocationManager
。接下来简单分析一下。
ConfigAttribute
AbstractSecurityInterceptor
的beforeInvocation()
方法内部在进行鉴权的时候使用的是注入的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
提供了两个常用实现类,FilterSecurityInterceptor
和MethodSecurityInterceptor
,MethodSecurityInterceptor
将用于调用受保护的方法,而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
来处理。在AbstractSecurityInterceptor
的beforeInvocation()
方法体中,在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
重新设置给SecurityContextHolder
。AbstractSecurityInterceptor
默认持有的是一个对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
是否符合受保护对象要求的configAttributes
。supports(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
实现类,它们分别是AffirmativeBased
、ConsensusBased
和UnanimousBased
。
AffirmativeBased的逻辑是这样的:
- 只要有
AccessDecisionVoter
的投票为ACCESS_GRANTED
则同意用户进行访问; - 如果全部弃权也表示通过;
- 如果没有一个人投赞成票,但是有人投反对票,则将抛出
AccessDeniedException
。
ConsensusBased的逻辑是这样的:
- 如果赞成票多于反对票则表示通过。
- 反过来,如果反对票多于赞成票则将抛出
AccessDeniedException
。 - 如果赞成票与反对票相同且不等于0,并且属性
allowIfEqualGrantedDeniedDecisions
的值为true,则表示通过,否则将抛出异常AccessDeniedException
。参数allowIfEqualGrantedDeniedDecisions
的值默认为true。 - 如果所有的
AccessDecisionVoter
都弃权了,则将视参数allowIfAllAbstainDecisions
的值而定,如果该值为true则表示通过,否则将抛出异常AccessDeniedException
。参数allowIfAllAbstainDecisions
的值默认为false。
UnanimousBased的逻辑与另外两种实现有点不一样,另外两种会一次性把受保护对象的配置属性全部传递给AccessDecisionVoter
进行投票,而UnanimousBased
会一次只传递一个ConfigAttribute
给AccessDecisionVoter
进行投票。这也就意味着如果我们的AccessDecisionVoter
的逻辑是只要传递进来的ConfigAttribute
中有一个能够匹配则投赞成票,但是放到UnanimousBased
中其投票结果就不一定是赞成了。UnanimousBased
的逻辑具体来说是这样的:
- 如果受保护对象配置的某一个
ConfigAttribute
被任意的AccessDecisionVoter
反对了,则将抛出AccessDeniedException
。 - 如果没有反对票,但是有赞成票,则表示通过。
- 如果全部弃权了,则将视参数
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();
}
}
RoleVoter
是Spring 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
可以处理的ConfigAttribute
有IS_AUTHENTICATED_FULLY
、IS_AUTHENTICATED_REMEMBERED
和IS_AUTHENTICATED_ANONYMOUSLY
。如果ConfigAttribute
不在这三者范围之内,则将弃权。否则将视ConfigAttribute
而定,如果ConfigAttribute
为IS_AUTHENTICATED_ANONYMOUSLY
,则不管用户是匿名的还是已经认证的都将投赞成票;如果是IS_AUTHENTICATED_REMEMBERED
则仅当用户是由Remember-Me
自动登录,或者是通过登录入口进行登录认证时才会投赞成票,否则将投反对票;而当ConfigAttribute为IS_AUTHENTICATED_FULLY
时仅当用户是通过登录入口进行登录的才会投赞成票,否则将投反对票。
AuthenticatedVoter
是通过AuthenticationTrustResolver
的isAnonymous()
方法和isRememberMe()
方法来判断SecurityContextHolder
持有的Authentication
是否为AnonymousAuthenticationToken
或RememberMeAuthenticationToken
的,即是否为IS_AUTHENTICATED_ANONYMOUSLY
和IS_AUTHENTICATED_REMEMBERED
。
当然,用户也可以通过实现AccessDecisionVoter来实现自己的投票逻辑。
总结
到这里有关Spring Security认证和鉴权都介绍完了。
参考 https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#RunAsManager