跳到导航
BEA Dev2Dev Oracle and BEA
首页 资源中心 dev2dev学堂 在线技术论坛 User Group CodeShare
dev2dev 首页 > 资源中心 > 技术文章
Java与安全性,第2部分

时间:2005-10-11
作者:Jon Mountjoy
浏览次数:
本文关键字:身份认证授权java 安全JAAS LDAP 身份验证
文章工具
推荐给朋友 推荐给朋友
打印文章 打印文章

  编辑注:上周我们节选了《WebLogic: The Definitive Guide》一书第17章的一部分,分析了WebLogic的安全机制,其中包括Java Security Manager。本周是节选的第二部分,也是最后一个部分。在这部分内容中,作者Avinash Chugh 和Jon Mountjoy介绍了WebLogic的各种提供程序及其默认实现,并考察了如何使用JAAS进行身份验证,最后给出一些Authentication 和Identity Assertion Providers的例子。

提供程序(Provider)

  现在,我们将更详细地了解构成安全域的不同SSPI。我们将了解WebLogic对这些安全提供程序的默认实现,以及配置它们的方法。默认实现提供了身份验证架构(以及更多),这我们已经知道了。如果想修改提供程序的行为,可以使用自己的代码替换一个或多个提供程序。再次说明,可以通过管理控制台查看和修改这些安全提供程序的配置。在管理控制台左侧窗格中的Security/Realms/myrealm/Providers节点下,可以找到安全域可用的所有安全提供程序,这里的“myrealm”是安全域的名称。最后,我们将了解代表默认安全提供程序保存域的所有安全数据的嵌入式LDAP服务器。

身份验证提供程序(Authentication Provider)

  身份验证是指服务器可靠地验证用户或系统身份的能力。我们通常把要进行身份验证的用户或系统统称为用户。在用户能够建立与服务器之间的信任之前,它需要一些身份检验。WebLogic支持Authentication Provider,它可以基于用户名-密码组合或数字认证来验证用户证书。安全提供程序库(security provider repository)用于保存用户和组信息,可以通过下列方式实现:

  • 作为嵌入式LDAP服务器,这是WebLogic安全提供程序的默认做法。
  • 作为外部LDAP存储器,比如Open LDAP、Active Directory、Novell或NDS。
  • 作为DBMS,您可能已经在采用这种做法来为企业应用程序提供数据。
  • 作为文本文件,这是WebLogic的示例安全提供程序使用的做法。

  WebLogic的身份验证提供程序架构严格遵照标准JAAS的身份验证部分。按照JAAS术语,subject(主题)代表安全请求的源——它代表尝试通过身份验证的用户或系统。身份验证的意义在于为主题指派principal(主体)。主体是代表成功授权的结果的身份。正如我们所看到的,当system用户成功通过身份验证时,代表该用户的主题就被指派一个主体,记录下它属于Administrators组,而另一个主体则记录下它是一个名为system的WebLogic用户。因此,主题就是身份验证信息(包括主体)的标准容器。客户端可以使用主体查询其身份和其他属性。图17-2说明了这个客户端身份验证过程。

图17-2. 客户端身份验证过程

  通过在许多可配置的JAAS LoginModule的基础上构造身份验证顺序,Authentication Provider遵从了标准的JAAS框架。LoginModule是Authentication Provider的关键组件,因为它们负责对安全域中的用户进行身份验证,以及使用所需的主体(现有的用户和组)填充主题。

  WebLogic要求至少在安全域中配置一个身份验证提供程序。

主体验证提供程序(Principal Validation Provider)

  客户端(可能是远程的)建立起与WebLogic的信任之后,在两次服务器调用之间,通过身份验证的主题是保存在客户端上的。Principal Validation Provider确保在两次调用之间的任何时间里,对于主题的主体不会出现什么有害行为。它是通过辨别和验证主题保存的主体的可靠性来做到这一点的。主体通过验证之后,Authorization Provider可以使用它们进行访问控制检查,或者Role Mapping Provider可以使用它们进行角色映射决策。安全域必须为每个Authentication Provider都定义一个Principal Validation Provider。

身份断言提供程序(Identity Assertion Provider)

  Identity Assertion Provider有助于保护对WebLogic部署的入口点的访问。外部客户端可以使用令牌来建立与WebLogic Server之间的信任,而不是使用用户名和密码。Identity Assertion Provider对令牌进行检查,如果成功就把它映射为合法的WebLogic用户。令牌被映射为合法用户之后,Authentication Provider就可以为该用户生成主体。这种机制称为周边身份验证(perimeter authentication),所以可以认为Identity Assertion Provider 是一类特殊的Authentication Provider。这里的关键在于外部代理负责对用户进行身份验证,然后负责把用户数据传送给WebLogic。

  周边身份验证的副作用是支持单点登录。例如,Identity Assertion Provider能够提供X.509数字证书作为身份令牌,而这些证书则可以跨多个系统使用。WebLogic支持的Identity Assertion Provider可以处理多种令牌类型(X.509,IIOP-CSIv2)。另外,还可以创建支持定制令牌类型的Identity Assertion Provider(比如Kerberos tickets)。Identity Assertion Provider可以有多种活动的令牌类型。然而,在一个提供程序中只能有一个令牌类型是活动的。当然,一个安全域可以支持多个Identity Assertion Provider,尽管一个都不需要。图17-3说明了周边身份验证。

图 17-3.周边身份验证过程中的身份确认

默认的身份验证提供程序

  WebLogic支持基于用户名-密码组合形式的身份验证,使用直接活通过HTTP服务器传送到WebLogic的数字证书的身份验证,以及基于安全令牌的周边身份验证。WebLogic允许使用接下来描述的Authentication Provider。

Default Authenticator

 这是WebLogic的默认Authentication Provider,依赖于嵌入式LDAP服务器来保存所有安全信息。

Realm Adapter Authentication Provider

  这个提供程序提供与老式WebLogic 6.x领域中保存的用户和组信息的向后兼容性。

Default Identity Asserter

  这是 WebLogic的默认Identity Assertion Provider,用于验证 X.509和IIOP-CSIv2 令牌的可靠性,并把它们映射为合法的WebLogic用户。

LDAP Authentication Provider

  这些提供程序使用外部LDAP存储器来保存所有有关用户和组的安全信息。WebLogic允许配置可以访问各种LDAP存储器的Authenticator,包括Open LDAP、Sun的iPlanet、微软的Active Directory和Novell NDS。

  另外,还可以指派定制构建的Authenticators和Identity Asserter给安全域。

  为了访问指派给一个领域的Authentication Provider,需要从管理控制台的左侧窗格中选择Authentication节点。在右侧的窗格中,可以选择已配置的Authenticatior其中之一,或者配置一个新的Authenticatior。

  安全域可以有一系列一致工作的Authentication Provider(和JAAS LoginModule)。每个Authentication Provider都有一项JAAS控制标志设置,用于确定对于特定的Authentication Provider来说,总的登录顺序的行为如何。这与在一个J2SE应用程序中配置多个LoginModule的做法没有差别。为了给一个安全域中的Authentication Provider排序,需要选择左侧窗格中的Authentication节点,然后选择Re-order the Configured Authentication Providers选项。每个身份验证提供程序的控制标志可能具有以下值:

REQUIRED

  这个选项是任意Authentication Provider的默认设置。一个必需的Authentication Provider始终会被调用,而不管其他提供程序上的控制标志设置如何。如果任何REQUIRED提供程序失败,总的身份验证就不会成功。因此,REQUIRED提供程序始终会被调用,如果其中任何一个失败,总的身份验证就会失败。

REQUISITE

  这个选项还要求Authentication Provider在登录顺序期间成功。然而,所有REQUISITE提供程序不需要为了总的身份验证成功而被调用。如果一个REQUISITE提供程序成功,身份验证就会按照常规依序进行到其他提供程序。然而,如果它失败了,总的身份验证就不会成功,而且一旦登录顺序中的REQUIRED提供程序被调用,控制权就会马上传回给应用程序。

SUFFICIENT

  这个选项不要求Authentication Provider在登录顺序期间成功。如果SUFFICIENT提供程序成功,总的身份验证会继续进行,确保在登录顺序中只有余下的SUFFICIENT提供程序被执行。然而,如果它失败了,总的身份验证就会按照常规依序进行到其他提供程序。

OPTIONAL

  这个选项不要求Authentication Provider在登录顺序期间成功。不论OPTIONAL提供程序是否成功,身份验证都会继续进行到已被配置为登录顺序一部分的其他提供程序。

  当有多个LoginModules用于对一个用户进行身份验证时,身份验证过程就可以分为两个阶段。在第一个阶段中,会要求模块尝试对用户进行身份验证。只有当模块经过了这个阶段,才能到达第二个阶段。在第二个阶段中,每个模块都会提交登录,并指派相关的主体给主题。控制标志将会影响这种两阶段提交如何发生。因此,为了总的身份验证成功,必须满足以下规则:

  • 必须调用所有的REQUIRED模块,而且每个模块都必须成功验证用户。
  • 任何被调用的REQUISITE模块都必须成功验证用户。
  • 如果一个SUFFICIENT模块成功验证了用户,总的成功取决于所有REQUIRED模块,以及任何在SUFFICIENT模块之前调用的EQUISITE模块的成功。
  • 如果登录顺序仅由OPTIONAL模块组成,至少要有一个模块必须成功验证用户。

  尽管可以编写自己的Authenticator,SSPI的WebLogic默认实现带有大量可以使用的内置验证器。让我们更加详细地了解这些验证器。

默认的Authenticator

  Default Authenticator是基于潜入的LDAP库进行验证的。它提供用户和组的概念,并把用户和组的信息保存在它自己的库中,并允许操作这些信息。这个验证器提供的惟一可配置选项是Minimum Password Length 设置。默认情况下,所有WebLogic用户都必须指定长度至少为8个字符的密码,创建用户时就会确认密码长度。当使用控制台或通过JNDI上下文登录到Administration Server上时,通常是由这个验证器验证用户提供的用户名和密码。当然,控制标志将实际决定调用哪个提供程序。

配置一个LDAP验证器

  WebLogic还允许配置一个可以使用现有外部LDAP目录(比如iPlanet LDAP、Active Directory、Open LDAP和 Novell NDS)的Authentication Provider。事实上,WebLogic的LDAP Authenticator可以与任何与LDAP v3兼容的目录服务器连接。在这部分内容中,我们将考察如何为安全域建立一个iPlanet Authenticator。其他LDAP Authenticator都可以遵循同样的方法。通过使用LDAP Authenticator其中之一,可以对WebLogic进行配置,使其可以识别定义在LDAP库中的用户和组,并基于这些信息进行身份验证。

  首先,在管理控制台的左侧窗格中选择领域下的Authentication节点。如果使用的是默认安全域myrealm,那么该节点应该位于Security/Realms/myrealm/Providers下。然后,选择“Configure a new iPlanet Authenticator”选项,或者右击该节点或从右侧窗格本身。现在,在右侧的General选项卡下,可以看到新Authenticator的总体详细信息。为Authenticator选择一个名称,并确保JAAS Control Flag的值是Required,然后点击Create按钮。

  注意:一开始,把Control Flag设置为Optional可能会有用。这样,即使LDAP身份验证失败,仍然可以使用WebLogic的默认验证器进行身份验证,并获得对管理控制台的访问权。

  现在,选择iPlanet LDAP选项卡,然后当主机、端口和主体应用于您的LDAP服务器时输入它们的值。在这里,主体是指WebLogic将用于连接LDAP服务器的LDAP用户的专有名称(distinguished name,DN)。通常,用户会使用与LDAP服务器上一些管理用户帐号相关的DN。对于iPlanet LDAP来说,这通常是uid=admin,ou=Administrators,ou=TopologyManagement,o=NetscapeRoot。如果LDAP服务器正在监听一个SSL端口,选择SSL Enabled选项,并确保已经指定了SSL端口。现在,点击Apply按钮保存对表单所做的修改。接下来,修改LDAP Principal的Credential属性。在新的一屏中,必须输入用于对定义在Principal属性中的LDAP用户进行身份验证的密码。

  现在,选择Users选项卡,并确保这个表单中的字段匹配LDAP库的配置。大多数情况下,只需要把User Base DN属性的值修改为ou=people,o=mydomain.com。这个属性定义了保存实际用户的LDAP树中分支的基本DN。表17-3列出了Users选项卡下的其他配置设置。

表 17-3.为LDAP Authenticator配置Users选项卡

设置

描述

默认值

User Object Class

这个属性指示保存用户信息的LDAP对象类。

person

User Name Attribute

这项设置指定保存用户名的LDAP用户对象中的属性名称。

uid

User Search Scope

这项设置决定如何在一棵多级分层或单级平面的树中组织用户。它影响到搜索用户时深入的层次深度。可以从下面的值中选择:subtree/onelevel。

subtree

User From Name Filter

这个属性为查找给定用户名的用户指定搜索过滤器。

"(&(uid=%u)(objectclass=person))"

  WebLogic使用切合实际的默认值来填充这些字段,所以在大多数情况下,不必改变它们。点击Apply按钮,然后是Groups选项卡。再次强调,需要确保设置精确地反应LDAP库的结构。大多数情况下,只需要把Group Base DN设置的值修改为(假定是)ou=groups,o=mydomain.com。表17-4列出了Groups选项卡下可用的其他配置设置。

表 17-4. 为LDAP Authenticator配置Groups选项卡

设置

默认值

Static Group Object Class

groupofuniquenames

Static Group Name Attribute

cn

cn Group Search Scope

subtree

Group From Name Filter

"(|(&(cn=%g)(objectclass=groupofUniqueNames))(&(cn=%g)(objectclass=groupOfURLs)))"

  这些设置是相当直观的,而且它们与Users选项卡下的属性具有相同的语义,恰好适用于组。

  Membership选项卡决定如何在LDAP目录中保存和查找组成员。WebLogic为表单中的所有字段都指定了默认值。表17-5 列出了Membership选项卡下的两个重要属性。

表 17-5.为LDAP Authenticator配置Membership选项卡

设置

描述

默认值

Static Member DN Attribute

这项设置指定用于保存组成员的DN的LDAP组对象中的属性名称。

member

Static Group DNs from Member DN Filter

这个属性为查找包含给定组成员名称的所有组指定了一个搜索过滤器。

(&(uniquemember=%M)(objectclass=groupofuniquenames))

  和其他身份验证提供程序不同,iPlanet提供程序还支持动态组,而动态组具有另外的选项。

  既然已经配置了LDAP Authenticator,几乎已经可以重新启动Administration Server了。然而,需要确保服务器的启动身份(即用于启动服务器的WebLogic用户帐号)对应于一个具有必要Admin权限的LDAP用户。这意味着,需要使用iPlanet Console(或特定于LDAP服务器的任意管理工具),并完成以下步骤:

  • 在LDAP库中创建一个Administrators组,并在这个组中放入与WebLogic的启动身份相关联的LDAP用户。
  • 如果无法创建一个Administrators组,那么在LDAP库中创建一个新的组——假设是MyAdministrators。是与WebLogic的启动身份相关联的LDAP用户成为MyAdministrators组的一个成员。然后,使用WebLogic的管理控制台把MyAdministrators组指派给默认的全局角色Admin。

  这样做,可以确保WebLogic的启动身份具有必需的Admin权限。下一次重新启动服务器时,进入管理控制台,然后从Authentication Providers的列表中删除Default Authenticator。应用这些修改之后,重新启动服务器——应该有希望能够在不出现任何身份验证错误的情况下启动,并针对LDAP库的用户和组信息基础建立WebLogic域,以便使用LDAP库。

默认的Identity Asserter

  Default Identity Asserter支持使用X.509证书或IIOP CORBA Common Secure Interoperability Version 2 (CSIv2)令牌进行周边身份验证。周边身份验证的一个不错的例子是,当配置一个web应用程序以使用CLIENT-CERT身份验证时。在这个例子中,WebLogic可以基于来自请求头和cookies的值执行身份确认。如果头名称或cookie名称匹配提供程序的活动令牌类型,该值就会被传递给提供程序。

  这个提供程序要求配置以下属性:

User Name Mapper Class Name

  这个属性指定一个Java类的名称,该Java类用于根据一些模式把X.509证书或X.501 DNS映射为WebLogic用户。User Name Mapping类必须实现weblogic.security.providers.authentication. UserNameMapper接口,而且必须在启动期间在WebLogic的CLASSPATH中可用。

Trusted Client Principals

  这个属性指定一个可能依赖于CSIv2身份确认的客户端主体的列表。可以使用通配符(*)来指示所有客户端主体都受到信任。如果某个客户端主体没有包含在这个列表中,CSIv2身份确认就会失败,而且访问将被拒绝。

  注意,Identity Assertion Provider不检验证明材料。例如,用户可以伪造一个CSIv2令牌,并假定一个错误的身份。因为WebLogic不相信这类令牌,Trusted Client Principals设置提供一种方式来限制可以使用身份确认的客户端主体的集合。在前一章中,我们曾见过一个使用X.509证书作为周边身份验证的良好例子,在这个例子中,我们还提供了一个定制的用户名映射器类,用于把数字证书映射为WebLogic用户。使用双向SSL和X.509证书进行身份确认时,SSL协议可以确保证书不是伪造的。所以,Trusted Client Principals设置只适用于CSIv2身份令牌。

  WebLogic 8.1带有一个默认的用户名映射器,可以从Details选项卡上启用它。这是一个通用的用户名映射器,可以将其配置为从证书中的主体DN字段的某个给定属性中提取用户名。所以,例如,如果客户端证书有一个email属性(E),那么可以把Default User Name Mapper Attribute Type设置为E。还可以指定一个分隔符,在这种情况下,WebLogic将使用属性直到但不包括分隔符的部分。例如,如果需要从email属性提取用户名,想使用分隔符 @。其他可以使用的属性类型有C、CN、L、O、OU、S和ST类型。如果需要比这更复杂的类型,必须创建自己的用户名映射器类。可以参考本章中稍后出现的示例17-2,了解如何编写定制的用户名映射器。

授权提供程序

  授权与访问控制是同义语——它决定一个主题是否能访问一项资源。应用程序请求对某项受保护资源进行操作时,接收请求的资源容器会调用WebLogic Security框架来决定是否授权用户访问资源。在调用的过程中,任何相关的请求参数,比如发出请求的主题,都会被传递给该框架。在做出访问决定之前还需要完成许多事情,如图17-4所示。

  首先调用已配置的Role Mapping Provider。这些提供程序使用请求参数来决定对主题合法的一个角色集合。在此之后,Authorization Provider要决定是否应该允许进行访问。Adjudication Provider具有最后的决定权。它考察Authorization Provider返回的不同访问决定,然后协调任何潜在的冲突。Adjudication Provider基于这些单独的访问决定生成一个最后的裁决。

  一个领域可能包括一个或多个Authorization Provider。例如,可以定义单独控制对JNDI分支、web应用程序、JMS连接工厂等等的访问的Authorization Provider。

图 17-4. 身份验证过程

默认的Authorization Provider

  WebLogic的Authorization Provider通过使用一个基于策略的授权引擎,执行前面提到的各项任务。这意味着WebLogic的资源是通过部署时指派的安全策略进行保护的。早先,在“The Security Provider Architecture”部分中,我们考察了安全策略语句如何能够有助于保护服务器端的资源。

  可以通过管理控制台手动指派安全策略,也可以通过部署描述符中指定的角色设置自动指定安全策略。Default Authorizer允许指定当部署EJB和web应用程序时,授权器是否保存所创建的策略。Policy Deployment Enabled选项指示当部署EJB和web应用程序时,是否评估安全策略。默认情况下,这项设置是启用的。这个选项十分类似于Role Mapping Provider中的Role Deployment Enabled标志。

Role Mapping Provider

  Role Mapping Provider用于在调用时动态地决定一个用户集合,这个集合中的用户在尝试访问受保护资源时,对于某个特定主题是合法的。Authorization Provider可以使用这些信息,通过评估策略和角色信息来决定是否允许用户访问资源。

  角色信息通常是基于特定于J2EE和WebLogic的部署描述符中定义的角色设置、请求资源的操作和任意定制的业务逻辑之上的。可以在部署时配置这些角色,要么使用部署描述符,要么通过管理控制台。因为Authorization Provider恰好在计算授权决定之前调用Role Mapping Provider,而且角色绑定是动态的,所以这为用户提供了一个绝佳的机会,使用户可以把自己的角色分配逻辑嵌入到一个定制的提供程序中。

  安全域可以定义一个或多个Role Mapping Provider。可以配置一个为帮定到JNDI树的资源处理角色关联的Role Mapping Provider,也可以配置另一个为web应用程序处理角色关联的Role Mapping Provider。

默认的Role Mapper

  给定受保护的资源和用户之后,WebLogic的默认Role Mapping Provider将决定对于资源的用户合法的所有角色,这些用户使用保存在它的库中的角色信息。为配置Default Role Mapper,需要从管理控制台左侧窗格的Role Mapping节点下选择Default Role Mapper节点。

  Default Role Mapper只有一项配置设置:Role Deployment Enabled选项。这个选项用于指示提供程序是否保存在部署web应用程序或EJB时创建的角色。这项设置类似于我们早先遇到过的Ignore Security Data in Deployment Descriptors设置,除了它是特定于角色信息的之外。如果设置了Role Deployment Enabled选项,WebLogic会基于部署描述符中的安全数据自动创建角色,然后把它们保存在嵌入式LDAP服务器中。这意味着,如果随后忽略部署描述符中的安全数据,下一次部署EJB或web应用程序时就不会处理部署描述符。默认情况下,Role Deployment Enabled设置是启用的,当这项设置启用时,为部署web应用程序和EJB,需要至少一个角色映射器。

Adjudication Provider

  只有当多个Authorization Provider决定了一个主题是否可以访问受保护的资源之后,该主题才能访问受保护的资源。每个Authorization Provider可以传递以下访问决定中的一种:PERMIT、ABSTAIN或DENY。Authorization Provider需要判定在所涉及的不同Authorization Provider所做出的可能有冲突的决定。通常,Authorization Provider通过仔细权衡每个提供程序的访问决定,从而解决Authorization Provider之间的可能出现的授权冲突。

  因此,如果安全域支持多个Authorization Provider,必须定义一个Adjudication Provider。显然,一个安全域可能只有一个Adjudication Provider!

默认的Adjudicator

  为配置Default Adjudicator,需要在管理控制台的左侧窗格中,从Adjudication节点下选择Default Adjudicator节点。

  在这里,可以调整Requires Unanimous Permit设置,这项设置决定了当涉及到多个Authorization Provider时,Adjudicator授予访问资源的权限的基础。

  如果启用这项设置,只有当所有参与的Authorization Provider都返回一个PERMIT结果时,Adjudicator才会授予访问资源的权限。也就是说,决定必须是一个无异议的PERMIT决定。如果任何提供程序返回一个DENY或ABSTAIN,总体的决定必须是拒绝访问。

  如果禁用该项设置,只有当没有Authorization Provider 返回DENY时,Adjudicator才会授予访问资源的权限。这意味着即使没有获得一个无异议的决定,Adjudicator也会授予访问资源的权限。只要所有的Authorization Provider都投票给PERMIT或 ABSTAIN,主题将获得访问资源的权限。当然,至少有一个Authorization Provider必须投票给对资源的PERMIT访问。

Credential Mapping Provider

  当某个WebLogic用户需要访问一些外部系统时,需要把用户证书映射为外部系统上一个合法的证书。只有这样,已经通过WebLogic身份验证的主题才能登录到该外部系统中。Credential Mapping Provider负责把通过WebLogic身份验证的用户关联到外部系统中的合适证书。通常,Credential Mapping Provider代表另一个组件被WebLogic所调用——特别地,这个组件就是作为需要连接到远程资源的资源适配器宿主的容器。

  Credential Mapping Provider可以处理各种用户证书——比如,用户名-密码组合,数字证书,等等。例如,可以在WebLogic用户和远程遗留DBMS上合法用户的证书之间实现一种证书映射。这可以是远程系统上被授权执行必要操作的合法用户的用户名-密码组合。可以在部署描述符中,或者通过管理控制台来定义这些证书映射。安全域必须定义至少一个Credential Mapping Provider。如果已经定义了多个提供程序,WebLogic会查询所有的提供程序,然后返回可能与WebLogic用户相关的证书的一个列表。

默认的Credential Mapper

  当用户使用资源适配器时,WebLogic的Credential Mapper把WebLogic用户关联到外部系统中的合适证书。所以,Credential Mapping Provider保存了WebLogic用户和组到可用于对远程系统进行身份验证的外部身份的映射。一个恰当的例子是J2EE连接器的使用。在这里,通常需要把WebLogic用户映射为可以访问目标EIS的远程用户。第7章说明了如何为已部署的J2EE连接器配置证书映射。

  为配置提供程序本身,需要在安全域中选择Providers/Credential Mapping下的Default Credential Mapper节点。Default Credential Mapper只提供一项配置设置,即Credential Mapping Deployment Enabled选项,这个选项指示当部署资源适配器的描述符文件时,提供程序是否处理它之中的证书映射。默认情况下,Credential Mapping Deployment Enabled标志被设置为true。就像其他Deployment Enabled设置一样,如果禁用这项设置,资源适配器的部署描述符中指定的证书映射不会覆盖通过管理控制台创建的证书映射。

Auditing Providers

  审计是WebLogic安全框架的另一个重要功能。认可(Nonrepudiation)要求用户维护安全事件的日志,用于对如何访问数据进行点子跟踪。可以对WebLogic的Auditing Provider进行配置,使其记录有关安全及其输出的信息。通常,在执行安全操作之前和之后,Auditing Provider都会代表其他安全提供程序被WebLogic的Auditor调用。这样,Auditor就能够捕捉有关任意安全请求和响应的详细信息。

  通常,Auditing Provider将决定是否基于几种标准审计安全请求。例如,可以为Auditing Provider配置安全级别,自动过滤没有达到所需安全级别的设计请求。Auditing Provider还支持可以记录信息到各种接收器的通道,比如LDAP存储器,数据库表或文本文件。WebLogic的Auditor可以与多个Auditing Provider交互。然而,也可以选择不为安全域定义任何Auditing Provider。

默认的Auditor

  默认情况下,没有为安全域配置Auditing Provider。为了配置一个新的Auditing Provider,需要从管理控制台的左侧窗格中展开Auditing节点,然后选择“Configure a new Default Auditor”选项。在新的一屏中,为审计日志指定一个严重性级别,然后点击Create按钮。

  严重性级别决定了WebLogic的 Default Auditor记录的事件种类。严重性级别越低,审计就越详细。严重性级别可以有以下几种:FAILURE、SUCCESS、ERROR、WARNING和INFORMATION。例如,FAILURE严重性级别可以确保Auditor只记录失败的安全请求(比如失败的身份验证),而INFORMATION严重性级别则可以确保Auditor记录所有的身份验证活动。默认的严重性级别是ERROR。

  注意:严重性级别越低,输出越详细。这可以影响到服务器性能,因为Auditor可能要繁忙许多。应该设置一个切合实际的严重性级别,并仔细监控设置过程,以确保它没有产生不可接受的性能水平。

  审计信息被写到mydomain\myserver\DefaultAuditRecorder.log文件中,这里的mydomain是WebLogic 域的名称。例如,如果某个已通过身份验证的用户尝试查看连接池,并被拒绝访问,Default Auditor将会记录以下信息: #### Audit Record Begin <05-Sep-02 22:24:46> <Severity =FAILURE>
<<<Event Type = Authorization Audit Event ><Subject: 1
Principal = class weblogic.security.principal.WLSUserImpl("A")>
<ONCE><<jdbc>><type=<jdbc>, application=, module=, resourceType=ConnectionPool,
resource=MyPool, action=reserve>>> Audit Record End ####

嵌入式LDAP服务器

  SSPI的任何实现都需要一些种类的安全提供程序数据,可以充当域的安全数据库。WebLogic依赖于嵌入式LDAP服务器来保存其有关用户、组、策略、角色和用户证书的所有信息。嵌入式LDAP服务器对于所有WebLogic的安全提供程序来说都是可访问的,这些安全提供程序需要保存和操作如下数据:Authentication、Authorization、Role Mapping和Credential Mapping Provider。在默认的设置中,管理控制台保存一个LDAP主库,接着这个库被复制给所有Managed Server。WebLogic的提供程序所做的任何修改都会被发送给LDAP主服务器,然后该主服务器就会把适当的修改发送给每台复制的服务器。Administration Server的LDAP库上的数据一有修改,域中的Managed Server就会进行同步。然而,在写到Administration Server和根据网络流量进行复制之间,会出现一个小窗口。

  要配置嵌入式LDAP服务器,需要从管理控制台的左侧窗格中选择域。选择View Domain-Wide Security Settings* 选项,然后选择Configuration/Embedded LDAP选项卡。表17-6列出了可以从Embedded LDAP选项卡进行配置的各种设置。

* WebLogic 7.0用户应该在选择域节点之后选择Security/Embedded LDAP选项卡。

表 17-6. 配置嵌入式LDAP服务器

设置

描述

默认值

Credential

.这项设置指定允许连接到LDAP服务器的密码。

none

Backup Hour/Minute

这项设置决定计划进行备份时的小时/分钟。

23;5

Backup Copies

这项设置决定应该备份LDAP数据的拷贝数量。

7

Cache Enabled

这个选项指示是否对LDAP服务器启用缓存。

true

Cache Size

这项设置决定LDAP服务器使用的缓存大小。

32KB

Cache TTL

这项设置决定保存在缓存中的项的持续时间。

60s

Replica Refresh

默认情况下,修改会被定期发送给Managed Server。如果在Managed Server停机时,做出了大量修改,那么发送数量巨大的修改将会花费很大代价。为了对此进行优化,启用这个参数,以确保启动时会总体刷新所有被复制的数据。

false

Master First

在极端的例子中,可以启用这个标志,这样任何Managed Server都必须联系LDAP主服务器,而不是它的本地LDAP服务器。

false

  注意如何配置LDAP服务器以备份它本身。备份文件被写入一个称为mydomain\servername\ldap\backup 的目录中,可以用于替换ldapfiles目录中的这些文件。

对LDAP服务器的外部访问

  用户可以使用最喜欢的LDAP浏览器来访问WebLogic的嵌入LDAP服务器。这对于集成很有用,或者也许在提供导入和导出用户数据的方式时更加有用。在访问服务器之前,需要建立它的安全证书。就像前面内容中描述的那样,选择Embedded LDAP选项卡。然后选择Credential选项,并输入LDAP服务器的证书(密码)。WebLogic将使用这个密码对任意LDAP客户端进行身份验证。

  现在,启动LDAP浏览器,并让它指向下面的URL:

ldap://hostname:port/dc=mydomain

  在这里,mydomain是指WebLogic域的名称。LDAP服务器不允许匿名访问,所以必须指定用户名cn=Admin。而对于密码,需要提供与开始为WebLogic的嵌入式LDAP服务器配置的相同证书。然后,可以浏览LDAP数据,这些数据包括有关用户、组、安全角色、策略语句等等的信息。

  一些LDAP浏览器还允许以LDIF格式导入和导出LDAP数据。这提供了一种轻松的迁移数据方式,这里的数据包括分别位于DN ou=people,ou=myrealm,dc=mydomain和 ou=groups,ou=myrealm,dc=mydomain下的用户和组信息。再次说明,myrealm是指安全域的名称,而mydomain则是指WebLogic域的名称。经验丰富的管理员会进一步限制对嵌入LDAP服务器的访问。因为WebLogic的 LDAP 服务器还支持IETF LDAP Access Control Model,如果需要,可以实现对LDAP服务器的细粒度访问。

  在WebLogic 8.1中,许多安全提供程序还允许导出和导入它们的数据。可以在Authentication、Authorization、Credential Mapper和Role Mapper Provider的Migration选项卡上找到这些功能。

配置两个域之间的信任

  可以对两个和多个WebLogic域进行配置,使它们相互信任。当在两个不同的WebLogic域之间建立了信任之后,就可以支持一个域中已通过身份验证的用户访问另一个域中的受保护资源。例如,一个域中的已验证用户可以调用另一个域中的受保护web服务或EJB,而不需要进行任何另外的身份验证。让我们假定,我们有两个WebLogic域,A和B。域之间的信任意味着,一个域——假定为A——中主题的主体受到另一个域B的信任(反之亦然),而且就好像是域B的本地主体一样。域B中的Authorization Provider不会知道该主题的主体属于域A。

  只有当两个WebLogic域具有相同的Credential属性时,它们才会彼此信任。Credential属性代表一个字符串值,这个字符串值被指定给一个域,然后用于标记属于在该域中创建的主题的主体。通常,如果没有显式地设置域证书,那么当Administration Server首次启动时,就会给它指定一个随机值。域中的所有Managed Server在启动时都会导入这个证书。为设置WebLogic域的Credential属性,需要选择View Domain-Wide Security Settings* 选项,然后选择Configuration/Advanced选项卡。如果两个域共享相同的Credential属性值,主体的签名将会匹配,而且被两个域认可。因此,为在几个域之间建立信任,只需要确保给它们的Credential属性赋了相同的值。

客户端中的JAAS身份验证

  我们已经见过Java客户端如何在登录WebLogic Server时对自身进行身份验证的许多例子。在大多数例子中,客户端在建立JNDI上下文时提交一个用户名-密码组合作为它的证书:
Hashtable env = new Hashtable( );
env.put(Context.INITIAL_CONTEXT_FACTORY,
             "weblogic.jndi.WLInitialContextFactory");
env.put(Context.PROVIDER_URL, "t3://10.0.10.10:7001");
env.put(Context.SECURITY_PRINCIPAL, "system");
env.put(Context.SECURITY_CREDENTIALS, "12341234");
Context ctx = new InitialContext(env);
// use the JNDI context as "system" user ...

  WebLogic还允许用户使用JAAS构建可以使用更多标准方法进行身份验证的Java客户端。即使JAAS身份验证比基于JNDI的传统身份验证要稍嫌冗长,但用户客户端的可移植性更强。因为JAAS框架具有可插入的本质,它应该能使用户能够从容面对身份验证技术的未来变化,因为无需修改客户端代码。

剖析JAAS客户端

  一个JAAS客户端包括大量类和接口之间的相互关系,如图17-5所示。让我们分析一下在JAAS风格的身份验证期间,这些不同的对象是如何相互作用的:

Subject

  这代表着身份验证顺序的目标。一旦客户端通过了身份验证,它就包含一个主题实例,而且会使用映射到客户端的所有主体填充它。

LoginContext

  该对象负责使用它的主体填充Subject。它最重要的方法是login( ),该方法交付一个已验证的Subject给客户端。要构建一个LoginContext实例,需要提供两个附类的对象:CallBackHandler和LoginModule实例。

CallBackHandler

  该对象负责获取要进行身份验证的客户端的用户名和密码。在基于Swing的应用程序中,CallBackHandler实例肯定会弹出一个对话框,向最终用户请求数据。事实上,CallBackHandler实例是由LoginModule调用的。

LoginModule

  该对象是指能够对用户证书进行身份验证的任意实体。一个单独的JAAS配置文件设定了如何实现LoginModule。通常,用户可以实现自己的登录模块。然而,WebLogic还为用户提供一个使用方便的LoginModule,它可以通过所提供的用户名和密码,基于已提供其URL的WebLogic实例对客户端进行身份验证。如果身份验证成功,LoginModule就会使用它的主体填充主题。通过使用这个已验证的主题,JAAS客户端现在可以执行一个或多个已授权动作。

图 17-5. 对一个JAAS客户端进行身份验证时的典型交互

PrivilegedAction

  任何实现PrivilegedAction接口的类都封装了一些代码,Java客户端可以在由已填充的Subject定义的安全上下文中运行这些代码。在这种安全上下文下调用已授权动作之前,weblogic.security.Security.runAs( )方法允许客户端把一个Subject与当前线程相关联。

示例JAAS客户端

  让我们考察一下如何构建一个能够为WebLogic对自身进行身份验证的JAAS客户端。我们将使用从上至下的方法讲述这个例子,从JAAS客户端需要实现的内容开始,然后再细分为其实现的单个组件。让我们从主类SimpleJAASClient开始,采取以下步骤:

  • 它从命令行读取用户名、密码和URL作为输入参数。
  • 它尝试连接到特定的URL,然后使用所提供的用户名和密码对客户端进行身份验证。
  • 它在新获得的已验证对象下执行一个已授权的动作。例17-1列出了我们的JAAS客户端的源代码。

例17-1列出了我们的JAAS客户端的源代码。

例17-1.示例JAAS客户端

package com.oreilly.wlguide.security.jaas;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
public class SimpleJAASClient {
   public static void main(String[] args) {
      String username = args[0];
      String password = args[1];
      String url = args[2];
      LoginContext loginContext = null;
      // Create a LoginContext using our own CallBackHander
      try {
         loginContext = new LoginContext("Simple",
            new SimpleCallbackHandler(username, password, url));
      } catch (Exception e) {
         // Can get a SecurityException or a LoginException
         e.printStackTrace( );
         System.exit(-1);
      }
      // Now authenticate. If we don't get an exception, we succeeded
      try {
         loginContext.login( );
      } catch (Exception e) {
         // Can get FailedLoginException, AccountExpiredException,
         // or CredentialExpiredException
         e.printStackTrace( );
         System.exit(-1);
      }
      // Retrieve authenticated subject and perform action using it
      Subject subject = loginContext.getSubject( );
      SimpleAction simpleAction = new SimpleAction(url);
      weblogic.security.Security.runAs(subject, simpleAction);
   }
}

  注意我们是如何突出JAAS客户端的重点部分的。我们的第一个关键步骤是建立LoginContext对象:

loginContext = new LoginContext("Simple",new SimpleCallbackHandler(username, password, url));

  LoginContext对象使用将在JAAS身份验证期间使用的CallBackHandler和LoginModule实例初始化了客户端。构造器带的第二个参数是我们自己的CallBackHandler实例,LoginModule将使用它来获得用户证书,以及将对我们的客户端进行身份验证的WebLogic实例的URL。

  构造器带的第一个参数是Simple,用于为客户端查找适当的LoginModule。JAAS客户端依赖于一个配置文件,该配置文件把JAAS登录模块的名称映射为它们的实现,而且还指定了另外的参数。例17-2列出了我们使用的JAAS配置文件。

例17-2. 登录配置文件jaas.config
Simple {
   weblogic.security.auth.login.UsernamePasswordLoginModule
   required
};

  我们的配置文件包含一个Simple入口,在给定用户名和密码的基础上为身份验证指定了WebLogic的LoginModule: weblogic.security.auth.login.UsernamePasswordLoginModule.当运行JAAS客户端时,必须使用一个系统属性指定该配置文件的位置。下面说明了如何运行我们的示例JAAS客户端:

java -Djava.security.auth.login.config=jaas.config \
   com.oreilly.wlguide.security.jaas.SimpleJAASClient system pssst t3://10.0.10.10:
8001/

  这样,我们就可以配置LoginContext以使用WebLogic的 LoginModule,它支持使用用户名-密码组合的身份验证。稍后,我们将看一看如何使用JAAS配置文件透明地使用LoginModule实现来代替这种方法。

  建立登录上下文之后,我们调用了loginContext.login( )方法来执行实际的登录。我们的LoginContext将利用已配置的登录模块和回调处理对象,并尝试借助服务器对客户端进行身份验证。如果客户端成功通过身份验证,可以从LoginContext获得已验证的主题:

Subject subject = loginContext.getSubject( );

  这个已验证Subject上的getPrincipals( )方法将获得与用户相关的所有主体。例如,如果我们的JAAS客户端使用系统管理员的证书进行身份验证,已验证的Subject将具有两个主体:代表用户的system,和代表用户的组的Administrators。现在,我们可以使用这个主题来执行一个或多个“已授权”的操作。换句话说,这些操作是在这个已验证主题的上下文中执行的:

weblogic.security.Security.runAs(subject, simpleAction);

  这里给出一个忠告——客户端必须调用WebLogic的Security类上的runAs( )方法。runAs( )方法带有两个参数:已验证的Subject和一个PrivilegedAction对象,后者包装了应用程序与服务器的特定交互。例17-3说明了我们的JAAS客户端希望执行的操作。

例17-3. 一个非常简单的操作
package com.oreilly.wlguide.security.jaas;
import java.security.PrivilegedAction;
import java.sql.Connection;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
public class SimpleAction implements PrivilegedAction {
   private static final String JNDI_NAME = "jdbc.xpetstore";
   private String url;
   public SimpleAction(String url) {
      this.url = url;
   }
   public Object run( ) {
      Object obj = null;
      try {
         Context ctx = null;
         Hashtable ht = new Hashtable( );
         ht.put(Context.INITIAL_CONTEXT_FACTORY,
         "weblogic.jndi.WLInitialContextFactory");
         ht.put(Context.PROVIDER_URL, url);
         // Get a context for the JNDI lookup
         ctx = new InitialContext(ht);
         // do any work here
         DataSource ds =(javax.sql.DataSource) ctx.lookup(JNDI_NAME);
         // ...
      } catch (Exception e) {
         e.printStackTrace( );
      }
      return obj;
   }
}

在这里,需要考虑以下重点:

  • 类实现 java.security.PrivilegedAction接口。然后,任何JAAS 客户端都可以在已验证Subject的上下文中调用这个类的一个实例。
  • run( )方法封装了客户端与服务器的交互。通常,客户端将建立一个JNDI上下文,使用它来获取绑定到JNDI树的资源,然后调用/访问这些资源。在前面的例子中,我们使用了JNDI上下文来获得JDBC数据源。
  • 当我们在PrivilegedAction.run( )方法中建立JNDI上下文时,我们没有为JNDI身份验证提供任何用户证书。JAAS客户端提供已验证的Subject给runAs( )方法,确保PrivilegedAction对象是在这个主题的上下文中调用的。也就是说,runAs( )方法负责把已验证的主题与当前线程关联起来。

  例17-4列出了我们的CallBackHandler类的源代码。通常,回调处理程序将与客户端交互,提示用户输入用于身份验证的用户名和密码。在我们的简单JAAS客户端的例子中,我们提供了必需的证书和URL给我们的回调处理程序的构造器,这样回调便可容易地返回这些信息。

例17-4. 一个简单的回调处理器
package com.oreilly.wlguide.security.jaas;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import weblogic.security.auth.callback.URLCallback;

public class SimpleCallbackHandler implements CallbackHandler {
   private String username = null;
   private String password = null;
   private String url = null;
   
   public SimpleCallbackHandler(String pUsername, String pPassword, String pUrl) {
      username = pUsername; password = pPassword; url = pUrl;
   }

   public void handle(Callback[] callbacks)
         throws java.io.IOException, UnsupportedCallbackException {
      for (int i = 0; i < callbacks.length; i++) {
         if (callbacks[i] instanceof NameCallback) {
            NameCallback nc = (NameCallback) callbacks[i];
            nc.setName(username);
         } else if (callbacks[i] instanceof URLCallback) {
            URLCallback uc = (URLCallback) callbacks[i];
            uc.setURL(url);
         } else if (callbacks[i] instanceof PasswordCallback) {
            PasswordCallback pc = (PasswordCallback) callbacks[i];
            pc.setPassword(password.toCharArray( ));
         } else {
            throw new UnsupportedCallbackException(
            callbacks[i], "Unrecognized Callback");
         }
      }
   }
}

  难题的最后一部分是JAAS登录模块。在前面,我们曾见过JAAS配置文件如何使我们能够建立我们的客户端,以使用WebLogic用于进行用户名-密码式的身份验证的登录模块UsernamePasswordLoginModule。这个Login-Module类希望我们的回调处理程序处理用户名和密码回调,以及URL回调(可选)。login( )方法为JAAS框架提供进入中LoginModule的入口点。它使用用户的证书为WebLogic对用户进行身份验证,如果成功,则返回一个使用适当主体填充的已验证Subject。

  我们已经很容易地构建我们自己的登录模块,并通过修改配置文件来引用这个模块。login( )方法是LoginModule实现类中最重要的,因为这个方法负责执行实际的身份验证。通常,它必须使用已配置的回调处理程序来获得用户名、密码和URL。然后,它必须创建一个使用这些数据填充的Environment对象,并调用WebLogic的Authenticate类上的authenticate( )方法来执行登录,并生成一个使用所需主体填充的已验证Subject。下面的代码说明了如何完成这种身份验证:

weblogic.jndi.Environment env = new weblogic.jndi.Environment( );
env.setProviderUrl(url);
env.setSecurityPrincipal(username);
env.setSecurityCredentials(password);
weblogic.security.auth.Authenticate.authenticate(env, subject);

  通常,WebLogic的登录模块应该可用于大多数场合;用户需要提供自己的LoginModule实现这种情况是不大可能出现的。

创建定制的身份验证提供程序

  相对来说,开发自己的安全提供程序是较为专业化的任务——只有当WebLogic的默认提供程序不够用的时候,它才是必需的。绝大多数定制提供程序往往改变了默认的身份验证或身份确认机制。以下内容将给出一个这样的例子。如果您计划创建自己的提供程序,我们建议您阅读有关WebLogic安全提供程序API的文档,从而了解每个提供程序的生命周期。官方web站点上提供了这些信息。BEA的 dev2dev web站点还包含了大量示例提供程序。

MBean

  WebLogic的提供程序体系结构是基于MBean的(参见第20章)——如果您准备编写一个新的提供程序,它必须具有一个相应的MBean实现。WebLogic为创建必需的MBean部署文件和实现提供了工具。在运行时,代表您的提供程序的MBean将被用于创建您的提供程序实现的一个实例——从某种意义上说,MBean是您必须提供的提供程序实现的一个工厂。因此,这将使用MBean来读取它的配置信息。任何提供程序MBean都必须扩展WebLogic提供的适当MBean基本类型。为了促进这些周边类的创建,WebLogic提供了一些实用工具。它们基于MBean定义文件(MBean Definition File,MDF),一个用于描述您的MBean实现的XML文件。例17-5列出了这样一个XML文件。

例17-5. 一个身份验证提供程序的简单MDF (MyAuthentication.xml)

<?xml version="1.0" ?>
<!DOCTYPE MBeanType SYSTEM "commo.dtd">
<MbeanType Name = "MyAuthenticator" DisplayName = "MyAuthenticator"
  Package = "com.oreilly.wlguide.security.iap"
  Extends = "weblogic.management.security.authentication.Authenticator"
  PersistPolicy = "OnUpdate">
<MbeanAttribute Name = "ProviderClassName" Type = "java.lang.String"
  Writeable = "false"
  Default =
  ""com.oreilly.wlguide.security.iap.MyAuthenticationProviderImpl""
  />
<MbeanAttribute Name = "Description" Type = "java.lang.String"
  Writeable = "false"
  Default = ""O'Reilly Authentication Provider""
  />
<MBeanAttribute Name = "Version" Type = "java.lang.String"
  Writeable = "false" Default = ""1.0""
  />
</MBeanType>

  如果您要实现自己的身份验证提供程序,可以逐字地复制例17-5,然后修改重点显示的部分即可(除非您想添加另外的属性)。ProviderClassName MBean属性最为重要。这个属性指示代表我们必须实现的定制提供程序的类名,在本例中就是com.oreilly.wlguide.security.iap.MyAuthentication-ProviderImpl。

   有了这个文件之后,就可以使用MbeanMaker实用工具来处理MDF文件,并生成MBean和桩:

java -DcreateStubs="true" weblogic.management.commo.WebLogicMBeanMaker -MDF ..\MyAuthentication.xml -files outdirectory

  这会生成大量文件,所有这些文件都将被忽略,除非您实现了定制的操作和属性。这些文件全部位于outdirectory中。现在,需要做的就是提供提供程序的实际实现,作为一个整体重新编译和重新打包,然后部署之。

  注意,要使用这些实用工具,不仅需要执行setEnv脚本来准备环境,而且必须给类路径添加一个额外的JAR文件。这个JAR文件位于WL_HOME/server/lib/mbeantypes/wlManagement.jar。

身份验证提供程序

  现在,我们转而关注身份验证提供程序实现本身。回想一下,在MDF中,我们指定了要用于提供身份验证提供程序的类的类名(参见例17-6)。这个类必须实现weblogic.security.spi.AuthenticationProvider接口。

例17-6. 一个身份验证提供程序的实现

package com.oreilly.wlguide.security.iap;

import java.util.HashMap;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
import weblogic.management.security.ProviderMBean;
import weblogic.security.provider.PrincipalValidatorImpl;
import weblogic.security.spi.*;

public final class MyAuthenticationProviderImpl implements AuthenticationProvider {
   private String description;
   private LoginModuleControlFlag controlFlag;

   // Our mapping of users to passwords/groups, instead of being in LDAP or in a
   // database, is represented by a HashMap of MyUserDetails objects..
   public class MyUserDetails {
      String pw;
      String group;
      // We use this to represent the user's groups and passwords
      public MyUserDetails(String pw, String group) {
         this.pw=pw; this.group = group;
      }  
      public String getPassword( ) {return pw;}
      public String getGroup( ) {return group;}
   }

   // This is our database
   private HashMap userGroupMapping = null;
   
   public void initialize(ProviderMBean mbean, SecurityServices services) {
      MyAuthenticatorMBean myMBean = (MyAuthenticatorMBean)mbean;
      description = myMBean.getDescription( ) + "\n" + myMBean.getVersion( );
      System.err.println("#In realm:" + myMBean.getRealm( ).wls_getDisplayName( ));
      // We would typically use the realm name to find the database
      // we want to use for authentication. Here, we just create one.
      userGroupMapping = new HashMap( );
      userGroupMapping.put("a", new MyUserDetails("passworda", "g1"));
      userGroupMapping.put("b", new MyUserDetails("passwordb", "g2"));
      userGroupMapping.put("system", new MyUserDetails("12341234",
                           "Administrators"));
      
	  String flag = myMBean.getControlFlag( );
      if (flag.equalsIgnoreCase("REQUIRED")) {
         controlFlag = LoginModuleControlFlag.REQUIRED;
      } else if (flag.equalsIgnoreCase("OPTIONAL")) {
         controlFlag = LoginModuleControlFlag.OPTIONAL;
      } else if (flag.equalsIgnoreCase("REQUISITE")) {
         controlFlag = LoginModuleControlFlag.REQUISITE;
      } else if (flag.equalsIgnoreCase("SUFFICIENT")) {
         controlFlag = LoginModuleControlFlag.SUFFICIENT;
      } else {
         throw new IllegalArgumentException("Invalid control flag " + flag);
      }
   }

