IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope:Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  WebSphere  >

结合使用基于 Enum 的访问控制列表和 EAz 进行细粒度 Java EE 授权,第 2 部分: 了解并使用 EAz 实现

developerWorks
文档选项

未显示需要 JavaScript 的文档选项

样例代码

英文原文

英文原文


级别: 中级

Robert Patt-Corner (rpatt-corner@proqual.net), 首席技术官, Proqual-IT
Keys Botzum, 高级技术人员 , IBM 

2008 年 4 月 18 日

Journal icon 尝试对访问应用程序资源进行细粒度控制的 Java™ 开发人员很快会受到内置 Java Platform Enterprise Edition (Java EE) 声明式授权的限制。本系列有关基于 Enum 授权 (EAz) 的文章共包含三个部分,第 1 部分介绍了 Java 5 中的完整访问控制列表 (ACL) 实现的基本体系结构。第 2 部分在第 1 部分提供的示例基础上,介绍 EAz 授权解决方案的使用和实现。

来自 IBM WebSphere Developer Technical Journal.

引言

基于 Enum 的授权(EAz,读作“easy”)系列提供了在 Java 5 中实现完整的访问控制列表 (ACL) 实现的方法,其中包括离散的二进制权限、任意命名的权限集、对受保护的资源应用和快速测试访问控制的方法,以及使用 JavaServer™ Faces (JSF) 显示特定访问控制的真值表的方法。

本系列的第 1 部分通过提出以下内容介绍 EAz:

  • Java EE 规范提供了强大的声明式和编程式授权功能,但是在许多应用程序要求的细粒度授权领域存在不足。
  • 截至撰写本文时,尚没有任何供应商框架能在这些领域充分扩展 Java EE。Acegi 框架走得更远,但它是完全替代 Java EE 身份验证和授权而不是扩展授权,并且目前存在一些大小方面的限制。
  • Java 5 Enum 最初的实现没有特别参考 Java EE 授权,它提供了枚举的快速位图实现,可以方便地进行修改以扩展 Java EE 授权和保留 Java EE 身份验证。
  • EAz 提供了易于使用和可扩展的实现,利用经典的 ACL 授权方法扩展了基于 Enum 模型的 Java EE 授权。

回顾示例场景

作为演示,我们将继续第 1 部分介绍的虚拟校园场景,说明如何通过 EAz 实现复杂的授权模型。作为复习,让我们回顾一下该示例到目前为止建立了哪些内容。

虚拟校园包括:

  • 多个校园,进入校园大门要求经过授权。
  • 进入每个校园中带门的大楼时都要求授权。
  • 大楼中的个人办公室。
  • 大楼中特殊的保护空间。

在本例中采用 EAz 以实现下列目的:

  • 管理权限和权限组比管理 Java EE 角色更精细,并且管理员可以容易而又安全地配置、了解和维护。
  • 引入授权资源访问的方法,使之可以直接受应用程序的域模型驱动——特别是通过授权才能进入校园、大楼、房间等。
  • 扩展并管理数千个校园、高达 100,000 个命名用户和数百个权限。
  • 与普通 Java EE 声明式和编程式安全共存,并对其进行扩展,这样,现有授权策略可以根据需要继续管理其他地方。

图 1 重申了虚拟校园布局,表 1 描述了示例中包含的组和个体,图 2 显示了您将在本文中使用 EAz 框架实现的 ACL。


图 1 示例虚拟校园布局
图 1  示例虚拟校园布局
表 1. 虚拟校园示例的组和个体
标识类型
校园 A 的用户---
校园 B 的用户---
校园 A 的工程师(工程师)---
校园 A 的生物学家(生物学家)---
校园 A 的清洁工---
Jane Linnaeus个体校园 A 的用户、生物学家
Jim Fermi个体校园 A 的用户、工程师
Stan the Yeti个体校园 A 的用户、生物学家
Ken Lay个体校园 A 的用户、校园 A 的清洁工
Danny Dafoe个体校园 A 的用户
Carla Bonheur个体校园 B 的用户

与第 1 部分一样,假设以下访问规则:

  • 只有校园 A 的用户可以进入校园 A;只有校园 B 的用户可以进入校园 B。

并且在校园 A 的用户中:

  • 生物学家和工程师可以进入自己的大楼和其中的公共空间;另外,生物学家可以进入工程楼。
  • 校园 A 的清洁工可以进入任何房间(实验室除外)。
  • 允许 Jim Fermi 进入工程楼中他自己的办公室,即个人办公室 2。
  • 只允许 Jim Fermi 和 Stan the Yeti 进入工程楼中的安全实验室 3。
  • 允许 Jane Linnaeus 进入生物楼中她自已的个人办公室 5,
  • 只有 Stan the Yeti 可以进入生物楼的安全实验室 6。

校园、大楼和房间的保护情况如图 2 所示。


图 2. 虚拟校园示例的 ACL 结构
图 2. 虚拟校园示例的 ACL 结构

(请参见第 1 部分以了解有关虚拟校园示例、EAz 操作概念和主要 EAz 体系结构的其他详细信息。)

实现和演示

本文提供了完整的 EAz 实现和虚拟校园测试,您可以通过 Eclipse 项目交换格式下载。本文将指导您完成实现,从讨论实践中的 EAz 开始,使用基于测试的方法来查看一系列 JUnit 测试对虚拟校园的 EAz 实现的验证。

一旦您完成了测试用例,您就会了解 EAz 的实际实现类,首先从 EAz Permissions 类(定义应用程序的特定 ACL 授权策略)开始,直到在 JUnit 测试示例中使用的 ACL 和其他 EAz 类。

本文最后简要讨论 EAz 中使用的 mock 和实用工具类,对特定 Java EE 实现的实现细节进行抽象,并描述在特定 Java EE 环境的实际实现中,可以放置 mock 类的区域。





回页首


EAz 实践

TestVirtualCampus.java 是一个 JUnit4 测试用例,用于演示和练习虚拟校园的 EAz 实现。该测试用例只处理房间进入,但是您可以对其进行扩展或开发并行测试用例,以测试其他虚拟校园操作,或者测试您自己对任何其他 EAz 授权场景的实现。

JUnit 中的大量代码都在 JUnit Fixture(请参见参考资料)中,即设置跨所有测试的通用测试结构的 JUnit 测试部分。该 fixture 是在 TestVirtualCampus.setUp() 方法中实现的,实现为在所有测试之前执行的 JUnit @Before 方法。

在此代码中:

  • 首先看一下清单 1 中 TestVirtualCampus 的类成员。正如您所看到的,这里有一个 ACLDao 类型的 mock 类,用于检索特定授权;此 mock DAO 使用属性文件,以便可以在不必安装数据库的情况下进行测试。属性文件位于 org.eaz.resources 包中,如 ACL_HOLDER_PROPERTIES_LOC 所示。

  • 组不是 Java EE 规范的一部分,然而,组几乎是所有实际 Java EE 实现的重要组成部分。本例在 EAz 中使用组(用字符串表示)表示用户的命名集合。

  • 您的组名称字符串需要与属性文件中的组名称完全匹配。例如,工程楼的 ACL 是从清单 2 中的 org.eaz.resources.EngrBldg.properties 文件读取的,并将 PERM_ENTER 权限授予工程师组、生物学家组和大楼清洁工组。在生产中将使用简单关系表来保存此信息。

  • 您的 Principal 对象也是 mock;我们采用实现 MockPrincipal 的方法,将组与主体直接关联,以方便测试。在 IBM® WebSphere® Application Server 中,组与 WSSubject 类和注册表相对应(请参见参考资料清单 8b)。

  • 类成员以定义 ACL 结束,用于为您将测试的各个空间提供授权。


清单 1. TestVirtualCampus
                
public class TestVirtualCampus {

	/* Define our data access object that will retrieve our ACL's.
	 * In our test the ACLDao is a mock that uses easily configured
	 * properties files to define ACL's; a production application will
	 * use a database to do the same
	 */
	ACLDao mockAclDao;
	private final static String ACL_HOLDER_PROPERTIES_LOC="org.eaz.resources.";
	
	/*
	 * Define our groups.  Groups are not Java EE constructs and are
	 * typically implemented as part of an environment-dependent
	 * registry, so consider this ours.
	 */
	public final String GROUP_CAMPUS_A_USERS="CAMPUS_A_USERS";
	public final String GROUP_CAMPUS_B_USERS="CAMPUS_B_USERS";
	public final String GROUP_CAMPUS_A_ENGRS="CAMPUS_A_ENGRS";
	public final String GROUP_CAMPUS_A_BIOLS="CAMPUS_A_BIOLS";
	public final String GROUP_CAMPUS_A_CLEANERS="CAMPUS_A_CLEANERS";
	
	/*
	 * Define our test's principal names and MockPrincipal entitites.  Our
	 * MockPrincipal class implements the java.security.Principal interface, and
	 * adds the ability to add and retrieve groups, which you'll typically find
	 * as part of an environment-specific registry.
	 */
	public final String PRINCIPAL_JANE="Jane Linnaeus";
	public final String PRINCIPAL_JIM="Jim Fermi";
	public final String PRINCIPAL_STAN="Stan the Yeti";
	public final String PRINCIPAL_KEN="Ken Lay";
	public final String PRINCIPAL_DANNY="Danny Dafoe";
	public final String PRINCIPAL_CARLA="Carla Bonheur";
	MockPrincipal jane, jim, stan, ken, danny, carla;
	
	/*
	 * Define our test ACL's, one for each space we'll probe for entry
	 */
	ACL campusA_ACL, engrBldgACL, engrBldgRoom2ACL, engrBldgSecureLab3ACL,
		biolBldgACL, biolBldgRoom5ACL, biolBldgSecureLab6ACL;


清单 2. org.eaz.resources.EngrBldg.properties
                
# This properties style file represents the ACL for the engineering building ACLHolder
# Format is groupOrPrincipal=permission
CAMPUS_A_ENGRS=PERM_ENTER
CAMPUS_A_BIOLS=PERM_ENTER
CAMPUS_A_CLEANERS=PERM_ENTER

清单 3 使用 @Before 方法设置测试的 fixture(请参见参考资料),该方法在您的任意 JUnit 测试之前执行一次:

  • 首先实例化您的 mockAclDao 和 ACLHolder。在 EAz 中,ACLHolder 表示通过授权保护的模型的一部分。虚拟校园 ACLHolder 是房间或大楼的门。您实例化每个 Holder,将其名称链接到其属性文件的位置;如前所述,在生产中,您的 ACLHolders 及其 ACL 将存储在基本关系表中。

  • 一旦您定义了面向房间的 ACL Holder,就可以使用较早创建的 mockAclDao 逐个检索它们的 ACL。在 Fixture 结束时,创建您的 mockPrincipal 并将其与各自的组成员关系相关联。在生产环境中,这通常是以 LDAP 实现的注册表、数据库或操作系统的功能。

  • Fixture 的所有工作通常都是在应用程序的初始化代码中完成的。您的 Fixture 已完成,并且您已准备好使用 EAz 进行授权。


清单 3. 设置测试 Fixture
                
@Before
public void setUp() throws Exception {
	/*
	 * Instantiate our Mock DAO and ACLHolders.  You'd implement your
	 * production DAO using a classic or Object-Relational-Mapping-based 
	 * DAO pattern.  
	 */
	mockAclDao=new ACLDao();
	
	/*
	 * ACLHolders represent parts of our Campus model that require authorization.
	 * Each is mapped to a resource file that defines its ACL.
	 */
	ACLHolder campusA = new 
ACLHolderImpl(ACL_HOLDER_PROPERTIES_LOC+"campusA");
	ACLHolder engrBldg = new 
ACLHolderImpl(ACL_HOLDER_PROPERTIES_LOC+"engrBldg");
	ACLHolder engrBldgRoom2 = new 
ACLHolderImpl(ACL_HOLDER_PROPERTIES_LOC+"engrBldgRoom2");
	ACLHolder engrBldgSecureLab3 = new 
ACLHolderImpl(ACL_HOLDER_PROPERTIES_LOC+"engrBldgSecureLab3");
	ACLHolder biolBldg = new 
ACLHolderImpl(ACL_HOLDER_PROPERTIES_LOC+"biolBldg");
	ACLHolder biolBldgRoom5 = new 
ACLHolderImpl(ACL_HOLDER_PROPERTIES_LOC+"biolBldgRoom5");
	ACLHolder biolBldgSecureLab6 = new 
ACLHolderImpl(ACL_HOLDER_PROPERTIES_LOC+"biolBldgSecureLab6");
	
	// Retrieve each ACL from the properties file persisting it
	campusA_ACL=mockAclDao.retrieveACL(campusA);
	engrBldgACL = mockAclDao.retrieveACL(engrBldg);
	engrBldgRoom2ACL=mockAclDao.retrieveACL(engrBldgRoom2);
	engrBldgSecureLab3ACL=mockAclDao.retrieveACL(engrBldgSecureLab3);
	biolBldgACL = mockAclDao.retrieveACL(biolBldg);
	biolBldgRoom5ACL=mockAclDao.retrieveACL(biolBldgRoom5);
	biolBldgSecureLab6ACL=mockAclDao.retrieveACL(biolBldgSecureLab6);
	
		
	/* 
	 * Set up our principals and their groups.  This code emulates a registry	
	*/
	
	// Jane is a Campus A Biologist
	jane = new MockPrincipal(PRINCIPAL_JANE);
	jane.addGroup(GROUP_CAMPUS_A_USERS);
	jane.addGroup(GROUP_CAMPUS_A_BIOLS);

	// Jim is a Campus A Engineer
	jim = new MockPrincipal(PRINCIPAL_JIM);
	jim.addGroup(GROUP_CAMPUS_A_USERS);
	jim.addGroup(GROUP_CAMPUS_A_ENGRS);
		
	// Stan is a Campus A Biologist
	stan = new MockPrincipal(PRINCIPAL_STAN);
	stan.addGroup(GROUP_CAMPUS_A_USERS);
	stan.addGroup(GROUP_CAMPUS_A_BIOLS);
		
	// Ken is a Campus A Building Cleaner
	ken = new MockPrincipal(PRINCIPAL_KEN);
	ken.addGroup(GROUP_CAMPUS_A_USERS);
	ken.addGroup(GROUP_CAMPUS_A_CLEANERS);
		
	// Danny is a generic Campus A user
	danny = new MockPrincipal(PRINCIPAL_DANNY);
	danny.addGroup(GROUP_CAMPUS_A_USERS);
	
	// Carla is a Campus B visiting professor
	carla = new MockPrincipal(PRINCIPAL_CARLA);
	carla.addGroup(GROUP_CAMPUS_B_USERS);
	}

清单 4 显示了您将在各个测试中使用的两个通用测试方法。从本质上说,EAz 授权是在 ACL 上对 permit() 方法的调用。permit() 方法有多种重载形式(正如我们了解 ACL 类时所讨论的)。所有形式都包含一些类型的必需权限集和一些类型的标识;这些测试中的形式将 MockPrincipal 对象用于标识。任何 EAz ACL 测试(实际上任何 ACL 测试)的本质都是以下问题: 标识 X 相对于实体 Z 是否有指定的权限 Y

assertPermitEntry 和 assertDenyEntry 方法都使用 EAz ACL 提出此问题:

  • assertPermitEntry 列出 MockPrincipal 标识的列表,并断言所有标识都具有 reqdPerms 权限。
  • assertDenyEntry 断言相反的结论:列表中的所有标识都缺乏 reqdPermissions 中的权限。

清单 4. 通用测试方法
                
private void assertPermitEntry(ACL aclToTest, PermissionSet reqdPerms, 
List<MockPrincipal> testCandidates) {
	try {
		for (MockPrincipal candidate: testCandidates){
			aclToTest.permit(reqdPerms, candidate);
		}
	} catch (Exception e) {
		fail("Should succeed");
	}
}
	
private void assertDenyEntry(ACL aclToTest, PermissionSet reqdPerms, 
List<MockPrincipal> testCandidates) {
	for (MockPrincipal candidate: testCandidates){
		try {
			aclToTest.permit(reqdPerms, candidate);
			fail("Should not succeed");
		} catch (Exception e) {
			// We expect an exception here
		}
	}
}

让该方法正常工作。清单 5 将权限应用到两个校园大门,然后应用到工程楼门:

