Spring Security:Authorization 授权(二)

Authorization 授权

  在更简单的应用程序中,身份验证可能就足够了:用户进行身份验证后,便可以访问应用程序的每个部分。

  但是大多数应用程序都有权限(或角色)的概念。想象一下:有权访问你的面向公众的Web前端页面的客户,以及有权访问单独的管理区域的管理员

  两种类型的用户都需要登录,但是仅凭身份验证并不能说明他们在系统中可以执行的操作。因此,还需要检查经过身份验证的用户的权限,即需要授权用户

  Spring Security 中的高级授权功能代表了其受欢迎程度的最令人信服的原因之一。 无论你如何选择进行身份验证,使用 Spring Security 提供的机制和提供程序,还是与容器或其他非Spring Security 认证机构集成,你会发现可以在你的应用程序中都是以一致且简单的方式使用授权服务。

 

授权架构

  ① Authorities

  身份验证,讨论所有身份验证实现如何存储 GrantedAuthority 对象列表。 这些代表已授予委托人的权限。 GrantedAuthority 对象由 AuthenticationManager 插入 Authentication 对象,并在以后做出授权决策时由 AccessDecisionManager 读取。

  GrantedAuthority 是只有一种方法的接口:

1 String getAuthority();

  此方法使 AccessDecisionManager 可以获取 GrantedAuthority 的精确 String 表示形式。 通过以字符串形式返回表示形式,大多数 AccessDecisionManager 都可以轻松地“读取” GrantedAuthority。 如果 GrantedAuthority 无法精确地表示为 String,则 GrantedAuthority 被视为“complex”,并且 getAuthority() 必须返回null。

  “complex” GrantedAuthority 的示例将是一种实现,该实现存储适用于不同客户帐号的一系列操作和权限阈值。 将复杂的 GrantedAuthority 表示为 String 会非常困难,因此getAuthority() 方法应返回 null。 这将向任何 AccessDecisionManager 指示它将需要专门支持 GrantedAuthority 实现,以便理解其内容。

  Spring Security 包含一个具体的 GrantedAuthority 实现,即 SimpleGrantedAuthority。 这允许将任何用户指定的 String 转换为 GrantedAuthority。 安全体系结构中包含的所有AuthenticationProvider 都使用 SimpleGrantedAuthority 来填充 Authentication 对象。

  ② Pre-Invocation 调用前处理

  Spring Security 提供了拦截器,用于控制对安全对象的访问,例如方法调用或 Web 请求。 AccessDecisionManager 会做出关于是否允许进行调用的预调用决定。

  1) AccessDecisionManagerAbstractSecurityInterceptor 调用,并负责做出最终的访问控制决策。 AccessDecisionManager 接口包含三种方法:

1   void decide(Authentication authentication, Object secureObject,
2       Collection<ConfigAttribute> attrs) throws AccessDeniedException;
3 
4   boolean supports(ConfigAttribute attribute);
5 
6   boolean supports(Class clazz);

  AccessDecisionManager 的 define 方法被传递给它进行授权决策所需的所有相关信息。 特别是,通过传递安全对象,可以检查实际安全对象调用中包含的那些参数。 例如,假设安全对象是 MethodInvocation。 在 MethodInvocation 中查询任何 Customer 参数,然后在 AccessDecisionManager 中实现某种安全性逻辑以确保允许主体对该客户进行操作将很容易。 如果访问被拒绝,则预期实现将引发 AccessDeniedException。

  在启动时,AbstractSecurityInterceptor 会调用 support(ConfigAttribute)方法,以确定 AccessDecisionManager 是否可以处理传递的 ConfigAttribute。 安全拦截器实现调用support(Class) 方法,以确保配置的 AccessDecisionManager 支持安全拦截器将显示的安全对象的类型。

  2)Voting-Based AccessDecisionManager

  尽管用户可以实现自己的 AccessDecisionManager 来控制授权的各个方面,但是 Spring Security 包括几种基于投票的 AccessDecisionManager 实现。 投票决策管理器说明了相关的类。

  使用此方法,将根据授权决策轮询一系列 AccessDecisionVoter 实现。 然后,AccessDecisionManager 根据对投票的评估来决定是否引发 AccessDeniedException。

  AccessDecisionVoter 接口具有三种方法:

