AccessDecisionManager中的supports

Spring security权限管理主要有两种
过滤器实现 FilterSecurityInterceptor
AOP MethodSecurityInterceptor

AccessDecisionManager是提供给AbstractSecurityInterceptor成为AbstractSecurityInterceptor的成员变量的
一个请求达到AbstractSecurityInterceptor时,会用请求中包含的Authentication(通过this.authenticateIfRequired()获得),进行身份权限验证

spring security对AbstractSecurityInterceptor 默认实现是FilterSecurityInterceptor,下面看一些它的关键代码

public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        this.invoke(fi);
        //因为是是基于过滤器的权限管理,而不是AOP的权限管理(MethodSecurityInterceptor),所以封装成FilterInvocation
        //FilterInvocation可获取request, response,自然再可以获得Authentication和请求的URL(url即为受验证保护的对象)
        }
}

AOP实现的权限管理是对方法的aop自然封装成MethodInvocation,这里是AbstractSecurityInterceptor类,不是MethodSecurityInterceptor,但是可以提一下

public void invoke(FilterInvocation fi) throws IOException, ServletException {
        if (fi.getRequest() != null && fi.getRequest().getAttribute("__spring_security_filterSecurityInterceptor_filterApplied") != null && this.observeOncePerRequest) {
        	//让这个请求只经过此过滤器一次处理
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());//交给后面的过滤器操作
        } else {
        //getAttribute("__spring_security_filterSecurityInterceptor_filterApplied")为null 说明这个请求时第一次到这个过滤器
            if (fi.getRequest() != null && this.observeOncePerRequest) {
                fi.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
            }

            InterceptorStatusToken token = super.beforeInvocation(fi);//主要代码之一

            try {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            } finally {
                super.finallyInvocation(token);
                //前面有super.beforeInvocation(fi),fi传进去了,可能会修改Request 里面authentication,所以finallyInvocation是还原一些信息
            }

            super.afterInvocation(token, (Object)null);
        }

    }

doFilter主要调用invoke(fi),invoke也主要调用beforeInvocation(),afterInvocation(),beforeInvocation()
满足那些if的前提下,先进行beforeInvocation(Object object),参数 object是FilterInvocation

 protected InterceptorStatusToken beforeInvocation(Object object) {
        Assert.notNull(object, "Object was null");
        boolean debug = this.logger.isDebugEnabled();
        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 {
            Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);//就可以获得受验证保护的对象(这里是url接口路径)所需要的权限
            if (attributes != null && !attributes.isEmpty()) {
                if (debug) {
                    this.logger.debug("Secure object: " + object + "; Attributes: " + attributes);
                }

                if (SecurityContextHolder.getContext().getAuthentication() == null) {//SecurityContextHolder依赖于session,无状态的对session的配置或者实现,会有区别,这里说不定以后会删除
                    this.credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes);
                }

                Authentication authenticated = this.authenticateIfRequired();//获得用户身份信息,如果没有登录的话,方法里面会创建一个匿名临时用户,SecurityContextHolder.getContext()实现的

                try {
                    this.accessDecisionManager.decide(authenticated, object, attributes);//进行决策判断
                } catch (AccessDeniedException var7) {
                    this.publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, var7));
                    throw var7;
                }

                if (debug) {
                    this.logger.debug("Authorization successful");
                }

                if (this.publishAuthorizationSuccess) {
                    this.publishEvent(new AuthorizedEvent(object, attributes, authenticated));
                }

                Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);//临时替换用户身份,runas就是被替换后的身份
                if (runAs == null) {//因为默认情况下runAsManager实例是NullRunAsManager,不做任何替换,所以返回为空
                    if (debug) {
                        this.logger.debug("RunAsManager did not change Authentication object");
                    }

                    return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);//所以就直接把SecurityContextHolder里的用户用来封装后返回
                } else {//这里就表示替换了this.authenticateIfRequired()得到的用户信息
                    if (debug) {
                        this.logger.debug("Switching to RunAs Authentication: " + runAs);
                    }

                    SecurityContext origCtx = SecurityContextHolder.getContext();//SecurityContextHolder还没有被手动跟新,所以原来的用户身份对象,后面要用它还原
                    SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());//把当前对应用户的context清空,换一个新的空的context
                    SecurityContextHolder.getContext().setAuthentication(runAs);//把新的context的Authentication设置成runas就是被替换后的身份,就更新了contextholder
                   
                    return new InterceptorStatusToken(origCtx, true, attributes, object);// InterceptorStatusToken里面有origCtx,就可以用它还原,finallyInvocation来实现,所以它的传参才是InterceptorStatusToken
                }
            } else if (this.rejectPublicInvocations) {
                throw new IllegalArgumentException("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'");
            } else {
                if (debug) {
                    this.logger.debug("Public object - authentication not attempted");
                }

                this.publishEvent(new PublicInvocationEvent(object));
                return null;
            }
        }
    }