  • 使用 Permissions.getPermissionSetForAction() 静态方法将所需的操作与 PermissionSet 相关联,该 PermissionSet 包含对操作进行授权所需的权限。
  • 指定要测试哪个 ACL。
  • 创建您希望获得授权的 MockPrincipal 候选者的列表,以及您希望被拒绝的 MockPrincipal 候选者的列表。
  • 对您的列表分别调用 assertPermitEntry() 方法和 assertDenyEntry() 方法。

TestVirtualCampus 中的所有测试方法都遵循此形式。图 3 显示了成功的测试结果。


清单 5. 应用权限
                
/**
 * Test Campus A's front gate.  We expect everyone to get in but Carla
 */
@Test public void testFrontGate(){
	PermissionSet reqdPerms = Permissions
		.getPermissionSetForAction(Permissions.AllActions.ACTION_ENTER);
	ACL aclToTest=campusA_ACL;
	MockPrincipal[] expectedPermitCandidates={jane, jim, stan, ken, danny};
	MockPrincipal[] expectedDenyCandidates={carla};
	assertPermitEntry(aclToTest, reqdPerms, Arrays.asList(expectedPermitCandidates));
	assertDenyEntry(aclToTest, reqdPerms, Arrays.asList(expectedDenyCandidates));
}

/**
 * Test the Engineering building door
 * Rules are: Engineers, Biologist and Cleaning Staff can Enter
 * 			  Generic Campus A staff cannot enter
 */
@Test public void testEngrBldgDoor(){
	PermissionSet reqdPerms = Permissions
		.getPermissionSetForAction(Permissions.AllActions.ACTION_ENTER);
	ACL aclToTest=engrBldgACL;
	MockPrincipal[] expectedPermitCandidates={jane, jim, stan, ken};
	MockPrincipal[] expectedDenyCandidates={danny, carla};
	assertPermitEntry(aclToTest, reqdPerms, Arrays.asList(expectedPermitCandidates));
	assertDenyEntry(aclToTest, reqdPerms, Arrays.asList(expectedDenyCandidates));
	}


图 3. 使用 EAz 测试虚拟校园
图 3. 使用 EAz 测试虚拟校园




回页首


探索 EAz 实现

现在您已经看到了 EAz 操作,接下来让我们看一下 EAz 实现本身的细节。首先从保存应用程序特定操作和 EAz 授权权限的 Permissions 类开始,接下来是实现关键的 permit() 方法的 ACL 类,该方法是 EAz 授权功能的核心。

Permissions 类

EAz 实现 Permissions 类的方式使其成为 EAz 应用程序声明其权限和操作的单一标准位置。权限和操作在 EAz 中都以 EnumSet 实现。权限表示应用程序的原子二进制权限;操作是应用程序的命令,即要求授权的活动。在语法术语中:

  • 操作是应用程序的谓词。
  • 权限是执行操作所需的资格。
  • ACL授权或拒绝对它们所保护的 ACLHolder 的操作。

应用程序开发人员必须为每个应用程序配置并维护一个唯一的 EAz 权限类,因为它表示应用程序的唯一权限集。清单 6 显示了虚拟校园的 Permissions 类:

  1. 首先,在静态 AllPermissions enum 中声明应用程序中的所有权限。我们的测试练习 PERM_ENTER 权限;包含其他权限是为了演示为何 AllPermissions 要枚举 EAz 需要用于授予或拒绝授权的所有权限。

  2. 定义权限后,继续定义虚拟校园的操作。在 ACTION_ENTER(在我们的 JUnit 中得到了练习)之上定义了其他操作,以强调 AllActions 可以而且必须包含应用程序中由 EAz 保护的所有操作定义。

  3. 由于两个 enum 都是静态的,因此其成员可以从应用程序中的任何位置直接引用,例如:

    AllActions.ACTION_ENTER
    或者
    AllPermissions.PERM_GRANT_ENTRY

  4. 两个静态 map 是将权限与操作相关联的应用程序结构。它们对于类是私有的,因此只能通过公共静态方法 getPermissionSetForAction()(大量用于检索 ACL 要对其进行授权的所需 PermissionSet)和 getSecuredActions()(很少使用但方便的实用程序方法,返回 EAz 保护的所有操作,主要用于 GUI 配置显示,将在本系列的第 3 部分讨论)访问。

  5. 类的静态初始化块在应用程序初始化时仅执行一次,并通过配置静态 map 来执行将权限与操作相关联的基本任务。您可以通过以下方法在此初始化方法中配置应用程序的基础授权结构:

    1. 组合执行应用程序操作所需的一组特定权限。
    2. 将操作和授权所需的权限传递给 configureAuthzAction 方法。
  6. configureAuthzAction 通过以下操作执行 map 配置的实际工作:创建用于保留所需权限的新 PermissionSet,将权限添加到 PermissionSet 中,然后配置静态 map 以保存关系。

通过这种方法,单个静态类可以声明 EAz 应用程序的完整扩展授权策略。


清单 6. 虚拟校园应用程序的 Permissions 类
                
public final class Permissions {

	/**
	 * The AllPermissions Enum enumerates all permissions available in the system.  
	 * Permissions are unary and individible and are not exposed beyond the 
	 * acl package
	 */
	protected static enum AllPermissions {
		PERM_ENTER,
		PERM_GRANT_ENTRY,
		PERM_REVOKE_ENTRY,
		PERM_LOCK_DOOR;
	}
	
	/**
	 * The AllActions Enum enumerates all actions in the system and provides the 
	 * set of keys for the available PermissionSets.  Since an action is the key 
	 * to a set of permissions it can represent one to many permissions.  
	 * Actions are exposed at the service layer
	 * 
	 * @author rpc
	 *
	 */
	public static enum AllActions {
		ACTION_ENTER,
		ACTION_MANAGE_ACCESS,
		ACTION_LOCK_DOOR;
	}
	