1     int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attrs);
2 
3     boolean supports(ConfigAttribute attribute);
4 
5     boolean supports(Class clazz);

  具体的实现将返回一个整形 int,可能的值反映在 AccessDecisionVoter 静态字段 ACCESS_ABSTAINACCESS_DENIEDACCESS_GRANTED 中。 如果有投票权的实施对授权决定没有意见,则将返回 ACCESS_ABSTAIN。 如果确实有意见,则必须返回 ACCESS_DENIED 或 ACCESS_GRANTED。

  Spring Security 提供了三个具体的 AccessDecisionManager 来对选票进行汇总。 基于 ConsensusBased 的实现将基于非弃权票的共识来授予或拒绝访问权限。 提供属性以控制在票数相等或所有票都弃权的情况下的行为。 如果收到一个或多个ACCESS_GRANTED 投票,则 AffirmativeBased 实现将授予访问权限(即,如果至少有一个授予投票,则拒绝投票将被忽略)。 像基于 ConsensusBased 的实现一样,有一个参数可以控制所有选民的弃权行为。 UnanimousBased 提供程序希望获得一致的 ACCESS_GRANTED 投票才能授予访问权限,而忽略弃权。 如果有任何 ACCESS_DENIED 投票,它将拒绝访问。 与其他实现一样,如果所有投票者都弃权,则有一个控制行为的参数。

  3)RoleVoter 角色投票器

  Spring Security 提供的最常用的 AccessDecisionVoter 是简单的 RoleVoter,它将配置属性视为简单角色名称和投票,以在授予用户该角色后授予访问权限。

  4)AuthenticatedVoter 认证投票器

  另一个投票者是 AuthenticatedVoter,它可以用来区分匿名,完全认证和记住我的认证用户。 许多站点允许使用“记住我”身份验证进行某些受限访问,但是要求用户通过登录以进行完全访问来确认其身份。

  当我们使用属性 IS_AUTHENTICATED_ANONYMOUSLY 授予匿名访问权限时,此属性已由 AuthenticatedVoter 来处理。

  5)Custom Voters 自定义投票器

  显然,还可以实现一个自定义的 AccessDecisionVoter,并且可以将几乎任何所需的访问控制逻辑放入其中。 它可能特定于你的应用程序(与业务逻辑有关),也可能实现某些安全管理逻辑。 例如,限制访问次数的功能实现可以再这里实现。

  ③ After Invocation 调用后处理

  虽然在进行安全对象调用之前,AbstractSecurityInterceptor 会调用 AccessDecisionManager,但某些应用程序需要一种修改安全对象调用实际返回的对象的方法。 尽管你可以轻松实现自己的 AOP 问题来实现这一目标,但 Spring Security 提供了一个方便的挂钩,该挂钩具有几个与其 ACL 功能集成的具体实现。

  After Invocation Implementation 说明了 Spring Security 的 AfterInvocationManager 及其具体实现。

  像 Spring Security 的许多其他部分一样,AfterInvocationManager 有一个具体的实现 AfterInvocationProviderManager,它轮询 AfterInvocationProvider的列表。 每个AfterInvocationProvider 都可以修改返回对象或引发 AccessDeniedException。 实际上,由于前一个提供程序的结果将传递到列表中的下一个,因此多个提供程序可以修改对象。

  如果使用的是 AfterInvocationManager,则仍将需要允许 MethodSecurityInterceptor 的 AccessDecisionManager 进行操作的配置属性。 如果使用的是典型的 Spring Security 随附的 AccessDecisionManager 实现,则未为特定的安全方法调用定义配置属性,则将导致每个 AccessDecisionVoter 放弃投票。 反过来,如果 AccessDecisionManager 属性“allowIfAllAbstainDecisions”为 false,则将引发 AccessDeniedException。 可以通过(1)将“ allowIfAllAbstainDecisions”设置为 true(尽管通常不建议这样做)或(2)仅确保至少有一个配置属性可供 AccessDecisionVoter 投票批准授予访问权限来避免此潜在问题。 后一种(推荐)方法通常是通过 ROLE_USERROLE_AUTHENTICATED 配置属性来实现的。

通常要求应用程序中的特定角色应自动“包括”其他角色。 例如,在具有“管理员”和“用户”角色概念的应用程序中,你可能希望管理员能够执行普通用户可以执行的所有操作。 为此,你可以确保还为所有管理员用户分配了“用户”角色。 或者,你可以修改要求“用户”角色也要包括“管理员”角色的每个访问约束。 如果你的应用程序中有很多不同的角色,这可能会变得非常复杂。

  ④ Hierarchical Roles 角色层次

  角色层次结构的使用使你可以配置哪些角色(或权限)应包括其他角色。 Spring Security 的 RoleVoter 的扩展版本 RoleHierarchyVoter 配置有 RoleHierarchy,从中可以获取分配给用户的所有“可访问权限”。 典型的配置可能如下所示:

 1 <bean id="roleVoter" class="org.springframework.security.access.vote.RoleHierarchyVoter">
 2     <constructor-arg ref="roleHierarchy" />
 3 </bean>
 4 <bean id="roleHierarchy"
 5         class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
 6     <property name="hierarchy">
 7         <value>
 8             ROLE_ADMIN > ROLE_STAFF
 9             ROLE_STAFF > ROLE_USER
10             ROLE_USER > ROLE_GUEST
11         </value>
12     </property>
13 </bean>

  在这里,我们在层次结构 ROLE_ADMIN ⇒ ROLE_STAFF ⇒ ROLE_USER ⇒ ROLE_GUEST 中具有四个角色。 当对使用上述 RoleHierarchyVoter 配置的 AccessDecisionManager 评估安全约束时,使用 ROLE_ADMIN 进行身份验证的用户将表现为具有所有四个角色。 可以将 => 符号视为 “包含”。

  角色层次结构为简化应用程序的访问控制配置数据和/或减少需要分配给用户的权限数量提供了一种方便的方法。 对于更复杂的要求,你可能希望在应用程序需要的特定访问权限与分配给用户的角色之间定义逻辑映射,并在加载用户信息时在两者之间进行转换。

使用 FilterSecurityInterceptor 授权 HttpServletRequest

  FilterSecurityInterceptor 为 HttpServletRequests 提供授权。 它作为安全筛选器之一插入到 FilterChainProxy 中。

  1. FilterSecurityInterceptor 从 SecurityContextHolder 获得身份验证。

  2. FilterSecurityInterceptor 根据传递到 FilterSecurityInterceptor 中的 HttpServletRequest,HttpServletResponse 和 FilterChain 创建一个 FilterInvocation。

  3. 接下来,它将 FilterInvocation 传递给 SecurityMetadataSource 以获取 ConfigAttributes。

  4. 最后,它将 Authentication,FilterInvocation 和 ConfigAttributes 传递给 AccessDecisionManager。

    5. 如果拒绝授权,则会引发 AccessDeniedException。 在这种情况下,ExceptionTranslationFilter 处理 AccessDeniedException。

    6. 如果授予访问权限,FilterSecurityInterceptor继续执行FilterChain,该链接可允许应用程序正常处理。

  默认情况下,Spring Security的授权将要求对所有请求进行身份验证。 显式配置如下所示:(Java配置 WebSecurityConfigurerAdapter)

