Spring Security可以通过http.authorizeRequests()对web请求进行授权保护。Spring Security使用标准Filter建立了对web请求的拦截,最终实现对资源的授权访问。
Spring Security的授权流程如下:
分析授权流程:
1.拦截请求,已认证用户访问受保护的web资源将被SecurityFilterChain中的FiltersecurityInterceptor的子类拦截。
2.获取资源访问策略,FilterSecurityInterceptor会从 SecurityMetadataSource 的子类 DefaultFilterlnvocationSecurityMetadataSource获取要访问当前资源所需要的权限Collection<ConfigAttribute> 。
SecurityMetadataSource其实就是读取访问策略的抽象,而读取的内容,其实就是我们配置的访问规则,读取访问策略如:
http
.authorizeRequests()
.antMatchers("/r/r1").hasAuthority("p1")
.antMatchers("/r/r2").hasAuthority( "p2")
3.Filtersecurityinterceptor会调用AccessDecisionManager进行授权决策,若决策通过,则允许访问资源,否则将禁止访问。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.security.access;
import java.util.Collection;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
public interface AccessDecisionManager {
/**
*通过传递的参数来决定用户是否有访问对应受保护资源的权限
*/
void decide(Authentication var1, Object var2, Collection<ConfigAttribute> var3) throws AccessDeniedException, InsufficientAuthenticationException;
boolean supports(ConfigAttribute var1);
boolean supports(Class<?> var1);
}
这里着重说明一下decide的参数:
authentication :要访问资源的访问者的身份
object :要访问的受保护资源,web请求对应Fi足revocation
configAttributes :是受保护资源的访问策略,通过SecurityMetadataSource获取。
decide接口就是用来鉴定当前用户是否有访问对应受保护资源的权限。
授权决策
AccessDecisionManager采用投票的方式来确定是否能够访问受保护资源。
AccessDecisionManager中包含的一系列AccessDecisionVoter将会被用来对Authentication是否有权访问受保护对象进行投票,AccessDecisionManager根据投票结果,做出最终决策。
AccessDecisionVoter是一个接口 ,其中定义有三个方法,具体结构如下所示。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.security.access;
import java.util.Collection;
import org.springframework.security.core.Authentication;
public interface AccessDecisionVoter<S> {
int ACCESS_GRANTED = 1;
int ACCESS_ABSTAIN = 0;
int ACCESS_DENIED = -1;
boolean supports(ConfigAttribute var1);
boolean supports(Class<?> var1);
int vote(Authentication var1, S var2, Collection<ConfigAttribute> var3);
}
vote()方法的返回结果会是AccessDecisionVoter中定义的三个常量之一。
- ACCESS_GRANTED表示同意
- ACCESS_DENIED表示拒绝
- ACCESS_ABS7AIN表示弃权
如果一个AccessDecisionVoter不能判定当前 Authentication是否拥有访问对应受保护对象的权限,则其vote()方法的返回值应当为弃权ACCESS_ABSTAIN。
Spring Security内置了三个基于投票的AccessDecisionManager实现类,它们分别是
AffirmativeBased、ConsensusBased和UnanimousBased。
AffirmativeBased的逻辑是:
- 只要有AccessDecisionVoter的投票为ACCESS_GRANTED则同意用户进行访问;
- 如果全部弃权也表示鮑;
- 如果没有一个人投赞成票,但是有人投反对票,则将抛出AccessDeniedException, Spring security默认使用的是AffirmativeBased。
ConsensusBased的逻辑是:
- 如果赞成票多于反对票则表示通过。
- 反过来,如果反对票多于赞成票则将抛出AccessDeniedException,
- 如果赞成票与反对票相同且不等于0 ,并且属性allowlfEqualGrantedDeniedDecisions的值为true,则表示通过,否则将抛出异常AccessDeniedException。参数allowlfEqualGrantedDeniedDecisions的值默认为true。
- 如果所有的AccessDecisionVoter都弃权了,则将视参数allowlfAIIAbstainDecisions的值而定,如果该值为true则表示通过,否则将抛出异常AccessDeniedExceptiono。参数allowlfAIIAbstainDecisions的值默认为false。
UnanimousBased
UnanimousBased的逻辑与另外两种实现有点不一样,另外两种会一次性把受保护对象的配置属性全部传递给AccessDecisionVoter进行投票,而UnanimousBased会一次只传递一个ConfigAttribute给AccessDecisionVoter进行投票。这也就意味着如果我们的AccessDecisionVoter的逻辑是只要传递进来的ConfigAttribute中有一个能够匹配则投赞成票,但是放到UnanimousBased中其投票结果就不一定是赞成了。
UnanimousBased的逻辑具体来说是这样的:
- 如果受保护对象配置的某一个ConfigAttribute被任意的AccessDecisionVoter反对了,则将抛出 AccessDeniedExceptiono
- 如果没有反对票,但是有赞成票,则表示通过。
- 如果全部弃权了,则将视参数allowlfAIIAbstainDecisions的值而定,true则通过,false则抛出 AccessDeniedExceptiono SpringSecurity也内置一些投票者实现类如RoleVoter、AuthenticatedVoter和WebExpressionVoter等,可以自行查阅资料进行学习。