SpringSecurity原理解析(七):权限校验流程

news2024/9/25 13:20:28

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.coloradmin.cn/o/2138093.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

八、4 DMA+AD多通道(代码)

一、&#xff08;1&#xff09;接线图 PA0接一个电位器&#xff0c;PA1~PA3接3个传感器的AO输出 &#xff08;2&#xff09;流程 二、代码部分 &#xff08;1&#xff09;复制AD多通道的工程 将DMA初始化的部分复制到ADC_Cmd之前 将开启DMA时钟的函数挪到前面 &#xff08;…

人工智能——猴子摘香蕉问题

一、实验目的 求解猴子摘香蕉问题&#xff0c;根据猴子不同的位置&#xff0c;求解猴子的移动范围&#xff0c;求解对应的过程&#xff0c;针对不同的目标状态进行求解。 二、实验内容 根据场景有猴子、箱子、香蕉&#xff0c;香蕉挂天花板上。定义多种谓词描述位置、状态等…

Linux入门1

文章目录 一、Linux的认识和构建1.1 初始Linux1.2 虚拟机1.3 Final Shell1.4 虚拟机快照 二 、Linux的相关知识2.1 Linux目录结构2.2 linux的基本命令 一、Linux的认识和构建 1.1 初始Linux 相比于其他的操作系统&#xff0c;Linux操作系统是在服务器端应用最为广泛和普遍被认…

力扣最热一百题——螺旋矩阵

目录 题目链接&#xff1a;54. 螺旋矩阵 - 力扣&#xff08;LeetCode&#xff09; 题目描述 示例 提示&#xff1a; 解法一&#xff1a;模拟 1. 边界初始化 2. 循环遍历矩阵 3. 从左到右遍历上边界 4. 从上到下遍历右边界 5. 从右到左遍历下边界 6. 从下到上遍历左边…

2.1 溪降技术:溪降运动的定义

目录 2.1 溪降运动的定义概览观看视频课程电子书: 溪降运动的定义**“溪降”&#xff08;Canyoning&#xff09;还是“峡谷探险”&#xff08;Canyoneering&#xff09;&#xff1f;**湿峡谷与干峡谷干峡谷**湿峡谷** 总结 2.1 溪降运动的定义 概览 溪降(新西兰) 溪降是一种在非…

P5425 [USACO19OPEN] I Would Walk 500 Miles G

*原题链接* 很离谱的题。首先可以想到暴力连边&#xff0c;整个图为一个完全图&#xff0c;将所有的边选出来&#xff0c;然后从小到大一条条加入&#xff0c;当剩下集合数量 <K 的时候就结束。答案为加入的最后一条边的大小。如果用prim算法的话时间复杂度为。足以通过此题…

yjs05——matplotlib画其他图像

不管是折线图还是散点图&#xff0c;饼状图&#xff0c;柱状图等&#xff0c;其流程都是 1.创建幕布 ❤2.画图画坐标补充信息 3.保存图像 4.展示图像 不同就是在画图时候的代码不太相同 折线&#xff1a;plt.plot(x,y) 散点&#xff1a;plt.scatter() 柱状图&#xff1a;plt.hi…

Threejs合并模型动画(下)

本文目录 前言最终效果展示一、AnimationAction1.1 简要介绍1.2 常用方法二、代码三、效果前言 我们在Threejs合并模型动画(上)以及Threejs合并模型动画(中)展示了同个模型不同动画的合并与展示,但是我们发现如果我们只是通过代码去改变模型的动画的话那肯定没意思,我们希…

微型导轨在光学仪器中的应用!

微型导轨在光学仪器中扮演着至关重要的角色&#xff0c;以其高精度、高稳定性的特点&#xff0c;提供稳定的光学路径和精确的光学元件位置。接下来&#xff0c;我们一起来看看微型导轨在光学仪器中的应用实例&#xff01; 1、显微镜&#xff1a;在显微镜中&#xff0c;微型导轨…

Linux-Swap分区使用与扩容

