当前位置: 首页 > article >正文

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());
	}


http://www.kler.cn/a/311505.html

相关文章:

  • 从0开始学docker (每日更新 24-11-7)
  • Django 详细入门介绍
  • 灵活就业,真的等同于失业吗?“三无人员”如何齐短板获贷款
  • 开源数据库 - mysql - xtrabackup工具进行备份
  • 关于git使用的图文教程(包括基本使用,处理冲突问题等等)超详细
  • java双向链表解析实现双向链表的创建含代码
  • TypeScript异常处理
  • Spring IoC 配置类 总结
  • LabVIEW多语言支持优化
  • [数据集][目标检测]不同颜色的安全帽检测数据集VOC+YOLO格式7574张5类别
  • 版本控制之Git
  • U盘显示未被格式化:深入解析、恢复策略与预防之道
  • 前端mock了所有……
  • firewalld实现NAT端口转发
  • 美国站群服务器优化技巧解析
  • Opencv边缘检测(四)
  • 梯度计算中的一些算子
  • 魔方财务迁移指南
  • 【mysql面试题】mysql复习之常见面试题(二)
  • Conda和pip 清空缓存
  • Vue Router v3 深入指南:配置、概念、代码演示及应用场景
  • 新发布的OpenAI o1生成式AI模型在强化学习方面迈出了重要的一步
  • PHP发邮件教程:配置SMTP服务器发送邮件?
  • Mysql高级篇(中)—— SQL优化
  • 【ComfyUI】自定义节点ComfyUI_LayerStyle——模仿 Adob​​e Photoshop 的图层样式、图层混合、图文混合、添加不可见水印
  • QString返回字符串的字节数