级别: 初级 王超, 软件工程师, IBM 史进军, 软件工程师, IBM
2009 年 10 月 22 日 本文简单介绍了 FileNet P8 Content Engine 中的安全管理机制和相关的 API。首先介绍了 FileNet Content Engine 安全机制,主要包括认证和授权。然后介绍了安全相关的 Java API,并且举例说明如何使用 Java API 来进行相关的安全设置。通过本文,读者可以建立起简单的 FileNet P8 中的安全管理概念,可以尝试使用相关 API 来定性内容查询和存储的安全。
本文分为两大部分,第一部分阐述了 FileNet Content Engine 的安全机制;第二部分举例说明了如何使用安全相关的 Java API 来设置安全,从而保证存储内容的安全。
FileNet Content Engine 安全机制简介
FileNet Content Engine 是 FileNet P8 体系中的核心模块之一,主要负责内容存储和内容管理。Content Engine 在一个面向对象的容器中提供了一系列服务来支持企业内容管理和客户自定义对象。Content Engine 创建了这些数字对象之间的关系,然后管理他们各自的组建和生命周期。Content Engine 可以在不同的分布式环境中管理商业对象的访问权限,并且可以维持这些对象的行为,特性,属性的相关信息。
由于不同的用户可以对不同的对象具有不同的权限,FileNet Content Engine 使用的一系列安全模型来保证存储内容的安全,主要分为以下部分:
-
FileNet Content Engine Authenticatio(认证):包括 JAAS 和 WS-Security 两种方式。
-
FileNet Content Engine Authorization(授权): FileNet Content Engine Domain Security.
主要包括 FileNet Content Engine 中 User 和 Group, ACE(Access Control Entries), ACL(Access Control List), 安全评估顺序,Security Policies,Markings 和 Marking Sets。
下面分别介绍这几个方面:
FileNet Content Engine Authentication
认证是基于用户的凭证来验证用户身份的行为。认证要做的事是回答:用户是谁?当前用户是否真的是他所代表的角色?而授权是在认证过后继续进行的动作,授权要做的事是判断用户是否被授权访问某个资源或者进行某个操作。
在 FileNet P8 的认证处理过程中,有两种标准处于核心地位,他们分别是 Java Authentication and Authorization Service (JAAS) standard 和 Web Services Security standard 。
JAAS 概要
JAAS 提供了一种基于策略的可靠和安全框架;使用了这种可插拔的框架,应用程序,例如 Content Engine,可以和底层的认证技术保持独立。
客户端程序在调用 Content Engine Java API 之前需要获得 JAAS Subject;通过与 Content Engine Java API 的交互来调用 Content Engine EJB。客户端的 JAAS Subject 通过每个 EJB 调用,透明地传递到 J2EE 应用程序服务器端,服务器端验证 JAAS subject,然后批准调用者的身份,之后调用者才能在 Content Engine EJB 中执行代码。在 Content Engine 认证用户时,JAAS 会和 Directory service providers 交互,检验用户是否合法。
WS-Security 概要
Web Service 的核心是 XML,通过 Web Service,不同类型的系统可以进行通信。FileNet 在 Content Engine 服务中提供了 Web Service 接口,从而支持了各种类型的连接。
Web Service 的核心标准之一是 WS-Security 标准。WS-Security web service 定义了三种主要的安全机制:安全令牌传播,消息集成和消息保密。WS-Security 提供了配置文件,这些配置定义了不同类型的安全凭证如何被格式化和插入到 web service 的消息中。
Content Engine 的认证架构
Content Engine 是以 J2EE 应用程序部署的,可以发布在一个或者多个 J2EE 应用程序服务器实例中。这种应用程序的核心组件是:
-
Content Engine Web Service listener:这个 listener 是以基于 servlet 的应用程序包存在于应用服务器的 web 容器中;来自 web service 的请求在被 WS-Security headers 处理后会传递到 Content Engine EJB 这一层,如下图所示:
-
Content Engine EJBs:这些 J2EE 会话存在于应用程序服务器端的 EJB 层,并且实现了想 CE web service 同样的接口,他们通过 Enterprise Java Bean 接口对外部暴露。所有使用 EJB 方式连接的客户端都需要 JAAS 认证。
图 1. FileNet P8 API 层次架构图
FileNet Content Engine Domain Security
FileNet P8 domain 代表了逻辑上的一组物理资源(如 object store 数据库,full text index areas, file storage areas, 和 content cache areas)。Content Engine 服务器提供了对这些资源的访问。
只有明确被授权的了的用户、组,机器帐户(或者服务等),应用程序,进程,脚本等才能访问 FileNet P8 domain 的资源(如 object, document, workflows,等)。
在 Domain Security Model 中,重要的有以下几个部分:
User 和 Group
在 FileNet P8 domain 中,访问的用户可以属于不同的组,这些分租通常是在 LDAP 服务器上完成的。区分用户和组的目的是为了更方便的进行安全控制,例如同一个组的用户可以有共同的权限。
在用户获得了认证,但是被允许在 objectstore 中进行操作之前,为这个用户将产生一个安全令牌作为一种内部护照。像一般的护照一样,在需要访问某个对象时将出示这个护照,然后这个对象根据护照来决定该用户是否授权访问。安全令牌中包含了用户的 security identifier(SID)和用户所属于的所有组的 (SID), 这些 SID 都是基于用户和组的在数据库中的认证信息。
ACL(Access Control List) 和 ACE (Access Control Entries)
在 FileNet P8 中,所有的 Object 分为两种:Securable 和 Non securable objects。大多数 Content Engine 对象类是独立的可安全的,意味着它们通过自己的 ACL 来保证安全。一些非独立的对象是可安全的,意味着它们的安全取决于它们相关的对象的安全。
在 FileNet P8 中,Securable 对象的安全是通过 ACL 和 ACE 来控制的。一个对象的访问权限决定了哪些用户可以查看对象,和进行什么操作。例如,要查看一个 folder 里面的内容,用户需要至少对 folder 的 View properties permission。要给 folder 添加 document,那么这个用户要具有添加的权限。
每个 FileNet P8 ACE 是“允许”或者“拒绝”的一个权限集合。例如,用户 A 对某个 document 具有读权限,而用户 B 则对这个 document 具有删除的权限。
每个 ACE 有个目录,这些目录包含了对某个特定对象的所有权限,每个权限都设置为允许或者拒绝,若不设置,则默认所有的权限都被拒绝。
每一个 Securable object 都有一个 ACL 来决定那个用户被授予相关属性内容的允许权限或者拒绝权限。每一个授权的访问权限集合以 ACE 的方式存储。简单地说,就是每个 ACL 是许多 ACE 的集合。如下图所示:上面是 ACL, 其中每个条目是一个 ACE, 而下面是每个 ACE 包含的具体权限设置。
图 2. Object 中的 ACE 和 ACE 示例
每个 ACE 除了包含访问权限是允许还是拒绝,也包含了每个权限的源,权限的源对于权限的后续继承成为有直接的影响,甚至决定了它是否可以被其他对象继承。
有三种 ACE Source:
-
Default: 对象的权限由它的 class 的 Default Instance Security ACL 来决定,就像父类的权限决定了子类的权限。
-
Direct: 对象的权限是直接添加上去的。
-
Template: 对象的权限由 security policy 来决定。
ACE security levels 是一个 Security Level 是预定义好的权限的集合。例如,document 的 Full Control level 包含了对这个对象所有可能存在的权限。
Order of Evaluation( 权限评估顺序 )
当某个对象上有多个 ACE 时,不同的 ACE 可能对同一个用户有不同权限定义,这时,如果权限定义发生冲突,应该以下列顺序为准:
Direct/Default Deny> Direct/Default Allow> Template Deny
> Template Allow> Inherited Deny> Inherited Allow
|
例如,当一个对象的 ACL 中,对于某个用户,同时存在 Default Deny 和 Direct Allow 的 ACE 时,由于前者的优先级高,所以以前者为主。
Security Policies
安全策略可以视为一些安全模板 (security templates) 的集合 , 每个安全策略包含了一组预定义的权限,或者 ACE,这些权限或者 ACE 可以应用到 document,custom object,或者 folder 等。
Security policies 可以使系统管理员对大量的 document 进行访问控制,从而避免直接编辑每个 document 的 ACL。
Security policies 与 versioning states 联合起来使系统管理员可以通过设置系统,来实现当 versioning states 改变时,document 的 ACL 自动改变。例如,管理员可以通过设置,当文档 release 时,自动赋予 document 的一些权限。
在实际应用中,可以给要产生实例的 class 添加相关的 Security policies,从而使改 class 产生的对象都具有 Security policies 定义的一些权限设置。
Markings 和 Marking Set
Markings 允许基于特定的属性值来控制对 object 的访问权限。当一个 marking 应用到一个对象时,产生的访问权限是由该对象原来的访问权限和 marking 的 Constraint Mask 设置的值来联合决定的。联合的结果是产生了有效的 security mask。
通常,Marking 的工作方式如下:
-
创建一个 marking set,包含一些可能的值,这些值被称为 markings;
-
每个 marking 值包含了一系列访问权限,这些权限定义了谁可以把这些特殊的值赋予一个对象的权限,谁可以修改和删除这些值,还有一旦被赋值,谁将拥有对这个 object 的访问权限;
-
Marking set 被赋予一个 class 的某个 property definition,这个 class 的 property 的值或者其实例必须是 marking set 定义的 markings 之一;
-
这些值只能被关联的 marking 授权了的用户赋值,并且一旦 marking 设置之后,对 object 的访问将被严格的限制。
Markings 没有代替 object 上传统的访问权限,但不是平等的决定访问权限。在一个既有 marking 又有传统的 ACL 访问权限设置的 object 上,权限的处理流程如下:
-
某个用户或者进程试图访问某个 object;
-
首先,Content Engine 查看 object 的 ACL,决定那个用户可以访问和进 行操作;
-
然后,Content Engine 计算应用到 object 上的 markings,决定应该禁止那些用户和行为。
实例:通过 JAVA Security API 来定性内容查询和存储的安全
这一部分使用一个例子说明如何使用 Content Engine 中安全相关的 Java API 来进行安全设置从而保证存储内容的安全。
分为以下几个步骤:
-
获得 LoginContext;
-
使用 Security Policies;
-
设置对象的权限;
-
使用 Marking 和 Marking Set。
获得 LoginContext
为了保证用户身份认证的安全性,我们使用 JAAS 进行登陆,由三个步骤组成:首先获得一个 LoginContext 对象;然后调用 LoginContext.login() 方法。
获得 LoginContext 对象需要在 JAAS login configuration file 中定义的一个标签来指示特殊的配置信息。
下面这段代码来进行登陆操作:
清单 1. 使用 API 来登录
LoginContext lc = newLoginContext("mysystem",
newUserPasswordHandler("username@testdom.local", "password"));
lc.login();
// 将 JAAS Subject 与 UserContext
UserContext uc = UserContext.get();
uc.pushSubject(lc.getSubject());
|
Login() 方法如果没有抛出异常,就表示该用户登陆成功了,即该用户通过了认证。
每个 UserContext 对象是与每个访问Content Engine Java API 的线程联系在一起的。一个 JAAS Subject 应该与这个线程是关联的,这是通过调用 UserContext.pushSubject() 方法来实现的。
注意:UserContext 类提供了一辅助方法来进行使用普通的用户名和密码的 JAAS 登陆,例如:
清单 2. 使用 JAAS 登录
// Obtain a JAAS Subject and associate it with the UserContext
UserContext uc = UserContext.get();
uc.pushSubject(
UserContext.createSubject("connection", "username", "password", "FileNetP8"));
|
在方法 createSubject()中,可以定义用户名和密码,以及选择连接方式,其中 FileNetP8 指定使用 EJB 方式连接,FileNetP8WSI 指定使用 WSI 方式来连接。
使用 Security Policy
下面我们来创建和赋值一个 security policy:
首先,创建三个 SecurityTemplate 对象,并把它们加到一个 SecurityTemplateList 对象中。其中,两个 template 的类型是 VersioningSecurityTemplate, 另一个是 ApplicationSecurityTemplate 。 VersioningSecurityTemplate 在 object 发行和取代版本时自动地应用。而 ApplicationSecurityTemplate 则需要手动地应用到一个 object 上。对于每个 template ,属性 TemplatePermissions 被设置到一个 AccessPermissionList 对象中,这是由 setPermissions() 方法来返回的。
清单 3. 使用 Security Policy 登录
// 创建 security templates
VersioningSecurityTemplate vst1 =
Factory.VersioningSecurityTemplate.createInstance(os);
VersioningSecurityTemplate vst2 =
Factory.VersioningSecurityTemplate.createInstance(os);
ApplicationSecurityTemplate vst3 =
Factory.ApplicationSecurityTemplate.createInstance(os);
SecurityTemplateList stl = Factory.SecurityTemplate.createList();
vst1.set_ApplyStateID(VersionStatusId.RELEASED);
vst1.set_TemplatePermissions(
setPermissions("#AUTHENTICATED-USERS", AccessLevel.MAJOR_VERSION_DOCUMENT.getValue()) );
vst1.set_DisplayName("Version Template for Released Object");
vst1.set_IsEnabled(Boolean.TRUE);
stl.add(vst1);
vst2.set_ApplyStateID(VersionStatusId.SUPERSEDED);
vst2.set_TemplatePermissions(
setPermissions("#AUTHENTICATED-USERS", AccessLevel.VIEW.getValue()) );
vst2.set_DisplayName("Version Template for Superseded Object");
vst2.set_IsEnabled(Boolean.TRUE);
stl.add(vst2);
vst3.set_ApplyStateID(newId("{21a47705-d20a-4b65-938e-2ddcefa45927}") );
vst3.set_TemplatePermissions( setPermissions("#AUTHENTICATED-USERS",
AccessLevel.READ.getValue()+ AccessRight.DELETE_AS_INT ) );
vst3.set_DisplayName("Application Template for Obsolete Objects");
vst3.set_IsEnabled(Boolean.TRUE);
stl.add(vst3);
// 创建 security policy
SecurityPolicy sp =
Factory.SecurityPolicy.createInstance(os, ClassNames.SECURITY_POLICY);
sp.set_SecurityTemplates(stl);
sp.set_DisplayName("Security Policy with Version and Application Templates");
sp.set_PreserveDirectPermissions(Boolean.FALSE);
sp.save(RefreshMode.REFRESH);
|
然后把一个创建好的 security policy 赋值给 class:
清单 4. 将 Security Policy 赋给 class 登录
ClassDefinition cd =
Factory.ClassDefinition.fetchInstance(os, classId, null);
SecurityPolicy sp =
Factory.SecurityPolicy.getInstance(os, ClassNames.SECURITY_POLICY, securityPolicyId);
PropertyDefinition pd =
getPropertyDefinition(cd.get_PropertyDefinitions(), ClassNames.SECURITY_POLICY);
pd.getProperties().get(PropertyNames.PROPERTY_DEFAULT_OBJECT)).setObjectValue(sp);
cd.save(RefreshMode.REFRESH);
|
下面我们遍历了一个文件夹,过滤出一年时间内从未修改过的 document。然后给该 document 应用了有删除权限的 application security template。
清单 5. 遍历文件夹中的 document
// 获得一个 folder
Folder folder = Factory.Folder.fetchInstance(os, folderId, null);
DocumentSet ds = folder.get_ContainedDocuments();
Calendar cal = newGregorianCalendar();
intcurrYear = cal.get(Calendar.YEAR);
intcurrMonth = cal.get(Calendar.MONTH);
// 遍历文档
Iterator iter = ds.iterator();
while(iter.hasNext())
{
Document doc = (Document) iter.next();
Date docDate = doc.get_DateLastModified();
cal.setTime(docDate);
if(cal.get(Calendar.YEAR) < currYear && cal.get(Calendar.MONTH) < currMonth )
{
doc.applySecurityTemplate(newId("{21a47705-d20a-4b65-938e-2ddcefa45927}") );
doc.save(RefreshMode.REFRESH);
}
}
|
然后我们获得 Security Template 的相关信息。
我们可以使用如下的方法来获得一个 security template 中的权限的描述信息。在一个 security template 对象中包含了 TemplatePermissionDescriptions 属性,在这个属性中包含了一系列 AccessPermissionDescription 对象,从这些对象中,就可以获得访问权限相关的信息。
清单 6. 获得 Security Template 的相关信息
securityPolicy sp = Factory.SecurityPolicy.fetchInstance(os, secPolicyId, null);
SecurityTemplateList stl = sp.get_SecurityTemplates();
Iterator outerIter = stl.iterator();
while(outerIter.hasNext())
{
SecurityTemplate st = (SecurityTemplate) outerIter.next();
AccessPermissionDescriptionList apdl = st.get_TemplatePermissionDescriptions();
Iterator innerIter = apdl.iterator();
System.out.println("Security template is " + st.get_DisplayName());
while(innerIter.hasNext())
{
AccessPermissionDescription apd = (AccessPermissionDescription) innerIter.next();
System.out.println("Permission is " + apd.get_DescriptiveText() + "\n" +
"Permission type is " + apd.get_PermissionType().toString() + "\n" +
"Access mask is " + apd.get_AccessMask()
);
}
}
|
当我们不再需要使用某个 Security Policy 时候,可以将它删除:
我们把一个对象的 SecurityPolicy 属性设置为 NULL 来删除这个 SecurityPolicy。也可以从一个 object 中删除一个 SecurityPolicy 对象,前提是没有其他对象引用它。
清单 7. 删除 Security Policy
// 获得要删除的对象的 class
ClassDefinition cd = Factory.ClassDefinition.fetchInstance(os, classId, null);
PropertyDefinition pd =
getPropertyDefinition(cd.get_PropertyDefinitions(), ClassNames.SECURITY_POLICY);
SecurityPolicy spTarget = (SecurityPolicy)
(pd.getProperties().get(PropertyNames.PROPERTY_DEFAULT_OBJECT)).getObjectValue();
Id spTargetId = spTarget.get_Id();
pd.getProperties().get(PropertyNames.PROPERTY_DEFAULT_OBJECT)).setObjectValue(null);
cd.save(RefreshMode.REFRESH);
SecurityPolicySet sps = os.get_SecurityPolicies();
Iterator outerIter = sps.iterator();
while(outerIter.hasNext())
{
SecurityPolicy sp = (SecurityPolicy) outerIter.next();
if(sp.get_Id().equals(spTargetId) )
{
sp.delete();
sp.save(RefreshMode.REFRESH);
}
}
|
设置对象的权限
在通过上面的设置之后,我们可以给一个 object 设置访问权限来保证访问的安全性。
可以使用 Access mask 定义了授予用户或者组的操作权限,mask 中的每个 bit 位都代表了一种操作。如果某个 bit 被设置,则这种对应的操作被允许,相反,bit 没有被设置,则该操作不允许。
在本例中,用户“test1”被赋予了对 document 的 full control 权限。
清单 8. 设置对象的访问权限
// 创建一个新的 permissions list
AccessPermissionList apl = Factory.AccessPermission.createList();
// 创建一个新的 access permission 对象
AccessPermission ap = Factory.AccessPermission.createInstance();
// 设置 access permissions
ap.set_GranteeName("test1");
ap.set_AccessType(AccessType.ALLOW);
ap.set_AccessMask(newInteger(AccessLevel.FULL_CONTROL_DOCUMENT_AS_INT));
// 添加 permissions 到 list
apl.add(ap);
// 新建一个 document 然后添加 permissions
Document doc = Factory.Document.createInstance(os, ClassNames.DOCUMENT);
doc.set_Permissions(apl);
// 保存 document
doc.getProperties().putValue("DocumentTitle", "Example: Permissions Setting");
doc.save(RefreshMode.REFRESH);
注意:其他的 Securable 对象都可以调用 set_Permissions() 方法来设置其权限。
|
使用 Markings 和 Marking Set
还可以使用 marking 和 marking set 来设置权限,比如:
下面的例子了如何得到 domain 中的所有 marking set:
清单 9. 获得 markings 和 marking set
Domain domain = Factory.Domain.fetchInstance(conn, "Domain", null);
domain.get_MarkingSets();
|
使用 Factory.Marking.createInstance() 方法和 Factory.MarkingSet.createInstance() 方法可以创建新的 marking 和 markingset。
总结
FileNetP8 的安全管理是一个十分复杂的体系,它综合了多种安全管理机制和技术,对不同的对象采用的多重的安全管理技术。本文从大体上简单介绍了 FileNetP8 Content Engine 中的安全管理机制和相关的 API。首先介绍了 FileNet Content Engine 安全机制 , 主要包括认证和授权。然后介绍了安全相关的 Java API,举例说明如何使用 Java API 来进行相关的安全设置。
参考资料 学习
获得产品和技术
- 使用可直接从 developerWorks 下载的 IBM 试用软件 构建您的您的应用开发项目。
- developrWorks 技术活动和网络广播:通过这些活动了解技术的最新发展。
讨论
作者简介  | 
|  | 王超:就职于 IBM 中国开发中心 ECM FVT 部门,对 FileNet Content Engine Java API 有深刻理解。
|
 | 
|  | 史进军:就职于 IBM 中国开发中心 ECM FVT 部门,对 FileNet Content Engine 的框架有深刻理解,有丰富的客户支持和项目经验。 |
对本文的评价
|