Terry.Li-彬

虚其心,可解天下之问;专其心,可治天下之学;静其心,可悟天下之理;恒其心,可成天下之业。

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  143 随笔 :: 344 文章 :: 130 评论 :: 0 Trackbacks

24.1. 概述

请注意:2.0.0之前,Spring Security称为Acegi Security。老的Acegi Security提供了一个ACL模块,放在org.[acegisecurity/springsecurity].acl包下。这个老的现在已经被抛弃,并可能会在将来的Spring Security发行版中去掉。本章讨论新的ACL模块,官方推荐在Spring Security 2.0.0或更高版本中使用,它被置于org.springframework.security.acls包下。

复杂的应用常常会有需要定义访问权限——不只是在web请求和方法调用水平的控制。而是安全决议需要由“who (Authentication), where (MethodInvocation) and what (SomeDomainObject)”构成。也就是说,授权决议还要考虑方法调用中的实际域对象。

想象你正在为宠物诊所设计一个应用。你的基于Spring的应用主要有两组用户:宠物诊所的职员和客户。职员可以访问所有数据,但客户只能看到自己的客户记录。为了使这个例子更加有趣一点,你的客户可以让其它客户看他们的客户记录,例如他们"puppy preschool"的顾问或本地"Pony Club"的总裁。以Spring Security为基础,你有几个方式可以选择:

1.      编写一个自己的业务方法强制安全控制。你可以从Customer域对象中获取一个有访问权限用户的集合。可以用SecurityContextHolder.getContext().getAuthentication()来访问Authentication对象。

2.      编写一个 AccessDecisionVoter ,通过 Authentication 中储存的 GrantedAuthority[] 来控制安全检查。这意味着你的 AuthenticationManager 要把代表用户对域对象访问权限的 GrantedAuthority []填充到Authentication对象中去。

3.      编写一个AccessDecisionVoter来强制安全控制,直接打开目标Customer域对象。这意味着你的voter需要访问DAO来获取Customer对象。它接着会访问Customer对象集合来进行安全决议。

这些方法都是合情合理的。但是,第一个方法把你的授权检查和业务代码耦合到一起了。带来的主要问题包括提高了单元测试的难度,以及Customer授权逻辑很难在其它地方重用。从Authentication中获取GrantedAuthority[]是好的,但是面对数量很大的Customers时也是不行的。如果用户访问的Customers数量可能达到5,000个(在这个例子里不大可能,但是想象如果是一个很大的Pony Club里的兽医!),构建Authentication需要耗费的内存和时间可能不是你想要的。最后一种方法,编写代码从外部直接打开Customer,可能是这3个方法里最好的一个。它成功分离了关注点,也没有滥用内存和CPU,但还是效率很低,AccessDecisionVoter和最终的业务方法都要自己来调用DAO方法来获取Customer对象。一次业务方法调用要访问两次DAO很明显也不是你想要的。另外,上面提到的每一种方法你都要自己从头来编写自己的访问控制列表(ACL)持久化和业务逻辑。

幸运地,还有另外一个选择,下面我们来讨论一下这个选择。

24.2. 关键概念

Spring SecurityACL服务发布为spring-security-acl-xxx.jar。要使用Spring Security的域对象安全,你要把这个包加入到你的classpath中。

Spring Security的域对象安全以访问控制列表(ACL)为核心概念。你系统里的每个域对象都有自己的ACL,这个ACL记录了谁能或不能访问该域对象。Spring Security为你的应用带来3个主要的ACL相关能力。

·         提供一条可以高效地为你所有的域对象获取ACL条目(以及修改ACL)的途径;

·         提供一条在方法被调用前保证给定用户有访问你的域对象许可的途径;

·         提供一条在方法被调用后保证给定用户有访问你的域对象许可的途径;

正如第一点所说,Spring SecurityACL模块的主要能力之一是提供一个高性能获取ACL的途径。这个ACL repository的能力是非常重要的,因为你系统中的每一个域对象可能有若干个访问控制条目,每个ACL又可能是从其他ACL继承而来——树型结构(这很常用,Spring Security提供了很容易使用的支持)。Spring SecurityACL模块已经很小心的考虑了设计以满足高性能的ACL检索,它利用了可插接的缓存模块,死锁最小化的数据库更新,独立于ORM框架(我们使用直接JDBC),适当的包装,以及透明的数据库更新。