protected void configure(HttpSecurity http) throws Exception {
    http
        // ...
        .authorizeRequests(authorize -> authorize
            .anyRequest().authenticated()
        );
}

  通过按优先级添加更多规则,我们可以将 Spring Security 配置为具有不同的规则。

  Authorize Requests 请求认证

 1 protected void configure(HttpSecurity http) throws Exception {
 2     http
 3         // ...
 4         .authorizeRequests(authorize -> authorize                                  
 5             .mvcMatchers("/resources/**", "/signup", "/about").permitAll()         
 6             .mvcMatchers("/admin/**").hasRole("ADMIN")                             
 7             .mvcMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")   
 8             .anyRequest().denyAll()                                                
 9         );
10 }

  代码解析:

  第4行,指定了多个授权规则。 每个规则均按其声明顺序进行考虑。

  第5行,我们指定了任何用户都可以访问的多个 URL 模式。 具体来说,如果URL以“/resources/”开头,等于“ /signup”或等于“ /about”,则任何用户都可以访问请求。

  第6行,任何以 “/admin/” 开头的 URL 都将限于角色为 “ROLE_ADMIN” 的用户。 你将注意到,由于我们正在调用 hasRole 方法,因此无需指定 “ROLE_” 前缀。

  第7行,任何以 “/db/” 开头的 URL 都要求用户同时具有 “ROLE_ADMIN” 和 “ROLE_DBA”。 你会将注意到使用的是 hasRole 表达式,因此无需指定 “ ROLE_” 前缀。

  第8行,任何尚未匹配的 URL 都会被拒绝访问。 如果不想意外忘记更新授权规则,这是一个很好的策略。

  Expression-Based Access Control 基于表达式的控制访问

  Spring Security 3.0 引入了使用 Spring EL 表达式作为授权机制的能力,此外还简单地使用了之前已经看到的配置属性和访问决定投票者。 基于表达式的访问控制基于相同的体系结构,但是允许将复杂的布尔逻辑封装在单个表达式中。

  Web Security 表达式

<http>
    <intercept-url pattern="/admin*"
        access="hasRole('admin') and hasIpAddress('192.168.1.0/24')"/>
    ...
</http>

  在Web安全表达式中引用Bean

1   http
2     .authorizeRequests(authorize -> authorize
3         .antMatchers("/user/{userId}/**").access("@webSecurity.checkUserId(authentication,#userId)")
4         ...
5     );

  Web安全表达式中的路径变量

1   http
2     .authorizeRequests(authorize -> authorize
3         .antMatchers("/user/{userId}/**").access("@webSecurity.checkUserId(authentication,#userId)")
4         ...
5     );

  方法安全性表达式(配置类启用注解 - @EnableGlobalMethodSecurity(prePostEnabled = true)

1 @PreAuthorize("hasRole('USER')")
2 public void create(Contact contact);

 

对象级别安全的实现

  AOP Alliance (MethodInvocation) 安全拦截器

  在 Spring Security 2.0 之前,确保 MethodInvocation 的安全需要大量样板配置。现在,推荐的方法安全性方法是使用名称空间配置。

  方法安全性是使用 MethodSecurityInterceptor 实施的,该方法可以保护 MethodInvocation。取决于配置方法,拦截器可能特定于单个 bean,也可能在多个 bean 之间共享。拦截器使用 MethodSecurityMetadataSource 实例获取适用于特定方法调用的配置属性。 MapBasedMethodSecurityMetadataSource 用于存储以方法名称作为关键字的配置属性(可以使用通配符),当在应用程序上下文中使用<intercept-methods> 或 <protect-point> 元素定义属性时,将在内部使用该属性。其他实现将用于处理基于注释的配置。

  SecurityInterceptor 显式配置方法,当然还可以在你的应用上下文中使用AOP配置一个 MethodSecurityIterceptor 

<bean id="bankManagerSecurity" class=
    "org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor">
  <property name="authenticationManager" ref="authenticationManager"/>
  <property name="accessDecisionManager" ref="accessDecisionManager"/>
  <property name="afterInvocationManager" ref="afterInvocationManager"/>
  <property name="securityMetadataSource">
      <sec:method-security-metadata-source>
      <sec:protect method="com.mycompany.BankManager.delete*" access="ROLE_SUPERVISOR"/>
      <sec:protect method="com.mycompany.BankManager.getBalance" access="ROLE_TELLER,ROLE_SUPERVISOR"/>
      </sec:method-security-metadata-source>
  </property>
</bean>

Method Security 方法级安全

  ① EnableGlobalMethodSecurity 开启全局方法安全

  从 2.0 版开始,Spring Security 大大改进了对为服务层方法增加安全性的支持。 它提供对 JSR-250 批注安全性以及框架原始 @Secured 批注的支持。 从 3.0 开始,还可以使用新的基于表达式的注释。 可以使用 Intercept-methods 元素装饰 Bean 声明,从而对单个 Bean 应用安全性,或者可以使用 AspectJ 样式切入点在整个服务层中保护多个 Bean。

1 @EnableGlobalMethodSecurity(securedEnabled = true)
2 public class MethodSecurityConfig {
3     // ...
4 }

  向方法(在类或接口上)添加注释将相应地限制对该方法的访问。 Spring Security 的本机注释支持为该方法定义了一组属性。 这些将被传递给 AccessDecisionManage r使其做出实际决定:

 1 public interface BankService {
 2 
 3   @Secured("IS_AUTHENTICATED_ANONYMOUSLY")
 4   public Account readAccount(Long id);
 5 
 6   @Secured("IS_AUTHENTICATED_ANONYMOUSLY")
 7   public Account[] findAccounts();
 8 
 9   @Secured("ROLE_TELLER")
10   public Account post(Account account, double amount);
11 }

  ② 全局方法安全性配置

1 @EnableGlobalMethodSecurity(prePostEnabled = true)
2 public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
3     @Override
4     protected MethodSecurityExpressionHandler createExpressionHandler() {
5         // ... create and return custom MethodSecurityExpressionHandler ...
6         return expressionHandler;
7     }
8 }

  我的Spring Security中一些注解配置

 1 /**
 2  * @EnableGlobalMethodSecurity 注解允许我们在控制器的方法中使用
 3  * prePostEnabled = true:
 4  * @PreAuthorize 在方法调用之前, 基于表达式的计算结果来限制对方法的访问
 5  * @PostAuthorize 允许方法调用, 但是如果表达式计算结果为false, 将抛出一个安全性异常
 6  * @PostFilter 允许方法调用, 但必须按照表达式来过滤方法的结果
 7  * @PreFilter 允许方法调用, 但必须在进入方法之前过滤输入值
 8  * securedEnabled=true:
 9  * 开启@Secured注解过滤权限
10  */

   ③ Adding Security Pointcuts using protect-pointcut 使用保护切入点添加安全切入点

  保护切入点的使用特别强大,因为它允许你仅通过简单的声明就可以将安全性应用于许多bean。 考虑以下示例:

