Spring Security 权限鉴定基础
admin
2023-07-30 19:53:56
0

Spring Security 的权限鉴定是由 AccessDecisionManager 接口负责的。具体来说是由其中的 decide()方法负责,其定义如下。

    void decide(Authentication authentication, Object object, Collection configAttributes)
        throws AccessDeniedException, InsufficientAuthenticationException;

如你所见,该方法接收三个参数,第一个参数是包含当前用户信息的 Authentication 对象;第二个参数表示当前正在请求的受保护的对象,基本上来说是 MethodInvocation(使用 AOP)、JoinPoint(使用 Aspectj)和 FilterInvocation(Web 请求)三种类型;第三个参数表示与当前正在访问的受保护对象的配置属性,如一个角色列表。

Spring Security 的 AOP Advice 思想

对于使用 AOP 而言,我们可以使用几种不同类型的 advice:before、after、throws 和 around。其中 around advice 是非常实用的,通过它我们可以控制是否要执行方法、是否要修改方法的返回值,以及是否要抛出异常。Spring Security 在对方法调用和 Web 请求时也是使用的 around advice 的思想。在方法调用时,可以使用标准的 Spring AOP 来达到 around advice 的效果,而在进行 Web 请求时是通过标准的 Filter 来达到 around advice 的效果。

对于大部分人而言都比较喜欢对 Service 层的方法调用进行权限控制,因为我们的主要业务逻辑都是在 Service 层进行实现的。如果你只是想保护 Service 层的方法,那么使用 Spring AOP 就可以了。如果你需要直接保护领域对象,那么你可以考虑使用 Aspectj。

你可以选择使用 Aspectj 或 Spring AOP 对方法调用进行鉴权,或者选择使用 Filter 对 Web 请求进行鉴权。当然,你也可以选择使用这三种方式的任意组合进行鉴权。通常的做法是使用 Filter 对 Web 请求进行一个比较粗略的鉴权,辅以使用 Spring AOP 对 Service 层的方法进行较细粒度的鉴权。

AbstractSecurityInterceptor

AbstractSecurityInterceptor 是一个实现了对受保护对象的访问进行拦截的抽象类,其中有几个比较重要的方法。beforeInvocation()方法实现了对访问受保护对象的权限校验,内部用到了 AccessDecisionManager 和 AuthenticationManager;finallyInvocation()方法用于实现受保护对象请求完毕后的一些清理工作,主要是如果在 beforeInvocation() 中改变了 SecurityContext,则在 finallyInvocation()中需要将其恢复为原来的SecurityContext,该方法的调用应当包含在子类请求受保护资源时的 finally 语句块中;afterInvocation()方法实现了对返回结果的处理,在注入了 AfterInvocationManager 的情况下默认会调用其 decide()方法。AbstractSecurityInterceptor 只是提供了这几种方法,并且包含了默认实现,具体怎么调用将由子类负责。每一种受保护对象都拥有继承自 AbstractSecurityInterceptor 的拦截器类,MethodSecurityInterceptor将用于调用受保护的方法,而 FilterSecurityInterceptor 将用于受保护的 Web 请求。它们在处理受保护对象的请求时都具有一致的逻辑,具体的逻辑如下。

  1. 先将正在请求调用的受保护对象传递给 beforeInvocation()方法进行权限鉴定。
  2. 权限鉴定失败就直接抛出异常了。
  3. 鉴定成功将尝试调用受保护对象,调用完成后,不管是成功调用,还是抛出异常,都将执行 finallyInvocation()。
  4. 如果在调用受保护对象后没有抛出异常,则调用 afterInvocation()。

以下是 MethodSecurityInterceptor 在进行方法调用的一段核心代码。

    public Object invoke(MethodInvocation mi) throws Throwable {
        InterceptorStatusToken token = super.beforeInvocation(mi);

        Object result;
        try {
            result = mi.proceed();
        } finally {
            super.finallyInvocation(token);
        }
        returnsuper.afterInvocation(token, result);
    }

ConfigAttribute

AbstractSecurityInterceptor 的 beforeInvocation()方法内部在进行鉴权的时候使用的是注入的 AccessDecisionManager 的 decide() 方法进行的。如前所述,decide()方法是需要接收一个受保护对象对应的 ConfigAttribute 集合的。一个 ConfigAttribute 可能只是一个简单的角色名称,具体将视 AccessDecisionManager 的实现者而定。AbstractSecurityInterceptor 将使用一个 SecurityMetadataSource 对象来获取与受保护对象关联的 ConfigAttribute 集合,具体 SecurityMetadataSource 将由子类实现提供。ConfigAttribute 将通过注解的形式定义在受保护的方法上,或者通过 access 属性定义在受保护的 URL 上。例如我们常见的 就表示将 ConfigAttribute ROLE_USER 和 ROLE_ADMIN 应用在所有的 URL 请求上。对于默认的 AccessDecisionManager 的实现,上述配置意味着用户所拥有的权限中只要拥有一个 GrantedAuthority 与这两个 ConfigAttribute 中的一个进行匹配则允许进行访问。当然,严格的来说 ConfigAttribute 只是一个简单的配置属性而已,具体的解释将由 AccessDecisionManager 来决定。