数据库的设计是以ACL模块的操作为中心的,让我们来看看该模块的实现里缺省使用的4个主要数据表。下面列出在一个典型的Spring Security ACL部署中的数据表,以数据量大小排序,行数最多的在最后面:

·         ACL_SID使我们能够在系统中唯一定义任何principalauthority(“SID”表示“Security IDentity”)。主键是IDSID的文字表示,以及一个表示该SIDprincipal名还是GrantedAuthority的标识。每个principalGrantedAuthority都有一个对应的记录。当在接受许可的上下文中使用的时候,SID通常被称为“接收者(recipient)”。

·         ACL_CLASS使我们能够在系统中唯一定义任何域对象类。主键是ID,然后是CLASSjava类名)。每个我们想要为其存储ACL许可的类有一个记录。

·         ACL_OBJECT_IDENTITY存储系统里每个域对象实例的信息。其中的列包括ID;一个到表ACL_CLASS 的外键OBJECT_ID_CLASS;一个使我们知道是为哪个ACL_CLASS类实例提供信息的唯一标识OBJECT_ID_IDENTITY;父对象,一个到ACL_SID的外键OWNER_SID表示该域对象的所有者;ENTRIES_INHERITING表示是否允许ACL条目从ACL父条目继承。

·         最后,ACL_ENTRY存储为每个recipient分配的权限。它的列包括一个到表ACL_OBJECT_IDENTITY的外键,recipient(例如一个到ACL_SID的外键),是否需要审计,以及一个表示许可是授予还是拒绝的“整数位掩码”(许可可能包括多个方面,如view, edit, delete)。每个recipient对每个域对象的许可都有一个记录


上一段中提到,ACL系统使用了“整数位掩码”。不要担心,你不需要知道这些数字指向的具体位移来使用ACL系统,只需要知道我们有32个开关可以打开或关闭就可以了(一个int数值4byte,共32bits)。每一个位代表一个permission,缺省地,这些permission是:读(bit 0),写(bit 1),创建(bit 2),删除(bit 3)和管理(bit 4)。如果你要使用其他permission,可以很容易的实现自己的permission实例,ACL框架的其余部分不需要具备你定义的扩展的知识,即可完成操作。

明白我们采用这种“整数位掩码”对你的系统中域对象的数量是完全没有影响这一点是很重要的。我们的permission只能使用32bit,但是你可以有数以十亿计的域对象(这也意味着会有数以十亿计的记录在ACL_OBJECT_IDENTITY表中,ACL_ENTRY表的记录则更多)。我们特别说明这一点是因为发现有些用户错误的以为我们要为每个潜在的域对象使用一个bit,这是不对的。

现在我们已经提供了一个ACL系统的基本概览,以及在表结构上看起来的样子,现在让我们来看看关键接口。关键接口列表如下:

·         Acl:每一个域对象有且仅有一个ACL的对象,它内部拥有AccessControlEntry,并知道Acl的所有者。Acl不直接指向域对象,而是指向一个ObjectIdentityAcl是存储在ACL_OBJECT_IDENTITY表中的。

·         AccessControlEntry :一个 Acl 包含多个 AccessControlEntry ,在ACL框架中,我们常常把它简称为ACE。每个ACE都指向一组PermissionSidAclACE可以是grantingnon-granting的,同时包含有审计的相关设置。ACE保存在ACL_ENTRY表中。

·         Permission Permission 表示一个不可变的位掩码,并为位掩码机制和信息输出提供方便的功能。上面提到的 5 个基本的 Permission (bits 0 4)包含在BasePermission类中。

·         Sid ACL 模块需要参照到 principal GrantedAuthority[] Sid 接口提供实际安全对象(如 principal, role, group 等等)和 Acl 中实际内容的间接联系,简称“安全身份”。普通的实现包括有 PrincipalSid (在 Authentication 中表示一个 principal )和 GrantedAuthoritySid 。安全身份信息放在 ACL_SID表中。