Collection attributes = this.obtainSecurityMetadataSource().getAttributes(object);
就可以获得受验证保护的对象(这里是url接口路径)所需要的权限

然后用SecurityContextHolder.getContext().getAuthentication()来判断用户认证信息是否存在,不存在还会抛出异常,终止这次请求
this.credentialsNotFound(this.messages.getMessage(“AbstractSecurityInterceptor.authenticationNotFound”, “An Authentication object was not found in the SecurityContext”), object, attributes);
像我之前定义自己默认的security过滤器链

public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
 public HttpSecurity getHttp() throws Exception {
        if (this.http != null) {return this.http;} 
        else {
            AuthenticationEventPublisher eventPublisher = this.getAuthenticationEventPublisher();
            this.localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
            AuthenticationManager authenticationManager = this.authenticationManager();
            this.authenticationBuilder.parentAuthenticationManager(authenticationManager);
            Map<Class<?>, Object> sharedObjects = this.createSharedObjects();
            this.http = new HttpSecurity(this.objectPostProcessor, this.authenticationBuilder, sharedObjects);


            if(custom){
                System.out.println("security 配置了自定义的过滤器和拦截器");
                http.addFilter(new WebAsyncManagerIntegrationFilter());
                //http.exceptionHandling();
                http.headers();
                http.sessionManagement();
                //http.securityContext();//有鬼,登录就报错
                http.requestCache();
                http.anonymous();//不能去除 否则就会抛出An Authentication object was not found in the SecurityContext
                http.servletApi();
                //http.apply(new DefaultLoginPageConfigurer());
                http.logout();
            }

}

不能注解 http.anonymous();取消匿名过滤器,然后就报错,而这个过滤器这里加了判断,这一小段只是突然发现的插曲,看来看源码有收获的

然后根据理想顺序就调用执行 afterInvocation(InterceptorStatusToken token, Object returnedObject),第一个参数是beforeInvocation的返回值,第二个是受保护对象的返回值,afterInvocation的核心工作就是调用afterInvocationManager对returnObject进行过滤,外面是super.afterInvocation(token, (Object)null);调用虽然是空对象 但是进入方法后还是会再调用afterInvocationManager,所以就不能因为以为是传参null就不调用

public class MyAccessDecisionManager {
 public boolean supports(ConfigAttribute configAttribute) {
 		System.out.println(configAttribute);
  		//这个supports的security启动初始化时对FilterInvocationSecurityMetadataSource所包含所以角色权限( getAllConfigAttributes方法获得)进行的遍历判断支持
        return true;
    }

    public boolean supports(Class<?> aClass) {
        /*
        因为aClass可能是FilterInvocation或者MethodInterceptor
        在此类方法 decide(Authentication authentication, Object o,...)关联o
        仅支持真实类型为FilterInvocation , 此类 getAttributes(Object o) o 对o ((FilterInvocation) o).getRequestUrl();
         */
        if(aClass.equals(FilterInvocation.class)){return  true;}
        System.out.println(this.getClass().getSimpleName()+"不支持对"+aClass+"中分析判定是否拥有权限的决策方法");
        return false;
    }

}



public class FilterInvocationSecurityMetadataSource{

	//请求过程到拦截链 拦截链传参调用
   public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        String requestUrl = ((FilterInvocation) o).getRequestUrl();
        if(map.containsKey(requestUrl)){
            System.out.println("已经收集到接口:"+requestUrl+"所需权限");
        }else {
            System.out.println("警告 没有收集到接口:"+requestUrl+"所需权限");
        }
        return map.get(requestUrl);
    }

	//springboot 启动时一起调用
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        System.out.println("判断是否支持"+this.getClass().getSimpleName()+"所有角色权限,如果返回为空可以跳过AccessDecisionManager的 supports(ConfigAttribute configAttribute)提高性能");
        HashSet<ConfigAttribute> tem=new HashSet<>();
        tem.add(new PreInvocationAttribute() {
            @Override
            public String getAttribute() {
                return "测试一";
            }
        });

        tem.add(new PreInvocationAttribute() {
            @Override
            public String getAttribute() {
                return "测试二";
            }
        });

        tem.add(new PreInvocationAttribute() {
            @Override
            public String getAttribute() {
                return "测试三";
            }
        });
        return tem;
    }
}

因为public class MyAccessDecisionManager {
public boolean supports(ConfigAttribute configAttribute) {
System.out.println(configAttribute);
return true;
}
所以项目启动后会打印测试一,测试二,测试三

所以有些地方直接FilterInvocationSecurityMetadataSource中getAllConfigAttributes返回直接为空就不会调用AccessDecisionManager supports(ConfigAttribute configAttribute)可以稍微加速项目一下

最后注意一下ConfigAttribute是个接口唯一成员方法getAttribute(),但是它有很多实现
我个人常用@EnableGlobalMethodSecurity(prePostEnabled = true)
那么它实际就是PreInvocationExpressionAttribute对象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值