1 <global-method-security>
2 <protect-pointcut expression="execution(* com.mycompany.*Service.*(..))"
3     access="ROLE_USER"/>
4 </global-method-security>

  这将保护在应用程序上下文中声明的 bean(其类在 com.mycompany 包中且其类名以“Service”结尾)上的所有方法。 只有具有 ROLE_USER 角色的用户才能调用这些方法。 与 URL匹配一样,最具体的匹配项必须在切入点列表中排在第一位,因为将使用第一个匹配表达式。 安全注释优先于切入点。

  ④ Domain Object Security (ACLs) 领域对象安全(安全访问控制)

  复杂的应用程序经常会发现需要定义访问权限,而不仅仅是在 Web 请求或方法调用级别。 相反,安全决策需要同时包含谁(身份验证),哪里(方法调用)和什么(SomeDomainObject)。 换句话说,授权决策还需要考虑方法调用的实际域对象实例主题。

  假设你正在设计宠物诊所的应用程序。 基于 Spring 的应用程序将有两个主要的用户组:宠物诊所的工作人员以及宠物诊所的客户。 工作人员将有权访问所有数据,而你的客户只能看到他们自己的客户记录。 为了使其更加有趣,你的客户可以允许其他用户查看其客户记录,例如其“幼稚园幼教”导师或本地“小马俱乐部”总裁。 以 Spring Security 为基础,可以使用几种方法:

  • 编写你的业务方法以增强安全性。 你可以在“客户”域对象实例中查询集合,以确定哪些用户有权访问。 通过使用 SecurityContextHolder.getContext()。getAuthentication(),你将能够访问 Authentication 对象。
  • 编写一个 AccessDecisionVoter 以从存储在Authentication对象中的GrantedAuthority []实施安全性。 这意味着你的 AuthenticationManager 将需要使用自定义 GrantedAuthority [ ] 填充 Authentication,这些 GrantedAuthority[ ] 代表主体可以访问的每个 Customer 域对象实例。
  • 编写一个 AccessDecisionVoter 来增强安全性并直接打开目标客户域对象。 这意味着你的 Voter 需要访问 DAO,以使其能够检索 Customer 对象。 然后,它将访问 “客户” 对象的已批准用户的集合,并做出适当的决定。

  这些方法中的每一种都是完全合法的。但是,第一个将你的授权检查与你的业务代码结合在一起。这样做的主要问题包括单元测试的难度增加以及在其他地方重用客户授权逻辑会更加困难。从 Authentication 对象获取 GrantedAuthority [ ] 也可以,但是不能扩展到大量的Customer。如果用户可能能够访问 5000 个 Customer(在这种情况下不太可能,但是可以想象如果它是大型Pony Club 的受欢迎的兽医!),那么构造 Authentication 对象所消耗的内存量和所需的时间将是不可取的。最终的方法(直接从外部代码打开客户)可能是这三种方法中的最好方法。它可以实现关注点分离,并且不会滥用内存或 CPU 周期,但是仍然效率低下,因为 AccessDecisionVoter 和最终的业务方法本身都将执行对负责检索 Customer 对象的 DAO 的调用。每个方法调用两次访问显然是不可取的。此外,列出每种方法后,你都需要从头开始编写自己的访问控制列表(ACL)持久性和业务逻辑。

  关键概念

  Spring Security 的 ACL 服务位于spring-security-acl-xxx.jar 中。 你需要将此 JAR 添加到类路径中,才能使用 Spring Security 的域对象实例安全功能。

  Spring Security 的域对象实例安全性功能以访问控制列表(ACL)的概念为中心。 系统中的每个域对象实例都有其自己的ACL,并且该 ACL 记录了谁可以使用或不能使用该域对象的详细信息。 考虑到这一点,Spring Security 为你的应用程序提供了三个与 ACL 相关的主要功能:

  • 一种有效检索所有域对象的 ACL 条目(并修改这些 ACL)的方法

  • 确保在调用方法之前允许给定的主体使用对象的方法

  • 在调用方法之后,一种确保给定的主体可用于对象(或它们返回的对象)的方法

  如第一个要点所示,Spring Security ACL 模块的主要功能之一是提供一种高性能的 ACL 检索方法。 这个 ACL 储存库功能非常重要,因为系统中的每个域对象实例可能都有多个访问控制项,并且每个 ACL 都可以从其他 ACL 继承为树状结构(Spring 对此提供了开箱即用的支持) 安全性,并且非常常用)。 Spring Security 的 ACL 功能经过精心设计,可提供对 ACL 的高性能检索,以及可插拔缓存,最小化死锁的数据库更新,与 ORM 框架的独立性(直接使用 JDBC),适当的封装以及透明的数据库更新。

  给定数据库是ACL模块操作的核心,让我们探讨一下实现中默认使用的四个主表。 下面是典型的Spring Security ACL部署中按大小顺序显示的表,最后列出的行数最多:

  • ACL_SID 允许我们唯一地标识系统中的任何主体或权限(“ SID”代表“安全身份”)。 唯一的列是ID,SID的文本表示形式以及用于指示文本表示形式是引用主体名称还是GrantedAuthority 的标志。 因此,每个唯一的主体或 GrantedAuthority 只有一行。 当在接收许可的上下文中使用 SID 时,通常将其称为“收件人”。

  • ACL_CLASS 允许我们唯一地标识系统中的任何域对象类。 唯一的列是 ID 和 Java类名称。 因此,对于每个我们希望存储其ACL权限的唯一类,都有一行。

  • ACL_OBJECT_IDENTITY 存储系统中每个唯一域对象实例的信息。 列包括 ID,ACL_CLASS表的外键,唯一标识符,以便我们知道我们要为其提供信息的 ACL_CLASS实例,父级,ACL_SID表的外键以表示域对象实例的所有者,以及是否允许ACL条目继承自任何父 ACL。 对于要为其存储ACL权限的每个域对象实例,我们只有一行。

  • 最后,ACL_ENTRY 存储分配给每个收件人的个人权限。 列包括 ACL_OBJECT_IDENTITY 的外键,接收者(即 ACL_SID 的外键),是否进行审核以及表示授予或拒绝的实际权限的整数位掩码。 对于每个接收到使用域对象的权限的收件人,我们只有一行。

  如上一段所述,ACL系统使用整数位掩码。 不用担心,你不需要了解使用ACL系统的位转换的优点,但是只要说我们有32位可以打开或关闭就可以了。 这些位中的每一个代表一个权限,默认情况下,权限为读取(位0),写入(位1),创建(位2),删除(位3)和管理(位4)。 如果你希望使用其他权限,则可以轻松实现自己的 Permission 实例,并且 ACL 框架的其余部分将在不了解扩展的情况下运行。

  请务必了解,系统中域对象的数量与我们选择使用整数位掩码这一事实完全无关。 虽然你有32位可用的权限,但是你可能有数十亿个域对象实例(这意味着ACL_OBJECT_IDENTITY中的数十亿行,很可能是ACL_ENTRY)。 之所以说出这一点,是因为我们发现有时人们会误认为每个潜在的域对象都需要一点,事实并非如此。

  现在,已经基本概述了ACL系统的功能以及它在表结构中的外观,下面我们来探讨关键界面。 关键接口是:

  Acl:每个域对象都只有一个Acl对象,该对象在内部保存AccessControlEntry,并且知道Acl的所有者。 Acl不直接引用域对象,而是引用ObjectIdentity。 Acl存储在ACL_OBJECT_IDENTITY表中。

  AccessControlEntry:Acl包含多个AccessControlEntry,在框架中通常将其缩写为ACE。每个ACE都引用Permission,Sid和Acl的特定元组。 ACE也可以是授予或不授予的,并且包含审核设置。 ACE存储在ACL_ENTRY表中。

  Permission:权限代表特定的不可变位掩码,并提供用于位掩码和输出信息的便捷功能。上面介绍的基本权限(位0到4)包含在BasePermission类中。

  Sid:ACL模块需要引用主体和GrantedAuthority []。 Sid接口提供了一个间接级别,它是“安全身份”的缩写。通用类包括PrincipalSid(代表Authentication对象中的主体)和GrantedAuthoritySid。安全标识信息存储在ACL_SID表中​​。

  ObjectIdentity:每个域对象在ACL模块内部由一个ObjectIdentity表示。默认实现称为 ObjectIdentityImpl。

  AclService:检索适用于给定ObjectIdentity的Acl。在包含的实现(JdbcAclService)中,将检索操作委托给LookupStrategy。 LookupStrategy提供了一种高度优化的策略,用于使用批量检索(BasicLookupStrategy)来检索ACL信息,并支持利用实例化视图,层次查询和类似的以性能为中心的非ANSI SQL功能的自定义实现。

  MutableAclService:允许显示修改的Acl以保持持久性。如果你不愿意,则不必使用此接口。

  使用

  要开始使用Spring Security的ACL功能,你需要将ACL信息存储在某处。这需要使用Spring实例化数据源。然后将数据源注入到JdbcMutableAclService和BasicLookupStrategy实例中。后者提供高性能的ACL检索功能,而前者提供了mutator功能。有关示例配置,请参阅Spring Security附带的示例之一。你还需要使用上一节中列出的四个特定于ACL的表格填充数据库(有关适当的SQL语句,请参阅ACL示例)。

  创建所需的架构并实例化JdbcMutableAclService后,接下来需要确保你的域模型支持与Spring Security ACL软件包的互操作性。希望ObjectIdentityImpl将证明是足够的,因为它提供了多种使用方式。大多数人将拥有包含公共Serializable getId()方法的域对象。如果返回类型为long或与long兼容(例如int),则将发现不需要进一步考虑ObjectIdentity问题。 ACL模块的许多部分都依赖长标识符。如果你使用的不是long型(或int,byte等),则很有可能需要重新实现许多类。我们不打算在Spring Security的ACL模块中支持非长标识符,因为长已经与所有数据库序列(最常见的标识符数据类型)兼容,并且长度足以容纳所有常见的使用情况。

  以下代码片段显示了如何创建Acl或修改现有的Acl:

 1 // Prepare the information we'd like in our access control entry (ACE)
 2 ObjectIdentity oi = new ObjectIdentityImpl(Foo.class, new Long(44));
 3 Sid sid = new PrincipalSid("Samantha");
 4 Permission p = BasePermission.ADMINISTRATION;
 5 
 6 // Create or update the relevant ACL
 7 MutableAcl acl = null;
 8 try {
 9   acl = (MutableAcl) aclService.readAclById(oi);
10 } catch (NotFoundException nfe) {
11   acl = aclService.createAcl(oi);
12 }
13 
14 // Now grant some permissions via an access control entry (ACE)
15 acl.insertAce(acl.getEntries().length, p, sid, true);
16 aclService.updateAcl(acl);

  在上面的示例中,我们要检索与标识符为44的“Foo”域对象相关联的ACL。然后,我们添加一个ACE,以便名为“ Samantha”的主体可以“管理”该对象。 除了insertAce方法外,该代码片段是相对不言自明的。 insertAce方法的第一个参数是确定新条目将在Acl中的哪个位置插入。 在上面的示例中,我们只是将新ACE放在现有ACE的末尾。 最后一个参数是布尔值,指示ACE是授予还是拒绝。 在大多数情况下,它将被授予(true),但如果拒绝(false),则实际上将阻止该权限。

  Spring Security没有提供任何特殊的集成来自动创建,更新或删除ACL,这是DAO或存储库操作的一部分。 相反,你将需要为单个域对象编写如上所示的代码。 值得考虑的是在服务层上使用AOP来自动将ACL信息与服务层操作集成在一起。 过去,我们发现这是一种非常有效的方法。

  使用上述技术在数据库中存储一些ACL信息后,下一步就是实际将ACL信息用作授权决策逻辑的一部分。 在这里有很多选择, 你可以编写自己的AccessDecisionVoter或AfterInvocationProvider,它们分别在方法调用之前或之后触发。 这样的类将使用AclService来检索相关的ACL,然后调用Acl.isGranted(Permission []权限,Sid [] sids,布尔型管理模式)来决定是否授予权限。 或者,你可以使用AclEntryVoter,AclEntryAfterInvocationProvider或AclEntryAfterInvocationCollectionFilteringProvider类。 所有这些类都提供了一种基于声明的方法来在运行时评估ACL信息,使你无需编写任何代码。 

主要译自:Spring Security官方文档

猜你喜欢

转载自www.cnblogs.com/magic-sea/p/12804889.html