·         ObjectIdentity ACL 模块中,内部用 ObjectIdentity 来表示每个域对象。缺省的实现是 ObjectIdentityImpl

·         AclService :用于获取 ObjectIdentity 适用的 Acl 。在包含的实现( JdbcAclService )中,获取操作委派给 LookupStrategy LookupStrategy 为获取 ACL 信息提供高度优化的策略,使用批获取( BasicLookupStrategy ),也支持用户实现以提供更好的性能,例如层次化查询和类似的以性能为中心的非 ANSI SQL 能力。

·         MutableAclService :允许修改 Acl 以便进行持久化,如果不是为了这个可以不必使用这个接口。

请注意我们提供的AclService和相关的数据库类都是使用ANSI SQL的。因此可以工作在所有主流数据库服务器上。在编写本文的时候,已经在Hypersonic SQL, PostgreSQL, Microsoft SQL Server Oracle上测试过。

Spring Security发行包中有两个例子可以用来演示ACL模块。其一是Contacts例子,另外一个是Document Management System (DMS)。我们建议你仔细看看这些例子。

24.3. 入门

要开始使用Spring SecurityACL能力,你需要把你的ACL保存在某个地方。这要通过Spring实例化一个DataSource。然后该数据源被注入到JdbcMutableAclServiceBasicLookupStrategy。后者提供了高性能的ACL获取能力,前者提供了可改变的能力。请参考发行包中任何一个例子来了解如何进行配置。你还需要往上一节中提到的四个表中填充数据(参考ACL例子看相关SQL语句)。

创建所需数据表并实例化JdbcMutableAclService后,你还要确认你的领域模型支持与Spring Security ACL包互操作。很可能ObjectIdentityImpl已经提供了足够的支持,它提供了多种可用的方式。大部分的域对象会包含一个public Serializable getId()方法。如果返回类型是long,或者和long兼容(例如int),你就不需要烦ObjectIdentity的问题了。ACL模块的很多部分都依赖于long identifier。如果没有使用long(或intbyte等),你有机会要重新实现几个类。我们不打算在Spring Security ACL模块中支持非long identifier,因为long和所有的数据库sequence兼容,也是最为常用的identifier数据类型,同时有足够的长度支持通常的应用场景。

下面的代码展示如何创建一个Acl,或修改一个已存在的Acl

				// 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);
		

在上面的例子中,我们获取的ACL是和一个标识(identifer)为44"Foo"域对象联系在一起的。我们增加一个ACE以便一个叫"Samantha"principal"administer"该对象。除了insertAce方法,这段代码不需要再多加说明。它的第一个参数是新的ACE要插入到Acl的什么位置。上面的例子中,我们把它放到最后。最后一个参数是一个表示该ACE授权或拒绝的boolean值。大部分时候应该为授权(true),但是如果为拒绝(false),该permission会被有效的关闭。

Spring Security没有在DAOrepository中为自动创建、更新或删除ACL提供任何特别集成。你要为域对象编写像上面这样的代码。在你的业务层中使用AOP来自动集成ACL信息和你业务层操作是值得考虑的。我们发现这是一种很有效的方式。

一旦你已经使用了上面的技术来在数据库中存储一些 ACL 信息,下一步就是实际如何使用这些 ACL 信息作为你的授权决议逻辑的一部分。在这里你有好几个选择。你可以分别在方法调用前和调用后编写自己的 AccessDecisionVoter AfterInvocationProvider 。这些类应该使用 AclService 来获取相关 ACL ,然后调用 Acl.isGranted(Permission[] permission, Sid[] sids, boolean administrativeMode) 来决定是授权还是拒绝。你也可以使用我们的 AclEntryVoter AclEntryAfterInvocationProvider AclEntryAfterInvocationCollectionFilteringProvider 类。所有这些类都提供了基于声明的方式,运行时根据这些设定信息来进行评估,把你从编写代码中解放出来。请参考例子应用学习如何使用这些类。
posted on 2010-08-26 16:01 礼物 阅读(871) 评论(0)  编辑  收藏 所属分类: Spring Security

只有注册用户登录后才能发表评论。

网站导航: