SpringSecurity原理解析(七):权限校验流程
SpringSecurity是一个权限管理框架,核心功能就是认证和授权;前边已经介绍了认证流程,
这里看下授权的流程和核心类。
一、权限管理的实现
1、开启对权限管理的注解控制
工程中后端各种请求资源要想被SpringSecurity的权限管理控制,首先我们需要在工程
中开启 “对权限管理的注解控制”,放开了相关的注解后我们在Controller中才可以使用
相关的注解来控制权限管理;可以通过 “注解” 和 “标签” 的方式开启
1)基于“注解”的方式开启 “对权限管理的注解控制” 是在配置类或启动类通过注解
@EnableGlobalMethodSecurity 实现的,如下图所示:
2)基于“标签” 的方式开启 “对权限管理的注解控制” 是在配置文件中实现的,
如下图所示:
2、jsr250、spring、springsecurity 提供的权限管理注解在使用上的区别
1)使用jsr250提供的注解来进行权限管理
/*******************************************************
*
* 采用JSR250注解 开启权限管理
*******************************************************/
@Controller
@RequestMapping("/jsr")
public class Jsr250Controller {
@RolesAllowed(value = {"ROLE_ADMIN"})
@RequestMapping("/query")
public String query(){
System.out.println("用户查询....");
return "/home.jsp";
}
@RolesAllowed(value = {"ROLE_USER"})
@RequestMapping("/save")
public String save(){
System.out.println("用户添加....");
return "/home.jsp";
}
@RequestMapping("/update")
public String update(){
System.out.println("用户更新....");
return "/home.jsp";
}
}
2)使用spring表达式来进行权限管理
/*******************************************************
* 使用spring 表达式开启权限管理
* 注意权限注解的区别
*
*******************************************************/
@Controller
@RequestMapping("/order")
public class OrderController {
@PreAuthorize(value = "hasAnyRole('ROLE_USER')")
@RequestMapping("/query")
public String query(){
System.out.println("用户查询....");
return "/home.jsp";
}
@PreAuthorize(value = "hasAnyRole('ROLE_ADMIN')")
@RequestMapping("/save")
public String save(){
System.out.println("用户添加....");
return "/home.jsp";
}
@RequestMapping("/update")
public String update(){
System.out.println("用户更新....");
return "/home.jsp";
}
}
3)使用 springsecurity 提供的注解来进行权限管理
/*******************************************************
* 使用 SpringSecurity提供的注解开启权限管理
* 注意权限注解的区别
*
* @author lbf
* @date 2024-09-14 16:42
*******************************************************/
@Controller
@RequestMapping("/role")
public class RoleController {
@Secured(value = "ROLE_USER")
@RequestMapping("/query")
public String query(){
System.out.println("用户查询....");
return "/home.jsp";
}
@Secured("ROLE_ADMIN")
@RequestMapping("/save")
public String save(){
System.out.println("用户添加....");
return "/home.jsp";
}
@RequestMapping("/update")
public String update(){
System.out.println("用户更新....");
return "/home.jsp";
}
}
3、最后一点,在我们自定义的service服务中重写UserDetailsService的loadUserByUsername
中进行用户认证的时候,需要绑定用户的权限数据,如下图所示:
二、权限校验原理
在前边"请求流转流程流程" 中分析过,一个请求到达后端servlet服务时需要经过很多个拦截
器,但会在最后一个拦截器 FilterSecurityInterceptor中做认证和权限的校验操作;下面就从
拦截器 FilterSecurityInterceptor 开始分析 SpringSecurity 的权限校验原理。
如下图所示:
1、FilterSecurityInterceptor
记住一点:任何拦截器的处里具体拦截操作的方法都是 doFilter 方法。
打开 FilterSecurityInterceptor 类找到 doFilter 方法 发现,在 doFilter 方法中先创建一个
FilterInvocation 对象,然后以 FilterInvocation 对象为参数调用FilterSecurityInterceptor.invoke
方法, doFilter方法代码如下:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
this.invoke(new FilterInvocation(request, response, chain));
}
接着,先看下 FilterInvocation 的构造函数,通过构造函数可以看到,FilterInvocation其实就是
对Request,Response和FilterChain做了一个非空的校验及封装。如下所示:
public FilterInvocation(ServletRequest request, ServletResponse response, FilterChain chain) {
Assert.isTrue(request != null && response != null && chain != null, "Cannot pass null values to constructor");
this.request = (HttpServletRequest)request;
this.response = (HttpServletResponse)response;
this.chain = chain;
}
接着进入 FilterSecurityInterceptor.invoke 方法中,看下具体的处里;
FilterSecurityInterceptor.invoke 代码如下:
public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
if (this.isApplied(filterInvocation) && this.observeOncePerRequest) {
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
} else {
if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
filterInvocation.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
}
/*
核心方法,在该方法中完成了用户认证校验和权限校验操作
*/
InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
try {
//执行连接器链的后续拦截器
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
} finally {
//判断SpringSecurity 对象SecurityContext是否刷新了,若已经刷新,则重新设置SecurityContext
super.finallyInvocation(token);
}
super.afterInvocation(token, (Object)null);
}
}
接着进入父类中的 beforeInvocation 方法,看下用户认证和授权的处里
beforeInvocation 代码如下:
//这个方法中完成对资源的授权
//这个object实际上就是FilterInvocation对象,里边封装了request,response,chain
protected InterceptorStatusToken beforeInvocation(Object object) {
Assert.notNull(object, "Object was null");
if (!this.getSecureObjectClass().isAssignableFrom(object.getClass())) {
throw new IllegalArgumentException("Security invocation attempted for object " + object.getClass().getName() + " but AbstractSecurityInterceptor only configured to support secure objects of type: " + this.getSecureObjectClass());
} else {
/*
obtainSecurityMetadataSource(): 该方法的作用是根据当前的请求获取对应的需
要具备的权限信息,即attributes表示当前要访问的资源需要哪些权限才能访问,这个是我
们自己配置的,
ConfigAttribute是一个接口,用来对资源的访问权限进行封装,这个后边再具体看。
这里先理解成
*/
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
// 对attributes进行校验
if (CollectionUtils.isEmpty(attributes)) {
Assert.isTrue(!this.rejectPublicInvocations, () -> {
return "Secure object invocation " + object + " was denied as public invocations are not allowed via this interceptor. This indicates a configuration error because the rejectPublicInvocations property is set to 'true'";
});
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Authorized public object %s", object));
}
this.publishEvent(new PublicInvocationEvent(object));
return null;
} else {
if (SecurityContextHolder.getContext().getAuthentication() == null) {
this.credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes);
}
/*
检查当前的authentication对象是否是已经认证过的,如果没有
它会调用认证管理器尝试进行一次认证
*/
Authentication authenticated = this.authenticateIfRequired();
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Authorizing %s with attributes %s", object, attributes));
}
/*
执行 attemptAuthorization 方法进行权限校验
*/
this.attemptAuthorization(object, attributes, authenticated);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Authorized %s with attributes %s", object, attributes));
}
if (this.publishAuthorizationSuccess) {
this.publishEvent(new AuthorizedEvent(object, attributes, authenticated));
}
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
if (runAs != null) {
SecurityContext origCtx = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
SecurityContextHolder.getContext().setAuthentication(runAs);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Switched to RunAs authentication %s", runAs));
}
return new InterceptorStatusToken(origCtx, true, attributes, object);
} else {
this.logger.trace("Did not switch RunAs authentication since RunAsManager returned null");
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
}
}
}
}
private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes, Authentication authenticated) {
try {
//调用 AccessDecisionManager的 decide 方法进行权限校验
//调用决策管理器的决策方法来授权
this.accessDecisionManager.decide(authenticated, object, attributes);
} catch (AccessDeniedException var5) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Failed to authorize %s with attributes %s using %s", object, attributes, this.accessDecisionManager));
} else if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Failed to authorize %s with attributes %s", object, attributes));
}
this.publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, var5));
throw var5;
}
}
2、ConfigAttribute 的获取
由上边可以知道,ConfigAttribute 是通过 FilterSecurityInterceptor 父类
AbstractSecurityInterceptor中的方法 obtainSecurityMetadataSource() 来获取 ConfigAttribute
的;obtainSecurityMetadataSource() 是一个抽象方法,其有2个实现,分别是:
1)FilterSecurityInterceptor.obtainSecurityMetadataSource()
2)MethodSecurityInterceptor.obtainSecurityMetadataSource()
当然,需要看在FilterSecurityInterceptor类中的实现。
FilterSecurityInterceptor.obtainSecurityMetadataSource() 方法代码如下:
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
obtainSecurityMetadataSource() 方法返回的是一个 FilterInvocationSecurityMetadataSource
类型的对象。
所以obtainSecurityMetadataSource()先获取到FilterInvocationSecurityMetadataSource,再
根据传入的object也就是最开始创建的FilterInvocation对象调用getAttributes方法来获取当
前request请求的资源需要什么权限才能访问。
2.1、FilterInvocationSecurityMetadataSource
FilterInvocationSecurityMetadataSource 是一个接口,定义如下:
虽然 FilterInvocationSecurityMetadataSource 接口的实现类有很多,但我们需要看的方法
getAttributes 的实现却只有2个即:
1)AbstractMethodSecurityMetadataSource
2)DefaultFilterInvocationSecurityMetadataSource
这里我们需要看 getAttributes 在DefaultFilterInvocationSecurityMetadataSource 中的实现;
DefaultFilterInvocationSecurityMetadataSource代码如下:
public class DefaultFilterInvocationSecurityMetadataSource implements
FilterInvocationSecurityMetadataSource {
protected final Log logger = LogFactory.getLog(getClass());
//这个map很重要,key:请求的匹配器,value:某个请求需要哪些权限才能访问
private final Map<RequestMatcher, Collection<ConfigAttribute>> requestMap;
//构造方法,map是通过构造方法传进来的
public DefaultFilterInvocationSecurityMetadataSource(
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap) {
this.requestMap = requestMap;
}
//这就是上边获取ConfigAttribute时调用的方法
public Collection<ConfigAttribute> getAttributes(Object object) {
//从object中获取到request
final HttpServletRequest request = ((FilterInvocation) object).getRequest();
//拿到上边map中的所有key进行遍历,如果找到一个key能够匹配request就会返回
//所以RequestMatcher在这个map中的顺序很重要,顺序靠前的就会被先匹配。
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap
.entrySet()) {
if (entry.getKey().matches(request)) {
return entry.getValue();
}
}
//找不到匹配的就返回null
return null;
}
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
3、AccessDescisionManager
AccessDescisionManager 从字面上看称之为决策管理器“”
前边提到,SpringSecurity 真正去请求资源进行授权的操作是在 AccessDescisionManager
中完成的;
1)AccessDescisionManager 定义如下:
public interface AccessDecisionManager {
/**
决策方法,
参数:
authentication:用户的authentication,
object:实际是FilterInvocation,
configAttributes:当前请求资源需要具备的权限列表
*/
void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException;
/*
判断当前管理器是否支持对本次请求资源授权
*/
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
}
AccessDecisionManager并不直接处理授权,它通过调用内部维护的投票器
AccessDecisionVoter来完成授权,每个投票器的投票结果有 通过,否决,弃权三种情况。
所以针对多个投票器的结果就会有 一票通过,一票否决,少数服从多数这几种授权模式,
对应的就是AccessDecisionManager的几个实现类;
AccessDecisionManager 默认实现有3个,如下图所示:
但这三个默认实现并不是直接实现AccessDecisionManager 接口 ,而是继承抽象
类 AbstractAccessDecisionManager;AbstractAccessDecisionManager 是直接实
现 AccessDecisionManager 接口,但并没有重写决策方法 decide,决策方法 decide
方法是在三个子类中实现的。
AbstractAccessDecisionManager 定义如下:
public abstract class AbstractAccessDecisionManager implements AccessDecisionManager, InitializingBean, MessageSourceAware {
protected final Log logger = LogFactory.getLog(this.getClass());
//维护的投票器列表
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;
}
/*
判断是否所有的投票器都弃权了,
*/
protected final void checkAllowIfAllAbstainDecisions() {
if (!this.isAllowIfAllAbstainDecisions()) {
throw new AccessDeniedException(this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
}
/**
遍历投票器列表,只要有一个投票器支持这个ConfigAttribute,则此管理器就支持此次授权
*/
public boolean supports(ConfigAttribute attribute) {
Iterator var2 = this.decisionVoters.iterator();
AccessDecisionVoter voter;
do {
if (!var2.hasNext()) {
return false;
}
voter = (AccessDecisionVoter)var2.next();
} while(!voter.supports(attribute));
return true;
}
public boolean supports(Class<?> clazz) {
Iterator var2 = this.decisionVoters.iterator();
AccessDecisionVoter voter;
do {
if (!var2.hasNext()) {
return true;
}
voter = (AccessDecisionVoter)var2.next();
} while(voter.supports(clazz));
return false;
}
//其他方法省略
}
下边分别看下 AccessDecisionManager 的3个默认实现类,
3.1、AffirmativeBased
在SpringSecurity中默认的权限决策对象就是AffirmativeBased。AffirmativeBased的作用
是在众多的投票者中只要有一个返回肯定的结果,就会授予访问权限。
具体的决策逻辑如下:
public class AffirmativeBased extends AbstractAccessDecisionManager {
//通过构造方法传入的投票器集合
public AffirmativeBased(List<AccessDecisionVoter<? extends Object>> decisionVoters) {
super(decisionVoters);
}
//核心方法
//此决策管理器的决策方法,重写接口AccessDecisionManager 的方法
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
//否决的票数
int deny = 0;
//获取到所有的投票器后,循环遍历所有的投票器
//getDecisionVoters(): 获取所有的投票器
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;
}
}
//如果deny > 0 说明没有投票器投赞成的,有投了否决的 则抛出异常
if (deny > 0) {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
// To get this far, every AccessDecisionVoter abstained
// 执行到这儿说明 deny = 0 说明都投了弃权 票 然后检查是否支持都弃权,
//该方法定义在父类中
checkAllowIfAllAbstainDecisions();
}
}
3.2、ConsensusBased
ConsensusBased则是基于少数服从多数的方案来实现授权的决策方案。
ConsensusBased 代码如下:
public class ConsensusBased extends AbstractAccessDecisionManager {
/*
用于给用户提供自定义的机会,其默认值为 true,即代表允许授予权限和拒绝权限相等,
且同时也代表授予访问权限。
*/
private boolean allowIfEqualGrantedDeniedDecisions = true;
//初始化,传入投票器集合
public ConsensusBased(List<AccessDecisionVoter<?>> decisionVoters) {
super(decisionVoters);
}
/**
注意下授予权限和否决权限相等时的逻辑
*/
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++; // 同意的 grant + 1
break;
case AccessDecisionVoter.ACCESS_DENIED:
deny++; // 否决的 deny + 1
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();
}
public boolean isAllowIfEqualGrantedDeniedDecisions() {
return this.allowIfEqualGrantedDeniedDecisions;
}
public void setAllowIfEqualGrantedDeniedDecisions(boolean allowIfEqualGrantedDeniedDecisions) {
this.allowIfEqualGrantedDeniedDecisions = allowIfEqualGrantedDeniedDecisions;
}
}
3.3、UnanimousBased
UnanimousBased是最严格的决策器,要求所有的AccessDecisionVoter都授权,
才代表授予资源权限,否则就拒绝。
UnanimousBased 代码如下:
public class UnanimousBased extends AbstractAccessDecisionManager {
public UnanimousBased(List<AccessDecisionVoter<?>> decisionVoters) {
super(decisionVoters);
}
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;
}
}
}
// 执行到这儿说明没有投 否决的, grant>0 说明有投 同意的
// To get this far, there were no deny votes
if (grant > 0) {
return;
}
// 说明都投了 弃权票
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
}
下面再来看看各种投票器AccessDecisionVoter
4、AccessDecisionVoter
AccessDecisionVoter是一个投票器,负责对授权决策进行表决。表决的结构最终由
AccessDecisionManager统计,并做出最终的决策。
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);
}
AccessDecisionVoter 具体实现类图:
下边来看看常见的几种投票器
4.1、WebExpressionVoter
WebExpressionVoter是最常用的,也是SpringSecurity中默认的 FilterSecurityInterceptor
实例中 AccessDecisionManager默认的投票器,它其实就是 http.authorizeRequests()基
于 Spring-EL进行控制权限的授权决策类。
如:进入自定义spring security配置类的SpringSecurityConfiguration.configure方法,
进入 authorizeRequests()方法 ,如下图:
而getExpressionHandler返回的对应的ExpressionHandler其实就是对SPEL表达式
做相关的解析处理。
4.2、AuthenticatedVoter
AuthenticatedVoter针对的是ConfigAttribute#getAttribute() 中配置为
IS_AUTHENTICATED_FULLY 、IS_AUTHENTICATED_REMEMBERED、
IS_AUTHENTICATED_ANONYMOUSLY 权限标识时的授权决策。因此,其投票
策略比较简单,如下所示:
@Override
public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
int result = ACCESS_ABSTAIN; // 默认 弃权 0
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 (this.authenticationTrustResolver.isRememberMe(authentication)
|| isFullyAuthenticated(authentication)) {
return ACCESS_GRANTED; // 记住我的状态 放过
}
}
if (IS_AUTHENTICATED_ANONYMOUSLY.equals(attribute.getAttribute())) {
if (this.authenticationTrustResolver.isAnonymous(authentication)
|| isFullyAuthenticated(authentication)
|| this.authenticationTrustResolver.isRememberMe(authentication)) {
return ACCESS_GRANTED; // 可匿名访问 放过
}
}
}
}
return result;
}
4.3、PreInvocationAuthorizationAdviceVoter
用于处理基于注解 @PreFilter 和 @PreAuthorize 生成的
PreInvocationAuthorizationAdvice,来处理授权决策的实现。
如下图所示:
具体投票逻辑代码如下所示:
@Override
public int vote(Authentication authentication, MethodInvocation method, Collection<ConfigAttribute> attributes) {
// Find prefilter and preauth (or combined) attributes
// if both null, abstain else call advice with them
PreInvocationAttribute preAttr = findPreInvocationAttribute(attributes);
if (preAttr == null) {
// No expression based metadata, so abstain
return ACCESS_ABSTAIN;
}
return this.preAdvice.before(authentication, method, preAttr) ? ACCESS_GRANTED : ACCESS_DENIED;
}
4.4、RoleVoter
角色投票器。用于 ConfigAttribute#getAttribute() 中配置为角色的授权决策。其默认前
缀为 ROLE_,可以自定义,也可以设置为空,直接使用角色标识进行判断。这就意味着,
任何属性都可以使用该投票器投票,也就偏离了该投票器的本意,是不可取的。
public class RoleVoter implements AccessDecisionVoter<Object> {
//角色前缀
private String rolePrefix = "ROLE_";
public String getRolePrefix() {
return rolePrefix;
}
/**
* Allows the default role prefix of <code>ROLE_</code> to be overridden. May be set
* to an empty value, although this is usually not desirable.
*
* @param rolePrefix the new prefix
*/
public void setRolePrefix(String rolePrefix) {
this.rolePrefix = rolePrefix;
}
//是否支持某个ConfigAttribute的判断
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);
// attributes表示能访问资源的角色集合
//遍历
for (ConfigAttribute attribute : attributes) {
//此投票器是否支持此ConfigAttribute
if (this.supports(attribute)) {
result = ACCESS_DENIED;
// 遍历用户的权限(角色)集合,尝试找到一个匹配的
for (GrantedAuthority authority : authorities) {
if (attribute.getAttribute().equals(authority.getAuthority())) {
return ACCESS_GRANTED;
}
}
}
}
return result;
}
Collection<? extends GrantedAuthority> extractAuthorities(
Authentication authentication) {
//获取用户的权限数据。
return authentication.getAuthorities();
}
}
4.5、RoleHierarchyVoter
基于 RoleVoter,唯一的不同就是该投票器中的角色是附带上下级关系的。也就是说,
角色A包含角色B,角色B包含 角色C,此时,如果用户拥有角色A,那么理论上可以同时
拥有角色B、角色C的全部资源访问权限。
@Override
Collection<? extends GrantedAuthority> extractAuthorities(Authentication authentication) {
return this.roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities());
}