	/*
	 * These two maps hold the relationship between actions and
	 * permissions
	 */
	private static Map<AllActions, PermissionSet> NamedPermissionSets = 
		new EnumMap<AllActions, PermissionSet>(AllActions.class); 
	private static Map<PermissionSet, AllActions> PermissionSetToAction = 
		new HashMap<PermissionSet, AllActions>();

	/**
	 * GetPermissionSetForAction retrieves the PermissionSet 
	 * required to perform a specified action
	 * 
	 * @param psKey
	 * @return
	 */
	public static PermissionSet getPermissionSetForAction(AllActions psKey) {
		PermissionSet ps = NamedPermissionSets.get(psKey);
		if (ps ==null){
		  throw new RuntimeException ("No such permission as "+ psKey);
		}
		return ps;
	}
	
	public static Set<AllActions> getSecuredActions(){
		return NamedPermissionSets.keySet();
	}


	/**
	 * This static initializer performs the one-time work of relating permissions
	 * to actions. It delegates the configuration of each authorization action to 
	 * the configureAuthzAction method, passing in an Action enum to configure 
	 * and a list of permissions required to perform the action
	 */
	static {
		try {
			ArrayList<AllPermissions> reqdPerms;
			
			reqdPerms=new ArrayList<AllPermissions>();
			reqdPerms.add(AllPermissions.PERM_ENTER);
			configureAuthzAction(AllActions.ACTION_ENTER, reqdPerms);
			
			reqdPerms=new ArrayList<AllPermissions>();
			reqdPerms.add(AllPermissions.PERM_GRANT_ENTRY);
			reqdPerms.add(AllPermissions.PERM_REVOKE_ENTRY);	
			configureAuthzAction
			(AllActions.ACTION_MANAGE_ACCESS, reqdPerms);		

			reqdPerms=new ArrayList<AllPermissions>();
			reqdPerms.add(AllPermissions.PERM_LOCK_DOOR);
			configureAuthzAction(AllActions.ACTION_LOCK_DOOR, reqdPerms);
			
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		
	}

	/**
	 * Performs the work of relating an AllActions enum to a PermissionSet
	 * of permissions required to perform it
	 * 
	 * @param action
	 * @param reqdPermissions
	 */
	private static void configureAuthzAction(AllActions action, 
List<AllPermissions> reqdPermissions) {
		PermissionSet ps= new PermissionSetImpl();;
		for (AllPermissions reqdPerm: reqdPermissions){
			ps.addPermission(reqdPerm);
		}
		NamedPermissionSets.put(action, ps);
		PermissionSetToAction.put(ps, action);
	}
	
}

ACL 类

如果 Permissions 类包含了应用程序的授权策略,则 ACL 类实现了授权处理。ACL 需要一些自定义,但与 Permissions 不同,该自定义是针对运行时环境,而不是特定应用程序。ACL 自定义与以下事实相关:不同的 Java EE 实现以不同的方式执行或扩展 Java 安全,以处理 Java EE 规范之外的重要构件和概念,例如组。

在深入分析 ACL 自身之前,让我们简单地查看一下 ACLDao 如何从数据库或其他持久源构造 ACL。清单 7 中的片段来自 mock ACLDao,显示 ACL 中组到 PermissionSet 关联的创建,该关联用于确定可用于 ACLHolder 上的组的权限。此处,将一系列持久的个别权限(在本例中作为一组字符串令牌取回)添加到 PermissionSet 中。一旦 PermissionSet 完成,代码就会调用 ACL 的 addPermissionSet(groupName, permissionSet) 方法将此 groupName 与权限集的关联添加到 ACL 结构中。


清单 7. 来自 ACLDao 的细节,在 ACL 中创建 groupName 与 PermissionSet 的关联
                
/*
 * This loop takes each permission granted to a group,
 * adds it to a PermissionSet, then adds the group and
 * its associated PermissionSet to the ACL under
 * construction
 */	
while (tokenizer.hasMoreTokens()){
	permissionSet.addPermission(Permissions.AllPermissions
	.valueOf(tokenizer.nextToken()));
}
// Add the group and its granted permission set into the ACL
acl.addPermissionSet(groupName, permissionSet);

清单 8a 中的 ACL 类提供了一个公共构造函数,用于接收 securedResource,即 ACL 保护的ACLHolder;还提供了实用方法,对 ACL 的内部授权结构进行操作。ACL 将其授权结构作为 PermissionCollections 的 map 实现,将名称作为键。授权 map 表示相对于 ACL 的 securedResource ACLHolder 的每个“名称”可用的权限集。名称在此实现中必须唯一,并且可以表示主体名称或组名称;ACL 不对它们加以区分。

您已看到 addPermissionSet(groupName, permissionSet) 方法在 ACLDao 中的表现;它从数据库中的 ACL 的持久性表格中创建授权结构中的关联,或者,在此示例实现中为属性文件。另一个 Helper 方法是 getPermittedNames(),它返回授权中的名称集,供 GUI 显示使用;以及 getPermissionSet(String name),在核心 permit() 方法的执行过程中,它是授权结构的核心访问函数。

ACL 的 permit() 方法为方便使用提供了大量重载;其主要形式为:

permit(PermissionSet reqdPerms, List<String> nameList) throws PermissionViolationException

另一个重载从其参数中提取一个名称列表并将其委派给主要形式。操作 permit() 遍历为其提供的名称(其可以表示主体或组),然后针对操作所需的权限,检查授权 map 中与每个名称关联的权限,成功时以静默方式返回。如果选项已用尽,则方法引发 PermissionViolationException。请注意,此异常包含有关所请求权限的信息以及为请求提供的所有权限的联合,主要是出于日志记录的目的。

permit() 使用显式或隐式主题的两个重载形式留作环境特定实现的空白存根。这些方法必须经过定制以适合特定的 Java EE 应用服务器,并且应该从隐式或显式主题中提取主体和可用组的唯一形式,然后委派给 permit() 的主要形式。下一部分显示了 WebSphere 特定示例。


清单 8a.EAz ACL 类
                

/**
 * The ACL implements the process of authorization in EAz by means of permit() methods.
 */
public class ACL {
	
//	 An ACL instance secures its ACLHolder
	private ACLHolder securedResource; 
	
	/*
	 * Main internal ACL structure, mapping names to the set of permissions available
	 * to them on the ACLHolder.  Note that a name can represent
	 * either a group or a Principal
	 */
	private Map<String, PermissionSet> grants = new HashMap<String, 
	   PermissionSet>();

	/**
	 * Public constructor creating an ACL and associating it
	 * with the ACLHolder resource it secures
	 * 
	 * @param securedResource
	 */
	public ACL(ACLHolder securedResource){
		this.securedResource=securedResource;
	}
	
	/**
	 * Add assocations of names and their associated PermissionSet
	 * Could be extended to merge permissionSets
	 * 
	 * @param name
	 * @param permissionSet
	 */
	public void addPermissionSet (String name, PermissionSet permissionSet){
		grants.put(name, permissionSet);
	}

	
	
	/**
	 * This variant of permit is for implementation-independent testing of the EAZ 
	 * classes, for example in JUnit tests
	 * 
	 * @param mockPrincipal - Demonstration principal for testing, implements a
	 * 		   				  getGroups() method
	 * 		
	 * @param reqdPerms - PermissionSet of required permissions
	 * @throws PermissionViolationException
	 */
	public void permit(PermissionSet reqdPerms, MockPrincipal mockPrincipal ) throws 
	   PermissionViolationException {
		List<String> groups = new 
		ArrayList<String>(mockPrincipal.getGroups()); 
	// Create groups list from MockPrincipal groups set
		permit(reqdPerms, groups);
	}

	/**
	 * 
	 * @param reqdPerms
	 * @param singleName - convenience implementation for a single group or Principal 
	 * name
	 * @throws PermissionViolationException
	 */
	public void permit (PermissionSet reqdPerms, String singleName) throws 
	   PermissionViolationException {
		List<String> groupList = new ArrayList<String>();
		groupList.add(singleName);
		permit(reqdPerms, groupList);
	}
	
	/**
	 * @param PermissionSet reqdPerms
	 * @param List groupList
	 */
	public void permit(PermissionSet reqdPerms, List<String> nameList) throws 
	   PermissionViolationException {
		PermissionSet availablePerms=new PermissionSetImpl(), namePerms;
		for (String name: nameList){
			namePerms = grants.get(name);
			if (namePerms == null){
				continue;
			}
			availablePerms.addPermissions(namePerms.getPermissions());
			if (namePerms.hasPermissions(reqdPerms)){
				return;
			}
		}
		/* If we got here no name allowed permission, so throw the exception
		 * that indicates denied permission
		 */
		throw new PermissionViolationException (reqdPerms, availablePerms);
	}
	
	/**
	 * @param PermissionSetreqdPerms
	 */
	public void permit(PermissionSet reqdPerms) throws PermissionViolationException {
		// TODO fill in, making use of your implied implementation-dependent 
		// subject
	}

	/**
	 * @param PermissionSetreqdPerms
	 * @param ListgroupList
	 */
	public void permit(PermissionSet reqdPerms, Subject subject) throws 
	   PermissionViolationException {
		// TODO fill in, making use of an explicit implementation-dependent 
		// subject

	}
	
	/**
	 * @return the set of Groups mapped to permission sets in this ACL.  
	 */
	public Set<String> getPermittedNames() {
		return grants.keySet();
	}
	
	/**
	 * 
	 * @param name - the group to return a permission set for
	 * @return PermissionSet for the given group in the ACL or null
	 */
	public PermissionSet getPermissionSet(String name){
		return grants.get(name);
	}
	
	public ACLHolder getSecuredResource() {
		return securedResource;
	}

	public void setSecuredResource(ACLHolder theSecuredResource) {
		securedResource = theSecuredResource;
	}
	
}





回页首


重载 ACL 类

两个使用隐式或显式主题执行授权的 ACL.permit() 方法已被搁置,因为根据 Java EE 实现的不同,从主题获取组的技术也有所区别。清单 8b 演示了在 WebSphere Application Server V6.1 中,如何通过继承 ACL 的类并覆盖 WASACL 类中面向主题的 permit() 方法来检索所需的组信息。(WASACL 类要求在构建和运行时路径中包含 sas.jar 和 wssec.jar 库;这两个 JAR 文件未包含在下载中,但是已在所有 WebSphere Application Server V6.1 安装中提供。)

permit(PermissionSet reqdPerms) 方法使用当前调用者的隐式主题作为授权的组信息的源。对 WebSphere 特定 WSSubject.getCallerSubject() 的静态调用将获得调用者主题。一旦您掌握了调用者的主题,方法就会直接委派给 permit(PermissionSet reqdPerms, Subject subject),该方法采用显式主题。对于请求当前用户授权这一典型情况,您将使用隐式主题技术。使用显式主题方法可以传入任何用户的主题,并确定他们是否具有执行某项操作的权限。

permit(PermissionSet reqdPerms, Subject subject) 方法使用其显式主题参数检索 WebSphere 特定的公共凭据集。大小检查确保在集合中如预期那样只有一个条目。如果特定应用程序的设计旨在向凭据集添加附加凭据,那么您将需要修改代码,以便通过应用程序特定的方式从集合中选择正确的 WebSphere 凭据条目。

一旦 WebSphere 特定凭据集可用,您就可以使用 groups = creds.getGroupIds() 来检索组。(组将作为字符串以 LDAP 形式的格式返回。您应该编写自己的应用程序特定解析代码,以便将组字符串转换为应用程序的 ACL.grants 结构所使用的格式,这样可用组和指定组就会匹配。)

当您获得代码委派到的组时,核心 permit (reqdPerms, groups); 方法和授权将照常进行。


清单 8b.使用 WebSphere 主题的 WASACL 类
                
/**
 * WASACL extends the ACL class for WebSphere-specific example implementations of the 
 * permit() method, using (6.1) WAS-specific implicit and explicit subjects as arguments
 */
package org.eaz.acl;

import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.eaz.domain.ACLHolder;


/*
 * The imports below are only for the WebSphere specific example overloaded
 * permit() methods.  You'll need to place sas.jar and wssec.jar in the classpath
 * to use the imports and example code.
 */
import com.ibm.websphere.security.WSSecurityException;
import com.ibm.websphere.security.auth.CredentialDestroyedException;
import com.ibm.websphere.security.auth.WSSubject;
import com.ibm.websphere.security.cred.WSCredential;
import javax.security.auth.Subject;
import javax.security.auth.login.CredentialExpiredException;

/**
 * @author Robert Patt-Corner, Keys Botzum
 * The WASACL extends ACL with WebSphere-specific techniques for dealing with
 * implicit and explicit subjects in the permit() method
 */
public class WASACL extends ACL {

	public WASACL(ACLHolder securedResource) {
		super(securedResource);
	}

	/**
	 * @param PermissionSet reqdPerms
	 * This is an example implementation for WebSpere V6.1, illustrating how to use
	 * the implicit WebSphere Subject to perform authorization.  Must be run in a
	 * WAS container.
	 * 
	 * Introduces a dependency on WAS 6.1's wssec.jar and sas.jar
	 */
	public void permit(PermissionSet reqdPerms) throws PermissionViolationException {
		try {
			// Get the subject in a WebSphere specific manner, then delegate
			Subject subject = WSSubject.getCallerSubject();
			permit(reqdPerms, subject);
		} catch (WSSecurityException e) {
			throw new PermissionViolationException("Unable to retrieve 
				the WebSphere caller subject", e);
		}
	}

	/**
	 * @param PermissionSet reqdPerms
	 * @param Subject explicit subject
	 * This is an example implementation for WebSphere 6.1 ilustrating how to 
	 * approach using an explicit subject to perform authorization.  
	 */
	public void permit(PermissionSet reqdPerms, Subject subject) throws 
PermissionViolationException {
		subject.getPrincipals();
		
		String username=null;
		List<String> groups=null;
		
		// Get the public credentials (should be only one) from the subject of 
		// interest 
		Set credSet = subject.getPublicCredentials(WSCredential.class);
		   if (credSet.size() != 1) {
		    throw new PermissionViolationException("Found "
		      + credSet.size() + "WSCredentals, which is not legal");
		   }

		   Iterator iter = credSet.iterator();
		   WSCredential creds = (WSCredential) iter.next();
		   
		   // Get the unique security name corresponding to the Principal
			try {
				username=creds.getUniqueSecurityName();
				groups = creds.getGroupIds();

				/* At this point you'll want to insert application-
				 * specific code to transform both the username and then 
				 * each of the groups identifiers to the format your 
				 * application uses for group names in the 
				 * grants structure
				 */ 
	        
				groups.add(username); 
				// add the Principal name to the groups
			} catch (CredentialExpiredException e) {
				throw new PermissionViolationException
				("Found expired WS credentials in ACL", e);
			} catch (CredentialDestroyedException e) {
				throw new PermissionViolationException
			   ("Found destroyed WS credentials in ACL", e);
			}
        permit (reqdPerms, groups);
	}

}





回页首


保留感兴趣的类

  • PermissionSetImpl(清单 9)是 PermissionSet 接口的实现,支持从 AllPermissions enum 构造一组二进制权限。主要感兴趣的内容是 EnumSet 的不常见的构造模式:使用 EnumSet.noneOf() 类方法;这是用于获取包含特定 enum 的空 EnumSet 实例的 Java 5 技术。



    清单 9. PermissionSet 实现
                            
    public class PermissionSetImpl implements PermissionSet{
    
    	private EnumSet<AllPermissions> permissions = 
    		EnumSet.noneOf(AllPermissions.class);
    
    	public void addPermission(AllPermissions permission) {
    		permissions.add(permission);
    	}
    
    	public void addPermissions(EnumSet<AllPermissions> permissions){
    		for (AllPermissions permission: permissions){
    			addPermission(permission);
    		}
    	}
    	
    	public boolean hasPermission(AllPermissions permission) {
    		return permissions.contains(permission);
    	}
    	
    	public PermissionSetImpl() {
    		
    	}
    	
    	public PermissionSetImpl(AllPermissions seed) {
    		this.addPermission(seed);
    	}
    
    	// Exposes the central containsAll operation
    	public boolean hasPermissions(PermissionSet ps) {
    		return permissions.containsAll(((PermissionSetImpl)ps).permissions);
    	}
    	
    	public EnumSet<AllPermissions> getPermissions() {
    		return permissions;
    	}
    	
    	
    	public String toString() {
    		return permissions.toString();
    	}
    	
    }

  • MockPrincipal(清单 10)演示了实现 java.security.Principal 接口的简单技术,使用一个方法将组与主体相关联。正如前面所讨论的,它是主题和主体的实现相关构造的替代物,出于我们的测试目的,通过注册表为其提供支持。



    清单 10. MockPrincipal 测试类
                            
    public class MockPrincipal implements Principal {
    	private String name;
    	private Set<String> groups;
    	public final static String PRINCIPAL_PREFIX="PRINCIPAL/";
    	public MockPrincipal(String name){
    		this.name=name;
    		this.groups=new HashSet<String>();
    		this.groups.add(PRINCIPAL_PREFIX+name);
    	}
    
    	public String getName() {
    		return name;
    	}
    	
    	public void addGroup(String groupName){
    		this.groups.add(groupName);
    	}
    	
    	public Set<String> getGroups(){
    		return this.groups;
    	}
    
    }

  • AuthorizationManager 接口和实现(如第 1 部分所述)是您的 ACL permit() 方法的简单包装,专门用于两个目的:

    • authorize() 方法设计用于保护服务层中的方法免受未经授权的访问。
    • canAuthorize() 方法设计用于方便 GUI 授权显示,以便通过图形界面显示或管理 EAz 授权。canAuthorize 和相关的显示方法将作为本系列下一篇文章的重点。

    清单 11 显示了从服务层保护的角度来看,AuthorizationManagerImpl 中令人感兴趣的关键方法。AuthorizationManagerImpl 使用 ACLManager 服务来检索 testACL,而不是直接从 aclDAO 获取 ACL。为 ACL 检索添加服务层促使 EAz 实现 ACLManager,以便能够为经常使用的 ACL 使用缓存。最后,清单 12 显示了 ACLManager 的部分实现,表明可能用到缓存的情况。



    清单 11. AuthorizationManagerImpl 中的服务层保护
                            
    public void authorize(PermissionSet ps, List<String> nameList,
    		ACLHolder aclHolder) throws PermissionViolationException {
    	// Main implementation
    	ACL testACL = aclManager.getACL(aclHolder);
    	try {
    	testACL.permit(ps, nameList);
    	} catch (PermissionViolationException pve){
    		logger.log(Level.WARNING, "Permission request failed on 
    			nameList "+nameList,pve);
    		throw pve;
    	}
    }



    清单 12. ACLManager 的部分实现
                            
    public class ACLManagerImpl implements ACLManager {
    	private ACLDao aclDao;
    	
    	public ACL getACL(ACLHolder aclHolder) {
    		ACL theACL = retrieveFromCache(aclHolder);
    		if (theACL==null){
    			theACL=retrieveFromDatabase(aclHolder);
    			addToCache(theACL);
    		}
    		return theACL;
    	}
    
    	private ACL retrieveFromDatabase(ACLHolder aclHolder) {
    		ACLDao aclDao = new ACLDao();
    		ACL theACL = aclDao.retrieveACL(aclHolder);
    		return theACL;
    	}
    
    	private void addToCache(ACL acl) {
    		// TODO cache-dependent ADD method
    		
    	}
    
    	private ACL retrieveFromCache(ACLHolder aclHolder) {
    		// TODO cache-dependent RETRIEVE method
    		return null;
    	}
    }





回页首


结束语

分享这篇文章……

digg 提交到 Digg
del.icio.us 发布到 del.icio.us
Slashdot Slashdot 一下!

您现在已经演练了 EAz 应用于虚拟校园模型的测试用法和实现,了解并讨论了使用和测试 EAz 的两种方法,以及主要 EAz 类的实现。下面介绍和演示在给定的 Java EE 容器(如注册表和主题实现)中,您需要添加 Java EE 实现相关代码以便正常工作的位置:

  • 使用 org.eaz.acl.Permissions 配置应用程序的权限和操作/命令。
  • 实现 EAz 数据访问对象以保持和检索权限所涉及的技术。
  • 核心 ACL 类在基于与组和主体相关联的 PermissionSets 的许可操作中的运行。
  • 涉及的实用工具类和服务层包装。

在作为本系列结束的第 3 部分,您将探索如何将应用程序操作、组和主体,以及它们与 EAz 权限的关系在图形界面中可视化。这会向用户和管理员显示应用程序的 Eaz 权限授予情况,并允许您构建管理控制台接口。





回页首


本系列的其他文章






回页首


下载

描述名字大小下载方法
Code sampleEAuthZ_1.zip18 KBHTTP
关于下载方法的信息


参考资料



作者简介

Robert Patt-Corner 是 ProQual-IT 的首席技术官,主要研究面向服务的体系结构。之前,他是 Noblis(以前称为Mitretek Healthcare)的首席技术官,领导团队开发了基于 Web 的自动化卫生保键警报服务。Robert 获得的认证包括 WebSphere 管理、Rational 应用程序开发和 Lotus 管理和开发。您可以通过 rpc@dorsetwest.comrpatt-corner@proqual.net 与 Robert 联系,并且可以访问他的网站,网址是:www.dorsetwest.comwww.proqual.net


Keys Botzum IBM Software Services for WebSphere 的一名高级技术人员。Botzum 在大型分布式系统设计方面有着十多年经验,并且专攻安全性问题。他使用过各种分布式技术,包括 Sun RPC、DCE、CORBA、AFS 和 DFS。最近,他着重研究 J2EE 及其相关技术。他拥有斯坦福大学计算机硕士学位和卡内基梅隆大学应用数学/计算机科学学士学位。

Botzum 发表过许多 WebSphere 和 WebSphere 安全性方面的论文。Keys Botzum 的其他文章和演示文稿可以在 http://www.keysbotzum.comIBM developerWorks WebSphere 上找到。他还著有 IBM WebSphere:Deployment and Advanced Configuration 一书(与 Bill Hines 合作完成)。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

建议?







回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款