在第一篇的授权部分,有分析到
授权主要由AbstractSecurityInterceptor及其子类完成
具体到实际代码中其实是
// 用户通过了认证,基于当前用户信息,和目标对象的安全属性配置,进行相应的权限检查
this.accessDecisionManager.decide(authenticated, object, attributes);
AccessDecisionManager
AccessDecisionManager 是一个决策管理器接口,主要有下面三个方法:
void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
InsufficientAuthenticationException;
该方法是投票过程, 有三个参数 :
Authentication authentication
代表访问者当事人,是访问者的认证令牌,包含了访问者的权限Object object
表示目标安全对象Collection<ConfigAttribute> configAttributes
表示访问目标安全对象所需要的权限
boolean supports(ConfigAttribute attribute);
该方法用于检测ConfigAttribute attribute
是否是当前 AccessDecisionManager 支持的 ConfigAttribute 类型。
boolean supports(Class<?> clazz);
检测Class clazz
是否是当前 AccessDecisionManager 支持的安全对象。
AccessDecisionManager 实现
Spring Security内置的 AccessDecisionManager 的实现是抽象类 AbstractAccessDecisionManager
public abstract class AbstractAccessDecisionManager implements AccessDecisionManager,
InitializingBean, MessageSourceAware {
protected final Log logger = LogFactory.getLog(getClass());
// 通过这一组AccessDecisionVoter来投票表决完成授权
private List<AccessDecisionVoter<?>> decisionVoters;
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private boolean allowIfAllAbstainDecisions = false;
protected AbstractAccessDecisionManager(
List<AccessDecisionVoter<?>> decisionVoters) {
Assert.notEmpty(decisionVoters, "A list of AccessDecisionVoters is required");
this.decisionVoters = decisionVoters;
}
public void afterPropertiesSet() {
Assert.notEmpty(this.decisionVoters, "A list of AccessDecisionVoters is required");
Assert.notNull(this.messages, "A message source must be set");
}
protected final void checkAllowIfAllAbstainDecisions() {
if (!this.isAllowIfAllAbstainDecisions()) {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
}
public List<AccessDecisionVoter<?>> getDecisionVoters() {
return this.decisionVoters;
}
public boolean isAllowIfAllAbstainDecisions() {
return allowIfAllAbstainDecisions;
}
public void setAllowIfAllAbstainDecisions(boolean allowIfAllAbstainDecisions) {
this.allowIfAllAbstainDecisions = allowIfAllAbstainDecisions;
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
public boolean supports(ConfigAttribute attribute) {
for (AccessDecisionVoter voter : this.decisionVoters) {// 只要有一个匹配,返回true
if (voter.supports(attribute)) {
return true;
}
}
return false;
}
public boolean supports(Class<?> clazz) {
for (AccessDecisionVoter voter : this.decisionVoters) {// 只要有一个不匹配,返回false
if (!voter.supports(clazz)) {
return false;
}
}
return true;
}
}
如上述注释所说AbstractAccessDecisionManager通过一组AccessDecisionVoter来投票表决完成授权,具体实现放在其子类之中。
先简单介绍一下AccessDecisionVoter
:
AccessDecisionVoter
是一个接口,返回结果只有三种:
拒绝 : ACCESS_DENIED
弃权 : ACCESS_ABSTAIN
允许 : ACCESS_GRANTED
AbstractAccessDecisionManager 有三个内置实现:
AffirmativeBased -----------一票通过制
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"));
}
// 所有投票器都弃权,根据属性allowIfAllAbstainDecisions进行处理
checkAllowIfAllAbstainDecisions();
}
ConsensusBased ----------少数服从多数制
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"));
}
// 遇到相等的情况则根据allowIfEqualGrantedDeniedDecisions 判断是允许还是拒绝
if ((grant == deny) && (grant != 0)) {
if (this.allowIfEqualGrantedDeniedDecisions) {
return;
}
else {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
}
// 所有投票器都弃权,根据属性allowIfAllAbstainDecisions进行处理
checkAllowIfAllAbstainDecisions();
}
UnanimousBased ----------全票通过制
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;
}
}
}
// 方法执行到这则全部通过
if (grant > 0) {
return;
}
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
AccessDecisionVoter
通过上面的分析,得知AccessDecisionManager是通过AccessDecisionVoter投票来决定是否允许访问的,那么接下来就看看AccessDecisionVoter。
AccessDecisionVoter 是一个投票器接口定义了三个方法:
public interface AccessDecisionVoter<S> {
int ACCESS_GRANTED = 1;
int ACCESS_ABSTAIN = 0;
int ACCESS_DENIED = -1;
// 判断是否支持目标ConfigAttribute
boolean supports(ConfigAttribute attribute);
// 判断是否支持目标class
boolean supports(Class<?> clazz);
// 投票
int vote(Authentication authentication, S object,
Collection<ConfigAttribute> attributes);
}
spring security 内置的voter 有:
名称 | 支持Secure Object类型 | 支持ConfigAttribute类型 |
---|---|---|
WebExpressionVoter | FilterInvocation | web表达式 |
Jsr250Voter | MethodInvocation | Jsr250SecurityConfig |
PreInvocationAuthorizationAdviceVoter | Object | PreInvocationAttribute |
AuthenticatedVoter | Object | 可返回属于特定集合的字符串的ConfigAttribute |
RoleVoter | Object | 可返回带有特定前缀(缺省ROLE_)的字符串的ConfigAttribute |
这里重点了解一下两种voter
RoleVoter与WebExpressionVoter
RoleVoter 将ConfigAttribute简单的看作是一个角色名称,在投票的时如果拥有该角色即投赞成票
public class RoleVoter implements AccessDecisionVoter<Object> {
private String rolePrefix = "ROLE_";
public String getRolePrefix() {
return rolePrefix;
}
public void setRolePrefix(String rolePrefix) {
this.rolePrefix = rolePrefix;
}
// 如果ConfigAttribute是以“ROLE_”开头的,则将使用RoleVoter进行投票
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();
}
}
WebExpressionVoter,表达式投票器用于对类似hasAnyRole('normal','admin')
之类的表达式进行解析投票
public class WebExpressionVoter implements AccessDecisionVoter<FilterInvocation> {
private SecurityExpressionHandler<FilterInvocation> expressionHandler = new DefaultWebSecurityExpressionHandler();
public int vote(Authentication authentication, FilterInvocation fi,
Collection<ConfigAttribute> attributes) {
assert authentication != null;
assert fi != null;
assert attributes != null;
// 获取表达式配置
WebExpressionConfigAttribute weca = findConfigAttribute(attributes);
if (weca == null) {
// 没有相关配置则弃权
return ACCESS_ABSTAIN;
}
// 创建上下文
EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,
fi);
ctx = weca.postProcess(ctx, fi);
// 执行表达式解析
return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED
: ACCESS_DENIED;
}
private WebExpressionConfigAttribute findConfigAttribute(
Collection<ConfigAttribute> attributes) {
for (ConfigAttribute attribute : attributes) {
if (attribute instanceof WebExpressionConfigAttribute) {
return (WebExpressionConfigAttribute) attribute;
}
}
return null;
}
public boolean supports(ConfigAttribute attribute) {
return attribute instanceof WebExpressionConfigAttribute;
}
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
public void setExpressionHandler(
SecurityExpressionHandler<FilterInvocation> expressionHandler) {
this.expressionHandler = expressionHandler;
}
}
经过一轮调用最后反射到SecurityExpressionRoot 的 hasAnyAuthorityName 方法进行解析:
...
private boolean hasAnyAuthorityName(String prefix, String... roles) {
// 获取权限列表
Set<String> roleSet = getAuthoritySet();
for (String role : roles) {
// 匹配角色
String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
if (roleSet.contains(defaultedRole)) {
return true;
}
}
return false;
}
...
private Set<String> getAuthoritySet() {
if (roles == null) {
Collection<? extends GrantedAuthority> userAuthorities = authentication
.getAuthorities();
if (roleHierarchy != null) {
userAuthorities = roleHierarchy
.getReachableGrantedAuthorities(userAuthorities);
}
roles = AuthorityUtils.authorityListToSet(userAuthorities);
}
return roles;
}
...
private static String getRoleWithDefaultPrefix(String defaultRolePrefix, String role) {
if (role == null) {
return role;
}
if (defaultRolePrefix == null || defaultRolePrefix.length() == 0) {
return role;
}
if (role.startsWith(defaultRolePrefix)) {
return role;
}
return defaultRolePrefix + role;
}