在本部分中,我们将介绍一些特性,这些特性需要了解前面的章节以及框架的一些更高级和不太常用的特性。
复杂的应用程序常常会发现不仅需要在 web 请求或方法调用级别定义访问权限。相反,安全决策需要包括 who( 想象一下,你正在设计一个宠物诊所的应用程序。基于 Spring 的应用程序的用户主要有两组:宠物诊所的工作人员和宠物诊所的客户。员工可以访问所有的数据,而你的客户只能看到他们自己的客户记录。为了更有趣,你的客户可以允许其他用户查看他们的客户记录,比如他们的 "puppy preschool" 导师或当地 "Pony Club" 的主席。以 Spring Security 为基础,你可以使用几种方法:
这些方法中的每一个都是完全合法的。但是,第一对将你的授权检查与业务代码进行配对。这方面的主要问题包括单元测试的难度增加,以及在别处重用 幸运的是,还有另外一种选择,我们将在下面讨论。
Spring Security 的 ACL 服务是在 Spring Security 的域对象实例安全能力集中在访问控制列表(ACL)的概念上。系统中的每个域对象实例都有自己的 ACL,ACL 记录谁能和不能使用该域对象的细节。考虑到这一点,Spring Security 向应用程序提供三个主要的 ACL 相关功能:
如第一点所示,Spring Security ACL 模块的主要功能之一是提供检索 ACL 的高性能方式。这个 ACL 存储库功能非常重要,因为系统中的每个域对象实例可能都有几个访问控制项,并且每个 ACL 可能以树状结构从其他 ACL 继承(Spring Security 支持开箱即用,并且非常常用)。Spring Security 的 ACL 能力经过精心设计,以提供高性能的 ACL 检索,以及可插拔缓存、死锁最小化的数据库更新、与 ORM 框架的独立性(我们直接使用 JDBC)、适当的封装和透明的数据库更新。 鉴于数据库是 ACL 模块操作的核心,让我们探讨一下实现中默认使用的四个主要表。下面按照典型 Spring Security ACL 部署的大小顺序给出这些表,最后列出的行数最多:
正如在最后一段中提到的,ACL 系统使用整数位掩蔽。不用担心,你不需要知道使用 ACL 系统时位移位的细节,但是只需要说我们有 32 位可以打开或关闭。这些位中的每一个都表示权限,默认情况下权限是读(位 0)、写(位 1)、创建(位 2)、删除(位 3)和管理(位 4)。如果希望使用其他权限,则很容易实现自己的 理解系统中域对象的数量与我们选择使用整数位屏蔽的事实完全没有关系,这一点很重要。虽然你有 32 位可用于权限,但是你可能拥有数十亿个域对象实例(这意味着 ACL_OBJECT_IDENTITY 中有数十亿行,也很可能是 ACL_ENTRY)。我们之所以这样做是因为我们发现,有时人们错误地认为他们需要为每个潜在的域对象提供一点信息,事实并非如此。 现在,我们已经提供了 ACL 系统所做的基本概述,以及它在表结构中的样子,让我们来探索关键接口。关键接口是:
请注意,我们的开箱即用 AclService 和相关的数据库类都使用 ANSI SQL。因此,这应该与所有主要数据库一起工作。在编写时,系统已经使用 Hypersonic SQL、PostgreSQL、Microsoft SQL Server 和 Oracle 进行了成功测试。 两个样本用 Spring Security 演示 ACL 模块。第一个是联系人示例,另一个是文档管理系统(DMS)示例。我们建议看一下这些例子。
要开始使用 Spring Security 的 ACL 能力,你需要将 ACL 信息存储在某个地方。这就需要使用 Spring 实现
创建了所需的模式并实例化了
下面的代码片段显示了如何创建 // Prepare the information we'd like in our access control entry (ACE) ObjectIdentity oi = new ObjectIdentityImpl(Foo.class, new Long(44)); Sid sid = new PrincipalSid("Samantha"); Permission p = BasePermission.ADMINISTRATION; // Create or update the relevant ACL MutableAcl acl = null; try { acl = (MutableAcl) aclService.readAclById(oi); } catch (NotFoundException nfe) { acl = aclService.createAcl(oi); } // Now grant some permissions via an access control entry (ACE) acl.insertAce(acl.getEntries().length, p, sid, true); aclService.updateAcl(acl); 在上面的例子中,我们用标识符号 44 检索与 "Foo" 域对象相关联的 ACL。然后我们添加一个 ACE,这样一个叫 "Samantha" 的主体可以 "administer" 这个对象。代码片段相对自解释,除了插入方法。插入式方法的第一个参数是确定 ACL 中的什么位置将插入新的条目。在上面的例子中,我们只是把新的 ACE 放在现有 ACE 的末尾。最后一个参数是指示 ACE 是否授予或拒绝的 Boolean。大部分时间,它将授予(true),但是如果它拒绝(false),权限被有效地阻塞。 Spring Security 不提供任何特殊的集成来自动创建、更新或删除作为 DAO 或存储库操作的一部分的 ACL。相反,你需要为上面的各个域对象编写代码,如上面所示。值得考虑在服务层上使用 AOP 自动将 ACL 信息与服务层操作集成。在过去我们已经发现这是一个非常有效的方法。
一旦您使用上面的技术在数据库中存储了一些 ACL 信息,下一步就是实际使用 ACL 信息作为授权决策逻辑的一部分。这里有很多选择。你可以编写自己的 在某些情况下,你希望使用 Spring Security 进行授权,但是用户在访问应用程序之前已经通过某个外部系统的可靠身份验证。我们将这些情况称为预认证场景。实例包括 X.509、Siteminder 和应用程序运行的 JavaEE 容器的身份验证。使用预认证时,Spring Security 必须
细节将取决于外部认证机制。对于 X.509,用户可以通过他们的证书信息进行标识,对于 Siteminder,则可以通过 HTTP 请求头进行标识。如果依赖于容器身份验证,那么将通过对传入的 HTTP 请求调用
因为大多数预身份验证机制遵循相同的模式,所以 Spring Security 具有一组类,这些类提供了用于实现预身份验证身份验证提供者的内部框架。这消除了重复,并且允许以结构化的方式添加新的实现,而不必从头开始编写所有内容。如果您想使用诸如 X.509 身份验证 之类的东西,你不需要知道这些类,因为它已经有了一个命名空间配置选项,该选项更易于使用和启动。如果需要使用显式的 bean 配置或正在计划编写自己的实现,那么了解所提供的实现如何工作将是有用的。你将在
该类将检查安全上下文的当前内容,如果为空,则尝试从 HTTP 请求中提取用户信息并将其提交给 protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest request); protected abstract Object getPreAuthenticatedCredentials(HttpServletRequest request);
调用这些之后,筛选器将创建包含返回数据的
与其他 Spring Security 身份验证过滤器一样,预身份验证过滤器具有
如果过滤器配置有
还有一个附加阶段,使用配置好的
预认证的提供者没有比用户加载 public interface AuthenticationUserDetailsService { UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException; }
这个接口还可以有其他用途,但是通过预身份验证,它允许访问打包在
在技术概述章中讨论了 X.509 身份验证包括在它自己的章节中。这里,我们将介绍一些为其他预认证方案提供支持的类。
外部认证系统可以通过在 HTTP 请求上设置特定的报头来向应用程序提供信息。一个众所周知的例子是 Siteminder,它在名为
使用这种过滤器的典型配置看起来是这样的: <security:http> <!-- Additional http configuration omitted --> <security:custom-filter position="PRE_AUTH_FILTER" ref="siteminderFilter" /> </security:http> <bean id="siteminderFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter"> <property name="principalRequestHeader" value="SM_USER"/> <property name="authenticationManager" ref="authenticationManager" /> </bean> <bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider"> <property name="preAuthenticatedUserDetailsService"> <bean id="userDetailsServiceWrapper" class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"> <property name="userDetailsService" ref="userDetailsService"/> </bean> </property> </bean> <security:authentication-manager alias="authenticationManager"> <security:authentication-provider ref="preauthAuthProvider" /> </security:authentication-manager>
我们在这里假设安全性命名空间正在被用于配置。还假定你已经将
类
在代码库中有一个使用这种方法的示例应用程序,因此如果你感兴趣的话,可以从 github 获取代码,并查看应用程序上下文文件。代码在 |