   public AppConfigurationEntry getLoginModuleConfiguration( ) {
      HashMap options = new HashMap( );
      options.put("usermap", userGroupMapping);
      return new AppConfigurationEntry(
           "com.oreilly.wlguide.security.provider.MyLoginModuleImpl",
           controlFlag, options
      );
   }
   public String getDescription( ) {
      return description;
   }
   public PrincipalValidator getPrincipalValidator( ) {
      return new PrincipalValidatorImpl( );
   }
   public AppConfigurationEntry getAssertionModuleConfiguration( ) {
      return null;
   }
   public IdentityAsserter getIdentityAsserter( ) {
      return null;
   }
   public void shutdown( ) {}
   }

  这个类简单地提供了一种获得各种模块的方法,这些模块中大部分是可选的。例如,我们没有提供身份确认器,所以我们只要返回null即可。同样地,我们简单地把WebLogic的默认主体验证器实现返回为主体验证器。所有动作都发生在initialize( )和getLoginModuleConfiguration( )方法中。

  通常,initialize( )方法的任务是建立执行任何身份验证所必需的资源。正如您从实现中所看到的,它可以直接访问MBean——所以,如果添加了额外的属性或操作给MBean,在这里就可以使用它们。在我们的例子中,我们简单地以哈希图的形式建立了一个本地数据库。然后,它保存了用在模块配置中的控制标志。这最终将被传递给登录模块。

  getLoginModuleConfiguration( )方法返回一个封装了登录模块的对象——我们必须实现的功能的最后部分。可以认为这个方法与JAAS配置文件担任的角色相同。它指示提供程序这次在服务器端使用哪个登录模块。注意,登录模块被包装在一个对象(AppConfigurationEntry)中,这个对象还捕捉了控制标志和选项。

登录模块

  现在,我们需要提供一个登录模块,如例17-7所示。这使用了标准的JAAS API。

例17-7. 一个登录模块

package com.oreilly.wlguide.security.provider;

import java.io.IOException;
import java.util.*;
import javax.security.auth.Subject;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import javax.security.auth.spi.LoginModule;
import weblogic.security.principal.WLSGroupImpl;
import weblogic.security.principal.WLSUserImpl;

/**
 * This login module will be called by our Authentication Provider.
 * It assumes that the option, usermap, will be passed which contains
 * the map of users to passwords and groups.
 */
public class MyLoginModuleImpl implements LoginModule {
   private Subject subject;
   private CallbackHandler callbackHandler;
   private HashMap userMap;
   // Authentication status
   private boolean loginSucceeded;
   private boolean principalsInSubject;
   private Vector principalsBeforeCommit = new Vector( );
   
   public void initialize(Subject subject, CallbackHandler callbackHandler,
   Map sharedState, Map options) {
      this.subject = subject;
      this.callbackHandler = callbackHandler;
      // Fetch user/password map that should be set by the authenticator
      userMap = (HashMap) options.get("usermap");
   }
   
   /* Called once after initialize to try and log the person in */
   public boolean login( ) throws LoginException {
      // First thing we do is create an array of callbacks so that
      // we can get the data from the user
      Callback[] callbacks;
      callbacks = new Callback[2];
      callbacks[0] = new NameCallback("username: ");
      callbacks[1] = new PasswordCallback("password: ", false);
      try {
         callbackHandler.handle(callbacks);
      } catch (IOException eio) {
         throw new LoginException(eio.toString( ));
      } catch (UnsupportedCallbackException eu) {
         throw new LoginException(eu.toString( ));
      }
   
      String username = ((NameCallback) callbacks[0]).getName( );
      char [] pw = ((PasswordCallback) callbacks[1]).getPassword( );
      String password = new String(pw);
      if (username.length( ) > 0) {
         if (!userMap.containsKey(username))
            throw new FailedLoginException("Authentication Failed: Could not find user:
" + username);
         String realPassword = ((MyAuthenticationProviderImpl.MyUserDetails) userMap.
get(username)).getPassword( );
         if (realPassword == null || !realPassword.equals(password))
            throw new FailedLoginException("Authentication Failed: Password incorrect
for user" + username);
      } else {
         // No Username, so anonymous access is being attempted
      }
      loginSucceeded = true;
      // We collect some principals that we would like to add to the user
      // once this is committed.
      // First, we add his username itself
      principalsBeforeCommit.add(new WLSUserImpl(username));
      // Now we add his group
      principalsBeforeCommit.add(new WLSGroupImpl(((MyAuthenticationProviderImpl.
MyUserDetails)userMap.get(username)).getGroup( )));
      return loginSucceeded;
   }

   public boolean commit( ) throws LoginException {
      if (loginSucceeded) {
         subject.getPrincipals().removeAll(principalsBeforeCommit);
         principalsInSubject = true;
         return true;
      } else {
         return false;
      }
   }

   public boolean abort( ) throws LoginException {
      if (principalsInSubject) {
         subject.getPrincipals( ).removeAll(principalsBeforeCommit);
         principalsInSubject = false;
      }
      return true;
   }

   public boolean logout( ) throws LoginException {
      return true;
   }
}

  这段代码尽管很长,但是相当直观。让我们对它进行仔细分析。initialize( )方法可以直接访问在提供程序中配置的选项。由于我们把我们的数据库放在了选项中,这个方法提供了理想的地方用于提取数据库。动作的余下部分发生在login( )、commit( )和abort( )方法的组合之中。如果登录成功,而且登录模块的控制标志是整个登录要提交,这将调用commit( )方法——否则就将调用abort( )方法。这些方法确保了应该与主题关联的主体被放入主题中。login( )方法完成了所有的工作。首先,它建立了大量回调——我们需要用户名和密码。注意,实际的回调实现将会由WebLogic来处理。例如,当尝试启动WebLogic服务器或者访问管理控制台时,WebLogic会提示您提供系统用户证书。最终提供给登录方法的数据正是来自回调。处理完回调之后,我们提取用户的用户名和密码,并把它们放在我们的数据库中。如果成功,我们就列出出一份我们想与用户关联的主体的清单,并把这些主体放在变量principalsBeforeCommit中。只有当WebLogic调用commit( )方法时,主体才会被添加给主题。

部署提供程序

  创建了身份验证提供程序和登录模块之后,就可以把这些与生成的桩及MBI文件打包在一起。为此,需要执行以下命令:

java weblogic.management.commo.WebLogicMBeanMaker -MJF myAuth.jar -files .

  现在,可以部署新提供程序了。把myAuth.jar复制到WL_HOME/server/lib/mbeantypes 目录,然后重新启动服务器。注意,所有定制的提供程序都必须位于这个目录中。启动管理控制台,并导航到Security/myrealm/Providers/Authentication 节点。在可用验证器和身份确认器的列表中,可以找到“Configure a new My Authenticator”这个选项。选择这个选项并点击Create,就可以配置验证器了。在接下来的选项卡中,将会注意到,可以修改控制标志。如果把这个标志修改为像requisite这样的值,确保您的数据库有一个用户属于Administrators组。如果没有,就无法启动服务器!在部署期间使用 OPTIONAL标志来避免这些问题。

创建一个Identity Assertion Provider

  如果您有一些外部系统——假定为Java客户端或者甚至也许是外部的web服务器——需要对用户进行身份验证,而且现在,您想让这个用户参与到涉及WebLogic的活动中来。此外,您不想WebLogic对该用户重新进行身份验证。相反,您想使用一些由要使用的外部系统所生成的令牌作为自动的WebLogic登录。这在许多单点登录场景中是相当典型的。实现这种效果的关键是使用Identity Assertion Provider。让我们看一看如何能够实现这类场景。

  我们准备采用一个外部的Java客户端作为例子,该客户端大概已经执行了一些用户身份验证,而且现在需要把这个身份转移给WebLogic,以便访问受保护的web应用程序。首先,让我们配置web应用程序以使用身份确认。为此,需要设置login-config使用CLIENT-CERT授权方法。由于这是标准的J2EE,需要创建一个web.xml文件,内容如下:

<security-constraint>
<!-- web resource collection omitted -->
<auth-constraint>
<description>nyse</description>
<role-name>mysecrole</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>CLIENT-CERT</auth-method>
<realm-name>myrealm</realm-name>
</login-config>
<security-role>
<role-name>mysecrole</role-name>
</security-role>>

  现在,让我们假定,我们有一个客户端(随便使用什么语言编写都可以),它已经执行了一些用户身份验证,而且现在需要访问受保护的web页面之一——假设是http://10.0.10.10:8001/index.jsp。下面的客户端就是一个这样的例子:

URL url = new URL("http://10.0.10.10:8001/index.jsp");URLConnection connection = url.openConnection( );BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream( )));// Read the input streamin.close( );

  如果现在运行这个程序,当试着访问输入流时,可能会得到一个IOException异常。这是401 HTTP错误代码,意思是您还没有获得授权。我们将用来解决这个问题的方法是,让客户端提供一个令牌,然后配置一个Identity Assertion Provider,以接受这个令牌并授权给用户。Identity Assertion Provider可以自动利用请求cookies或头。例如,如果WebLogic找到了与令牌名称相同的头属性(我们马上就可以看到如何使用令牌名称配置身份提供程序),它会假定头属性的值就是令牌的值。我们将使用的令牌是一个简单的字符串,当我们创建到服务器的连接时,就会在HTTP请求头中发送这个字符串。为此,把前面的代码修改为下面这样:

URL url = new URL(urlAddr);URLConnection connection = url.openConnection( );connection.setRequestProperty("MyToken",encodedToken);// Everything as before

  在我们的例子中,请求属性的名称是MyToken,它很有意义。这被解释为令牌的类型,稍后我们将会看到这一点。这里给出一个小小的警告,WebLogic始终希望进入的令牌是基于64位编码的。可以使用实用工具类weblogic.utils.encoders.BASE64Encoder来做到这一点。所以,要创建一个编码的令牌,可以编写下面这样的代码:

String token = "jon";BASE64Encoder encoder = new BASE64Encoder( );String encodedToken = encoder.encodeBuffer(token.getBytes( ));

  可以在令牌中放入任何文本,只要Identity Assertion Provider可以读取它就行了。在我们的例子中,将使用一个简单的字符串,用于代表已验证的用户。

  注意:WebLogic 8.1允许用户配置Identity Assertion Provider,以使用非编码的令牌,在这种情况下,不需要使用编码器。

  现在剩下的事情就只有创建一个Identity Assertion Provider了。在我们的例子中使用的MBean定义文件已在例17-8中完整地给出。

例17-8. 确认提供程序的MDF文件MyA.xml

<?xml version="1.0" ?>
<!DOCTYPE MBeanType SYSTEM "commo.dtd">
<MBeanType Name = "MyA" DisplayName = "MyA"
  Package = "com.oreilly.wlguide.security.iap"
  Extends = "weblogic.management.security.authentication.IdentityAsserter"
  PersistPolicy = "OnUpdate"
>
<MBeanAttribute Name = "ProviderClassName" Type = "java.lang.String"
  Writeable = "false"
  Default = ""com.oreilly.wlguide.security.iap.MyAProviderImpl""
/>
<MBeanAttribute Name = "Description" Type = "java.lang.String"
  Writeable = "false" Default = ""My Identity Assertion Provider""
/>
<MBeanAttribute Name = "Version" Type = "java.lang.String"
  Writeable = "false" Default = ""1.0""
/>
<MBeanAttribute Name = "SupportedTypes" Type = "java.lang.String[]"
  Writeable = "false" Default = "new String[] { "MyToken" }"
/>
<MBeanAttribute Name = "ActiveTypes" Type = "java.lang.String[]"
  Default = "new String[] { "MyToken" }"
/>
</MBeanType>

  请注意以下内容:

  • 因为我们编写的是一个Identity Asserter,它必须扩展weblogic.management.security.authentication.IdentityAsserter MBean。
  • ProviderClassName属性必须始终被设置为实现类。
  • SupportedTypes属性必须被设置为令牌类型。在本例中,即为MyToken。
  • ActiveTypes属性列出了列出了要激活的提供程序的支持类型的子集。因为我们想激活惟一的令牌,所以把它也设为MyToken。

  可以照常创建支持文件。在这里,我们把所有的输出都放在了out目录中:

java -DcreateStubs="true" weblogic.management.commo.WebLogicMBeanMaker -MDF MyA.xml-files out

  最后,需要创建提供程序类com.oreilly.wlguide.security.iap.MyAProviderImpl,ProviderClassName属性中引用了它。

例17-9 完整地列出了这个类。

例17-9. 提供程序实现

package com.oreilly.wlguide.security.iap;

import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.AppConfigurationEntry;
import weblogic.management.security.ProviderMBean;
import weblogic.security.spi.*;

public final class MyAProviderImpl
             implements AuthenticationProvider, IdentityAsserter {
   private String description; // holds our description which we derive from MBean
        attributes
   
   public void initialize(ProviderMBean mbean, SecurityServices services) {
      MyAMBean myMBean = (MyAMBean)mbean;
      description = myMBean.getDescription( ) + "\n" + myMBean.getVersion( );
   }

   public CallbackHandler assertIdentity(String type, Object token)
            throws IdentityAssertionException {
      if (type.equals("MyToken")) {
         byte[] tokenRaw = (byte[])token;
         String username = new String(tokenRaw);
         return new SimpleSampleCallbackHandlerImpl(username,null,null);
      } else
         throw new IdentityAssertionException("Strange Token!");
      }
      public String getDescription( ) {
         return description;
      }
      public void shutdown( ) {
      }
      public IdentityAsserter getIdentityAsserter( ) {
         return this; // this object is the identity asserter
      }
      public AppConfigurationEntry getLoginModuleConfiguration( ) {
         return null; // we are not an authenticator
      }
      public AppConfigurationEntry getAssertionModuleConfiguration( ) {
         return null; // we are not an authenticator
      }
      public PrincipalValidator getPrincipalValidator( ) {
         return null; // we are not an authenticator
      }
   }
}

  最重要的方法是initialize( )和assertIdentity( )。initialize()方法简单地从代表提供程序的MBean提取一些信息,然后使用这些信息来创建描述。assertIdentity( )方法带有两个参数,令牌的类型和令牌本身。我们简单地检查令牌类型是否正确,然后把令牌映射为用户名。在这里,无疑可以做很多事情,比如验证令牌的可靠性以获得更高的安全性。该方法必须返回一个标准的JAAS回调处理程序,最终会调用这个回调处理程序来提取用户名(也就是说,只使用NameCallback)。我们使用的是我们在例17-4中定义的回调处理程序。注意,身份确认器也可以是验证器,在这种情况下,它可以使用属于用户的用户名和组填充主题。因为我们进行的是纯身份确认,相应的方法只会返回null。

  把这个文件和回调处理程序放入out目录,然后使用以下命令来创建一个打包的提供程序:

java weblogic.management.commo.WebLogicMBeanMaker -MJF myIAP.jar -files out

  把这个提供程序复制到WL_HOME/server/lib/mbeantypes目录下,然后重新启动服务器。启动管理控制台,并导航到Security/myrealm Providers/Authentication节点。在可用验证器和身份确认器的列表中,应该可以找到“Configure a new MyA...”选项。选择这个选项并点击Create,就可以配置身份确认器了。在接下来的选项卡中,您将会注意到,支持的令牌类型被设置为MyToken和对于MyToken是活动的令牌。现在,必须重新启动服务器,从而使修改生效。

  如果返回客户端应用程序,您会发现,将不再看到未授权的警告(假定jon属于权限组mysecrole,该组有权访问web资源)。为了进一步说明这一点,可以尝试以这种方式访问servlet或JSP页面,这都会调用request. getUserPrincipal( )。您会发现,这次调用会如您所愿地返回jon。

  下面对上述内容做一个小结,如图17-2所示:

  • 客户端尝试访问受保护的web页面。web容器注意到,客户端没有任何安全证书,而且web应用程序实现了身份确认,所以它使用了Identity Assertion Provider,并传入适当的请求参数。
  • Identity Asserter直接从传入的令牌获取用户名,然后以回调处理程序的形式返回它。
  • 那么,为安全域配置的任何登录模块都会投入使用,借助回调处理程序来获取用户名。所以,例如,Default Authenticator将投入使用,并让用户登录。然而,因为它知道数据来自Identity Asserter,它不会要求密码。结果,用户登录成功,而且现在可以访问web应用程序。
 作者简介
icon
Jon Mountjoy
Jon Mountjoy 是dev2dev的编辑。他喜欢XML和J2EE技术,并且是“WebLogic: The Definitive Guide”的作者之一。他维护两个关于dev2dev的blog: 一个关于dev2dev的每周编辑blog和一个更个性化的blog。
dot dot dot

dot
  作者其它文章
您对本文的评价
您对这篇文章的看法如何?
太棒了!5分 不错啊 4分 一般般 3分 有待提高 2分 不好 1分

   
相关产品