RunAsManager

在某些情况下你可能会想替换保存在 SecurityContext 中的 Authentication。这可以通过 RunAsManager 来实现的。在 AbstractSecurityInterceptor 的 beforeInvocation()方法体中,在AccessDecisionManager鉴权成功后,将通过RunAsManager在现有 Authentication 基础上构建一个新的 Authentication,如果新的 Authentication 不为空则将产生一个新的 SecurityContext,并把新产生的 Authentication 存放在其中。这样在请求受保护资源时从SecurityContext中获取到的 Authentication 就是新产生的 Authentication。待请求完成后会在 finallyInvocation()中将原来的SecurityContext 重新设置给 SecurityContextHolder。AbstractSecurityInterceptor 默认持有的是一个对RunAsManager进行空实现的NullRunAsManager。此外,Spring Security 对RunAsManager有一个还有一个非空实现类RunAsManagerImpl,其在构造新的Authentication时是这样的逻辑:如果受保护对象对应的 ConfigAttribute 中拥有以“RUN_AS_”开头的配置属性,则在该属性前加上 “ROLE_”,然后再把它作为一个 GrantedAuthority 赋给将要创建的Authentication(如 ConfigAttribute 中拥有一个“RUN_AS_ADMIN”的属性,则将构建一个 “ROLE_RUN_AS_ADMIN” 的 GrantedAuthority),最后再利用原 Authentication 的 principal、权限等信息构建一个新的 Authentication 进行返回;如果不存在任何以 “RUN_AS_” 开头的 ConfigAttribute,则直接返回 null。RunAsManagerImpl 构建新的 Authentication 的核心代码如下所示。

    public Authentication buildRunAs(Authentication authentication, Object object, Collection attributes) {
        List newAuthorities = new ArrayList();
        for (ConfigAttribute attribute : attributes) {
            if (this.supports(attribute)) {
                GrantedAuthority extraAuthority = new SimpleGrantedAuthority(getRolePrefix() + attribute.getAttribute());
                newAuthorities.add(extraAuthority);
            }
        }
        if (newAuthorities.size() == 0) {
            returnnull;
        }
        // Add existing authorities
        newAuthorities.addAll(authentication.getAuthorities());
        returnnew RunAsUserToken(this.key, authentication.getPrincipal(), authentication.getCredentials(),
                newAuthorities, authentication.getClass());
    }

AfterInvocationManager

在请求受保护的对象完成以后,可以通过 afterInvocation() 方法对返回值进行修改。AbstractSecurityInterceptor 把对返回值进行修改的控制权交给其所持有的 AfterInvocationManager 了。AfterInvocationManager 可以选择对返回值进行修改、不修改或抛出异常(如:后置权限鉴定不通过)。

以下是 Spring Security 官方文档提供的一张关于 AbstractSecurityInterceptor 相关关系的图。

相关内容

热门资讯

QQ音乐提示代理模式可能无法正... QQ音乐提示代理模式可能无法正常访问,如上图所示,是怎么回事呢? 这个可能和你的网络设置有关系,首先...
别人打电话听不见我说话怎么回事... 当我们在使用手机时,可能会遇到别人打电话过来听不见声音的情况,这种情况可能是由多种原因导致的,下面我...
玻璃硬盘原理图 玻璃硬盘原理 玻璃硬盘,又称为磁头悬浮硬盘(Magnetic Head Flying Disk,MHFD),是一种...
frp内网穿透配置 HTTP ... HTTP 类型的代理相比于 TCP 类型,不仅在服务端只需要监听一个额外的端口 vhost_http...
家里监控最长能保存多少天的记录... 家里监控一般保存多久 随着科技的发展,家庭监控系统已经成为了许多家庭的必备设备,它不仅可以帮助我们...
广电4k机顶盒怎么连接 广电网... 四广电网络,即四家主流的广播电视网络运营商,包括中国电信、中国移动、中国联通和中国广电,这些运营商为...
hwid是永久激活吗 hwid... HWID,全称Hardware ID,是硬件识别码的缩写,它是计算机硬件制造商为了区分每一台设备而分...
当前安全设置不允许下载该文件的... 今天新装了一台服务器 在服务器上准备安装下载chrome浏览器,结果发现不能下载,提示当前安全设置不...
荣耀路由器中继和mesh 荣耀... 荣耀路由器中继连接后网络较慢的问题可能是由于多种原因造成的,以下是一些可能的解决方案: 1. 调整...
为什么我的局域网传输最高速度只... 网络传输的最高速度取决于你网络中瓶颈最小值。 比如传输的两台电脑 路由器等一切传输设备的限制。 这个...