授权流程
1、拦截请求,已认证用户访问受保护的web资源将被SecurityFilterChain中(实现类为DefaultSecurityFilterChain)的 FilterSecurityInterceptor 的子类拦截。
FilterSecurityInterceptor:
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 {
// first time this request being called, so perform security checking
if (fi.getRequest() != null && observeOncePerRequest) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
其中,InterceptorStatusToken token = super.beforeInvocation(fi);将会调用父类AbstractSecurityInterceptor的beforeInvocation方法:
protected InterceptorStatusToken beforeInvocation(Object object) {
......
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
......
// Attempt authorization
try {
this.accessDecisionManager.decide(authenticated, object, attributes);
}
......
}
2.获取资源访问策略,AbstractSecurityInterceptor的beforeInvocation方法会从 SecurityMetadataSource 的子类DefaultFilterInvocationSecurityMetadataSource 获取要访问当前资源所需要的权限Collection 。SecurityMetadataSource其实就是读取访问策略的抽象,而读取的内容就是我们配置的访问规则。
接口SecurityMetadataSource的getAttributes方法:
Collection<ConfigAttribute> getAttributes(Object object)
throws IllegalArgumentException;
子类DefaultFilterInvocationSecurityMetadataSource的getAttributes方法:
public Collection<ConfigAttribute> getAttributes(Object object) {
final HttpServletRequest request = ((FilterInvocation) object).getRequest();
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap
.entrySet()) {
if (entry.getKey().matches(request)) {
return entry.getValue();
}
}
return null;
}
3.授权决策,AbstractSecurityInterceptor的beforeInvocation方法会调用 AccessDecisionManager 的子类(AffirmativeBased,ConsensusBasesd和UnanimaousBased)的decide方法,进行授权决策。
接口AccessDecisionManager的decide方法:
/*
通过传递的参数来决定用户是否有访问对应受保护资源的权限
authentication:要访问资源的访问者的身份
object:要访问的受保护资源,web请求对应FilterInvocation
configAttributes:是受保护资源的访问策略,通过SecurityMetadataSource获取。
*/
void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
InsufficientAuthenticationException;
决策流程
AccessDecisionManager中包含了一系列的AccessDecisionVoter讲会被用来对Authentication是否有权访问受保护对象进行投票,AccessDecisionManager根据投票结果,做出最终决策。
AbstractAccessDecisionManager中的AccessDecisionVoter:
......
private List<AccessDecisionVoter<? extends Object>> decisionVoters;
......
public List<AccessDecisionVoter<? extends Object>> getDecisionVoters() {
return this.decisionVoters;
}
接口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);
}
AccessDecisionManager 的子类–AffirmativeBased,ConsensusBasesd和UnanimaousBased,会根据投票结果进行决策:
AffirmativeBased是Spring Security默认使用的投票方式:只要有一个投票通过,就表示通过。
1、只要有一个投票通过了,就表示通过。
2、如果全部弃权也表示通过。
3、如果没有人投赞成票,但是有人投反对票,则抛出AccessDeniedException.
decide方法:
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();
}
ConsensusBased:多数赞成就通过。
1、如果赞成票多于反对票则表示通过
2、如果反对票多于赞成票则抛出AccessDeniedException
3、如果赞成票与反对票相同且不等于0,并且属性allowIfEqualGrantedDeniedDecisions的值为true,则表示通过,否则抛出AccessDeniedException。参数allowIfEqualGrantedDeniedDecisions的值默认是true。
4、如果所有的AccessDecisionVoter都弃权了,则将视参数allowIfAllAbstainDecisions的值而定,如果该值为true则表示通过,否则将抛出异常AccessDeniedException。参数allowIfAllAbstainDecisions的值默认为false。
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException { int grant = 0; 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: grant++; break; case AccessDecisionVoter.ACCESS_DENIED: deny++; break; default: break; } } if (grant > deny) { return; } if (deny > grant) { throw new AccessDeniedException(messages.getMessage( "AbstractAccessDecisionManager.accessDenied", "Access is denied")); } if ((grant == deny) && (grant != 0)) { if (this.allowIfEqualGrantedDeniedDecisions) { return; } else { throw new AccessDeniedException(messages.getMessage( "AbstractAccessDecisionManager.accessDenied", "Access is denied")); } } // To get this far, every AccessDecisionVoter abstained checkAllowIfAllAbstainDecisions();}
UnanimousBased:一票否决。
1、如果受保护对象配置的某一个ConfifigAttribute被任意的AccessDecisionVoter反对了,则将抛出AccessDeniedException。
2、如果没有反对票,但是有赞成票,则表示通过。
3、如果全部弃权了,则将视参数allowIfAllAbstainDecisions的值而定,true则通过,false则抛出AccessDeniedException。
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) throws AccessDeniedException { int grant = 0; List<ConfigAttribute> singleAttributeList = new ArrayList<>(1); singleAttributeList.add(null); for (ConfigAttribute attribute : attributes) { singleAttributeList.set(0, attribute); for (AccessDecisionVoter voter : getDecisionVoters()) { int result = voter.vote(authentication, object, singleAttributeList); if (logger.isDebugEnabled()) { logger.debug("Voter: " + voter + ", returned: " + result); } switch (result) { case AccessDecisionVoter.ACCESS_GRANTED: grant++; break; case AccessDecisionVoter.ACCESS_DENIED: throw new AccessDeniedException(messages.getMessage( "AbstractAccessDecisionManager.accessDenied", "Access is denied")); default: break; } } } // To get this far, there were no deny votes if (grant > 0) { return; } // To get this far, every AccessDecisionVoter abstained checkAllowIfAllAbstainDecisions();}