一、背景 在Linux系统中&#xff0c;swap空间&#xff08;通常称为swap分区&#xff09;是一个用于补充内存资源的重要组件。当系统的物理RAM不足时&#xff0c;Linux会将一部分不经常使用的内存页面移动到硬盘上的swap空间中&#xff0c;这个过程被称为分页&#xff08;paging…

Python并发100个请求:提升数据获取效率的艺术

在当今数据驱动的世界中&#xff0c;快速有效地获取数据变得至关重要。Python&#xff0c;作为一种广泛使用的编程语言&#xff0c;提供了多种并发编程工具&#xff0c;使得同时发送大量网络请求成为可能。本文将探讨如何使用Python并发地发送100个网络请求&#xff0c;并分析其…

gin集成jaeger中间件实现链路追踪

1. 背景 新业务线带来新项目启动&#xff0c;需要改进原有项目的基础框架和组件能力&#xff0c;以提升后续开发和维护效率。项目搭建主要包括技术选型、框架搭建、基础服务搭建等。这其中就涉及到链路追踪的内容&#xff0c;结合其中的踩坑情况&#xff0c;用一篇文章来说明完…

SpringBoot权限认证-Sa-Token的使用与详解

本文详细介绍了Sa-Token在Java项目中的使用方法&#xff0c;包括Sa-Token的基本概念、与其他权限框架的比较、基本语法和高级用法&#xff0c;并通过实例讲解了如何在项目中集成和使用Sa-Token。作为一款轻量级Java权限认证框架&#xff0c;Sa-Token在简化权限管理、提高开发效…

编译原理之预处理

目录 生成预处理文件的的命令 预处理做了什么 实验 --------------------------------------------------------------------------------------------------------------------------------- 本篇文章主要是带着大家一起看看预处理阶段编译器都做了些什么 --------------…

多线程1(游戏逆向)

#include<iostream> #include<windows.h> #include<tchar.h> #include<stdio.h> #include <process.h> #pragma warning(disable:4996) //exe应用程序 VOID PrintUI(CONST CHAR* ExeName, CONST CHAR* UIName, CONST CHAR* color, SHORT X坐标, …

react hooks--概述

前言 ◼ Hook 是 React 16.8 的新增特性&#xff0c;它可以让我们在不编写class的情况下使用state以及其他的React特性&#xff08;比如生命周期&#xff09;。 ◼ 我们先来思考一下class组件相对于函数式组件有什么优势&#xff1f;比较常见的是下面的优势&#xff1a; ◼ …

软件测试学习笔记丨Docker 安装、管理、搭建服务

本文转自测试人社区&#xff0c;原文链接&#xff1a;https://ceshiren.com/t/topic/32192 容器&#xff08;Docker&#xff09;技术的价值 保证环境一致性&#xff0c;只要使用相同镜像部署就可以保证一致性。轻量级虚拟化访问&#xff0c;运行更快&#xff0c;资源更小。同时…

通用与专用LabVIEW软件版本对比

在LabVIEW开发过程中&#xff0c;通常会根据项目需求设计出通用版本和专用版本两类软件。通用版本适合广泛的测试场合&#xff0c;具有较强的扩展性和适用性&#xff0c;而专用版本则针对特定设备或功能进行定制&#xff0c;提供更高的精确度和效率。两者各有优势&#xff0c;开…

《SpringBoot+Vue》Chapter01_SpringBoot介绍

SpringBoot的介绍 简单来说&#xff0c;SpringBoot就是Spring提供的用于Web开发的脚手架框架。配置简单、上手快速 SpringBoot的特性 自带tomcat、Jetty服务器可以部署war包自动配置Spring框架和第三方框架能够提供应用的健康监控和配置的监控没有代码生成&#xff0c;并且尽可…

新能源汽车充电基础设施大爆发

新能源汽车充电基础设施迈入新阶段&#xff1a;全国总量破千万&#xff0c;未来五年将翻番增长 截至2024年7月底&#xff0c;全国充电设施总量已达到1060万台&#xff0c;为超过2500万辆新能源汽车提供了充电服务。目前&#xff0c;95%的高速公路服务区已具备充电功能&#xf…