下列主题适用于对已经熟悉基本概念(在 Portlet API 和 编写 portlet 中描述)的开发者。
使用凭证保险库
对于门户网站保护的资源,WebSphere Portal 使用 CORBA 凭证和加密的 LTPA cookie 来认证用户(请参阅认证概念以获取详细信息)。然而,对于需要拥有自己的认证的后端系统,portlet 需要提供一些认证表单来访问这些远程应用。为了提供单点登录用户经验,portlet 必须可以存储和检索特殊关联应用程序的用户凭证,以及使用这些凭证代表用户来登录。WebSphere Portal 支持凭证保险库的使用,在那里用户和管理员可以安全地存储认证凭证。所写的用于从保险库抽取用户凭证的 portlet 可以隐藏用户的登录提问。
凭证保险库精确地提供此功能。Portlet 可以通过 Credential Vault PortletService 使用它。下列章节提供关于凭证保险库的更多信息。
凭证保险库组织
按如下所示组织 Portal Server 的凭证保险库:
- 门户网站管理员可以分区保险库为几个保险库段。保险库段仅能由门户网站管理员创建和配置。
- 一个保险库段包含一个或多个保险库槽。保险库槽是 portlet 存储和检索用户凭证的“抽屉”。每个槽拥有一个凭证。
- 保险库槽与保险库实现中的资源链接。
- 保险库实现是用户凭证实际存储的位置。保险库实现的示例包含 WebSphere Portal 的缺省数据库或 IBM Tivoli Access Manager 锁定框。
- 保险库实现中的资源与应用程序或需要拥有自己的认证的后端系统对应。资源的示例包含 Lotus Notes、人员记录或银行帐户。
保险库段
保险库段被标志为管理员管理或用户管理。当 portlet(代表门户网站用户)可以设置和检索两种类型的段的凭证时,允许它们仅能以用户管理的保险库段来创建保险库槽。下图显示如何在不同的保险库实现中分布管理员管理的保险库段。仅有一个用户管理的保险库段,并且驻留在 WebSphere Portal 提供的保险库中。
保险库实现 | |||||
保险库服务 | WebSphere Portal 保险库 | ||||
---|---|---|---|---|---|
用户管理的保险库(U) | |||||
槽 Ua![]() ![]() | ![]() | ||||
槽 Ub![]() ![]() | |||||
槽 Uc![]() ![]() | |||||
管理员管理 段(A1) | |||||
槽 A1a![]() ![]() | |||||
槽 A1b![]() ![]() | |||||
槽 A1c![]() ![]() | |||||
| |||||
管理员管理 段(A2) | 其它保险库实现 | ||||
槽 A2a![]() ![]() | ![]() | ||||
槽 A2b![]() ![]() | |||||
槽 A2c![]() ![]() | |||||
要使用另一个保险库实现,需要保险库适配器。请参阅使用其它凭证保险库实现以获取更多信息。
保险库槽
WebSphere Portal 提供的凭证保险库可以分辨保险库槽的四种不同类型:
- 系统槽存储所有用户和 portlet 共享实际机密的系统凭证。
- 管理槽允许每个用户存储一个管理员定义的资源(例如,Lotus Notes)的机密。
- 共享槽存储用户 portlet 共享的用户凭证。
- portlet 专用槽存储非 portlet 共享的用户凭证。
凭证对象
CredentialVault PortletService 以凭证对象格式返回凭证:
com.ibm.wps.portletservice.credentialvault.credentials.Credential
WebSphere Portal 区分消极的和活动的凭证对象:
- 消极凭证对象是凭证机密的容器:使用消极凭证的 portlet 需要从凭证中抽取机密,并自己执行与后端的所有认证通信。
示例:消极凭证对象使用(pseudo 代码)
Object userSecret = credential.getUserSecret(); < portlet 使用用户机密连接到后端系统认证 > < portlet 可以使用连接来与后端应用程序通信 > < portlet 在后端看管记录日志 >
- 活动凭证对象隐藏 portlet 凭证机密,无法将它从凭证中抽取。作为替换,活动凭证对象提供看管所有认证的商务方法。
示例:活动凭证对象使用(pseudo 代码)
// 登录到后端系统 credential.login() // 获取要使用的已认证的连接 URLConnection = credential.getAuthenticatedConnection(); // 在后端注销 credential.logout();
第二种情况允许 portlet 使用诸如基本认证、基于 HTTP 格式的认证,或 POP3 认证的标准机制来触发对远程服务器的认证,甚至无需知道凭证机密。他们可以请求门户网站代表他们来认证,然后使用已经认证的连接。从安全性的观点来看,portlet 从不与凭证机密接触,因此不存在 portlet 可能违反任何诸如在 portlet 会话中存储机密的安全性规则的风险。尽管不可能总是有适当可用的活动凭证类,它是将要使用的凭证对象的首选类型。
在凭证类型注册表中注册所有门户网站中可用的凭证类型。WebSphere Portal 提供凭证类型的一个小的集合,但是可以在该注册表中容易地注册附加凭证对象。
与当前发行版一起提供的凭证类为:
消极凭证对象:
-
SimplePassive
- 该凭证对象以序列化的 Java 对象的格式存储机密。由于当前保险库服务不支持二进制大对象(BLOB)机密,此对象仅为将来使用。 UserPasswordPassive
- 以用户标识/密码的格式存储机密的凭证对象成对。 JaasSubjectPassive
- 以 javax.security.auth.Subject 对象的格式存储机密的凭证对象。再次,当前此类机密无法由保险库服务存储。
活动凭证对象:
-
HttpBasicAuth
- 该凭证对象存储用户标识/密码机密并支持 HTTP 基本认证。 HttpFormBasedAuth
- 该凭证对象存储用户标识/密码并支持基于 HTTP 格式的认证。 JavaMail
- 存储用户标识/密码的凭证对象成对,并且影响 javax.mail API 的认证功能。
在 PortletSession 中存储凭证对象
凭证对象不实现 java.io.Serializable
—— 它们无法简单地存储在 PortletSession
中。这是为安全起见的原因。因为凭证类把实际的凭证机密作为它们其中一个专用属性来存储,对应用程序应用程序服务器会话数据库有访问权的任何人可以查找到机密。
然而,只要确保它在群集设置中没有序列化,则可以在 PortletSession
中存储凭证对象。这样做的一种方法将定义把实际的凭证对象作为瞬时成员进行存储的凭证容器类。然后可以在 PortletSession
中存储该容器对象而没有任何问题,仅需要确保检查序列化期间凭证对象是否丢失并在这种情况下从保险库再次检索它。
示例:凭证对象会话容器
import com.ibm.wps.portletservice.credentialvault.credentials.Credential; public class CredentialSessionContainer implements java.io.Serializable { private transient Credential credential = null; public void setCredential(Credential cred) {this.credential = cred;} public Credential getCredential() {return credential;} public boolean hasCredential() {return credential != null;} }
凭证保险库使用方案
需要凭证来完成它们的服务的 portlet 有两个选项:
- 使用现有的槽,该槽已经由门户网站管理员在管理员管理的保险库段定义。
- 在用户管理的保险库段创建槽。
选择的选项取决于如何使用 portlet。通常,最好的解决方案隐藏用户凭证保险库的技术详细信息。以下是一些使用槽的示例方案。
内部网 Lotus Notes 邮件 portlet
公司拥有内部网雇员门户网站。每个门户网站用户拥有 Lotus Notes 邮件服务器帐户,并且将在雇员缺省门户网站页面的一页上部署和预配置 Lotus Notes 邮件 portlet。
设计解决方案:
Notes 邮件 portlet 需要存储用户的 Notes 密码。正如大部分用户实际上将会使用此 portlet,管理员需要为它创建“Lotus Notes 凭证槽”。管理员使用 portlet 的配置方式为所有具体 portlet 实例设置保险库槽标识。portlet 允许用户在编辑方式下设置个人 Notes 密码。portlet 可以在凭证保险库中存储每个用户的密码。
库存 portlet 的原料
公司的购买部门运行一个集成不同的旧应用程序的门户网站。这些应用程序之一是直接与供应商系统连接的大型机定序应用程序。多个雇员使用定序 portlet。然而,大型机应用程序由单个系统标识保护;它不支持多个用户帐户。
设计解决方案:
定序 portlet 需要使用系统标识访问定序应用程序。portlet 部署期间,管理员配置保险库槽标识。因此,门户网站管理员在管理员管理的保险库段创建保险库槽,并且把它标记为系统凭证。管理员在该槽中使用凭证保险库 portlet 来存储定序系统标识和密码。购买部门雇员完全不必关心凭证。
因特网邮件联合 POP3 portlet
因特网公用门户网站在其它功能部件中提供门户网站用户可以使用来从一定数量的 POP3 邮件帐户收集邮件的邮件联合 portlet。
设计解决方案:
邮件联合 portlet 仅是公用门户网站的另一个功能部件,因此可能仅由一些门户网站用户使用。此外,不清楚一个用户需要从外部联合多少邮件帐户。因此,门户网站管理员为该 portlet 创建保险库槽是没有意义的。相反,portlet 在编辑方式下为用户提供舒适的配置。用户可以添加必需的所有 POP3 邮箱。portlet 为用户管理保险库段中每个用户邮箱创建一个保险库槽。
理论上,用户可以在页面上配置两个 portlet 实例,一个用于商务帐户,另一个用于专用邮件帐户。因此,因为它最不可能与其它 portlet 共享用户邮件凭证,所以保险库槽创建的 portlet 更好的标记为 portlet 专用。
wp_root/dev 目录中 CredentialVaultImplementation.zip 文件包含使用凭证保险库服务的代码节。该文件还包含集成此代码到 portlet 的说明。构造代码使之允许管理员选择是否用户可以为他们的凭证创建自己的保险库槽,在管理员定义的槽中存储他们的凭证,或使用系统凭证。credential_vault_readme.html 文件中为管理员提供说明,并且可以作为和 portlet 一起提供的 portlet 文档的基础一起使用。
CredentialVaultService 的方法
本节提供 portlet 可以通过 CredentialVaultService 使用的方法的主要概述。
-
该方法返回所有在凭证类型注册表中注册的凭证类型的列表。
返回值
凭证类型以字符串对象迭代符的格式返回。
getSlotDescription
-
该方法返回指定语言环境中某个槽的描述。
参数
- slotId [String]
槽的标识符。
- locale [Locale]
描述语言环境。如果设置为空,将使用缺省语言环境。
返回值
描述以字符串对象的格式返回。异常
PortletServiceException:如果无法检索描述,则丢弃。
getAccessibleSlots
- slotId [String]
-
该方法返回所有授权给 portlet 使用的槽的列表。
参数
- request [PortletRequest]
为了确定 portlet 标识、portlet 应用程序标识和类似标识,CredentialVault 服务需要 portlet 请求。
返回值
该描述以 CredentialSlotConfig 对象迭代符的格式返回。异常
PortletServiceException:如果无法检索槽的列表,则丢弃。
setCredentialSecretBinary
- request [PortletRequest]
-
该方法设置拥有二进制凭证数据的凭证机密。
参数
- slotId [String]
槽的标识符。
- secret [byte[]]
二进制格式的凭证机密数据。
- request [PortletRequest]
为了确定 portlet 标识、portlet 应用程序标识和类似标识,CredentialVault 服务需要 portlet 请求。
异常
PortletServiceException:如果凭证机密不是二进制类型或无法设置机密,则丢弃。
setCredentialSecretUserPassword
- slotId [String]
-
该方法设置拥有用户标识/密码对格式的凭证数据的凭证机密。
参数
- slotId [String]
槽的标识符。
- userId [String]
凭证用户标识。
- password [char[]]
凭证密码。
- request [PortletRequest]
为了确定 portlet 标识、portlet 应用程序标识和类似标识,CredentialVault 服务需要 portlet 请求。
异常
PortletServiceException:如果凭证机密不是二进制类型或无法设置机密,则丢弃。
createSlot
- slotId [String]
-
该方法创建一个新的槽。
参数
- resourceName [String]
资源名称。
- segmentId [ObjectID]
创建槽的保险库段的对象标识。
- descriptions [Map]
人类可理解的关于槽的描述,由描述的语言环境产生密钥。一个描述——至少在缺省的语言环境中——总是仅为 portlet 专用槽而设置,用户根本不可能查看该描述,在这种情况下,它设置为空。
- keywords [Map]
描述槽的特性和可以用于搜索的关键字列表。
- secretType [int]
CredentialVaultService 常量中定义的机密类型。
- active [boolean]
初始化凭证对象所需要的配置参数映射。大多数凭证类没有这些参数(设置为空)。请参阅凭证类文档以获取详细信息。
- portletPrivate [boolean]
标志表明该槽是 portlet 独占使用,还是可以被其它 portlet 共享。
- request [PortletRequest]
为了确定 portlet 标识、portlet 应用程序标识和类似标识,CredentialVault 服务需要 portlet 请求。
返回值
该方法返回新创建的槽的 CredentialSlotConfig 对象。异常
PortletServiceException:如果凭证机密不是二进制类型或无法设置机密,则丢弃。
getDefaultVaultSegmentId
- resourceName [String]
-
返回缺省用户管理的保险库段的 ObjectID。当前仅有用户管理的槽,所以返回用户管理的段的标识。
返回值
该方法返回缺省用户管理的保险库段的 ObjectID,如果无法找到缺省的用户管理的保险库段则为空。异常
PortletServiceException:如果无法检索保险库段的信息,则丢弃。
getAllVaultSegments
-
返回所有保险库段的列表。
返回值
该方法以 VaultSegmentConfig 对象列表格式返回所有保险库段。异常
PortletServiceException:如果无法检索保险库段的信息,则丢弃。
getCredential
-
该方法从凭证保险库检索某个凭证。
参数
- slotId [String]
槽的标识符。
- secretType [int]
CredentialVaultService 常量中定义的机密类型。
- config [Map]
初始化凭证对象所需要的配置参数映射。大多数凭证类没有这些参数(设置为空)。请参阅凭证类文档以获取详细信息。
- request [PortletRequest]
为了确定 portlet 标识、portlet 应用程序标识和类似标识,CredentialVault 服务需要 portlet 请求。
返回值
该方法返回新创建的槽的 CredentialSlotConfig 对象。异常
- PortletServiceException
如果凭证机密不是二进制类型或无法设置机密,则丢弃。
- CredentialSecretNotSetException
如果没有设置各自的凭证机密,则丢弃。如果有问题的凭证是用户凭证,portlet 应该通过提示用户以获取凭证数据来重新操作该异常,以便调用各自的 setCredential() 方法。
getUserSubject
- slotId [String]
-
该方法返回用户 JAAS 对象。这是 getCredential 调用的一个特殊的例子。
参数
- request [PortletRequest]
为了确定 portlet 标识、portlet 应用程序标识和类似标识,CredentialVault 服务需要 portlet 请求。
返回值
该方法返回新创建的槽的 CredentialSlotConfig 对象。异常
PortletServiceException:如果凭证机密不是二进制类型或无法设置机密,则丢弃。 - request [PortletRequest]
getCredentialTypes
示例:从保险库读取凭证
下面是一个使用用户标识/密码凭证登录到受基于 HTTP 格式的登录页面保护的 web 应用程序的 portlet 示例。示例显示如何使用 CredentialVaultService 从保险库读取凭证,以及如何使用该凭证。它不显示在 portlet 编辑方式下 portlet 如何查询用户以获取他/她的机密以及在保险库中存储它。
示例:Portlet 和 HttpFormBasedAuthCredential 使用 CredentialVault PortletService
import org.apache.jetspeed.portlet.service.PortletServiceException; import com.ibm.wps.portletservice.credentialvault.CredentialVaultService; import com.ibm.wps.portletservice.credentialvault.CredentialSecretNotSetException; import com.ibm.wps.portletservice.credentialvault.credentials.HttpFormBasedAuthCredential; ... public void doView( PortletRequest request, PortletResponse response ) throws PortletException, IOException { // get output stream and write out the results PrintWriter writer = response.getWriter(); // get the CredentialVault PortletService PortletContext context = this.getPortletConfig().getContext(); CredentialVaultService service = (CredentialVaultService) context.getService(CredentialVaultService.class); // retrieve slotId from persistent portlet data String slotId = (String) request.getData().getAttribute("VaultTestSlotID"); if (slotId == null) { writer.println("<h2>Credential not found. Please set it in the edit mode! </h2>"); return; } // bundle the credential config data HashMap config = new HashMap(); config.put( HttpFormBasedAuthCredential.KEY_USERID_ATTRIBUTE_NAME, "userid"); config.put( HttpFormBasedAuthCredential.KEY_PASSWORD_ATTRIBUTE_NAME, "password"); config.put( HttpFormBasedAuthCredential.KEY_LOGIN_URL, "EAI.yourco.com/login.jsp"); config.put( HttpFormBasedAuthCredential.KEY_LOGOUT_URL,"EAI.yourco.com /quit.jsp"); config.put( HttpFormBasedAuthCredential.KEY_USE_AUTH_COOKIES, Boolean.TRUE); // get the actual credential from the credential vault HttpFormBasedAuthCredential credential; try { credential = (HttpFormBasedAuthCredential) service.getCredential(slotId, "HttpFormBasedAuth", config, request); } catch (PortletServiceException serviceExc) { writer.println("<h2>Credential vault error, please contact your admin! </h2>"); return; } catch (CredentialSecretNotSetException userExc) { writer.println("<h2>Credential not set. Please set it in the edit mode! </h2>"); return; } try { // use credential object to log in at the backend server credential.login(); // get an authenticated connection to the backend server and send the actual request connection = credential.getAuthenticatedConnection("EAI.yourco.com/request.jsp"); // Work with the connection: send an HTTP GET or POST and evaluate the response [...] // your business code // use credential object to log out at the backend server credential.logout(); } catch (IOException exc) { writer.println("<h2>Single-sign-on error, login at backend failed! </h2>"); return; } // get output stream and write out the results PrintWriter writer = response.getWriter(); }
使用其它凭证保险库实现
可以使用 WebSphere Portal 提供的保险库,或提供自己的实现。凭证保险库服务允许您通过写保险库适配器连接门户网站到其它保险库实现。每个保险库实现需要它自己的保险库适配器和门户网站一起工作。除了保险库适配器外,必须配置 Portal Server 来使用新的保险库。请参阅凭证保险库以获取详细信息。
下面是一个保险库适配器的示例,MemoryVault,它扩展了 VaultAdapter 类。当写您自己的保险库适配器时,请使用下列准则。
- 需要完成所有抽象方法。可选地,如果它们的适配器实现需要初始化或终止,它们可能会重设 init() 和 destroy() 方法。
- 为了 WebSphere Portal 装入适配器,必须有一个公用的、无自变量的构造器。其中,应该调用 setVersion() 方法来设置该保险库适配器的描述。
保险库适配器示例
/* @copyright sample */ package com.ibm.wps.sso.vaultservice; import com.ibm.logging.*; import com.ibm.logging.mgr.*; import java.util.Iterator; import java.util.Hashtable; import com.ibm.wps.puma.User; import com.ibm.wps.util.DataBackendException; import com.ibm.wps.sso.vaultservice.exceptions.*; import com.ibm.wps.sso.credentialvault.secrets.CredentialSecret; /** * In Memory Sample Vault. It uses a Hashtable of Hashtables to store the * credentials * *<pre> * Licensed Materials - Property of IBM, 5724-B12, (C) Copyright IBM Corp. 2002 * * DISCLAIMER OF WARRANTIES: * ------------------------- * The following [enclosed] code is sample code created by IBM Corporation. * This sample code is provided to you solely for the purpose of assisting * you in the development of your applications. * The code is provided "AS IS", without warranty of any kind. IBM shall * not be liable for any damages arising out of your use of the sample code, * even if they have been advised of the possibility of such damages. *</PRE> */ public class MemoryVault extends VaultAdapter { /** Copyright */ private static final String COPYRIGHT = "Licensed Materials - Property of IBM, 5724-B12, (C) Copyright IBM Corp. 2002"; /** Version String */ private final static String VERSION = "WebSphere Portal Sample Memory Vault 1.0"; /** Supported Secret Types */ private final static int[] SUPPORTED_SECRETS = { CredentialSecret.TYPE_USERID_STRING_PASSWORD_STRING }; /** Resource Hashtable */ private Hashtable resources = null; /** * Init routine. Sets the version string * * @param String Configuration Filename * @return True if initialization succeeded, false if initialization failed. */ public boolean init(String configFilename) { // Set the version setVersion(VERSION); // Create the Resource Hashtable resources = new Hashtable(); return(true); } // Resource methods. . . /** * Adds a resource of the given name to the vault. * * @param resource Resource name to create * @throws DataBackendException Problem communicating with the * backend vault or the resource could not be * added */ public void createResource(String resource) throws DataBackendException { // Add it if we don't have it if (!(containsResource(resource))) { resources.put(resource, new Hashtable()); } else { throw(new DataBackendException("The resource already exists")); } } /** * Deletes the specified resource from the vault. * * @param resource Resource name to delete * @throws DataBackendException Problem communicating with the * backend vault or if the resource could not * be deleted */ public void deleteResource(String resource) throws DataBackendException { // Remove the resource from the Hashtable if (containsResource(resource)) { resources.remove(resource); } else { throw(new DataBackendException("The resource does not exist")); } } /** * Tells if the vault contains the specified resource. * * @param resource Resource name * @return true if the vault has the resource, false otherwise */ public boolean containsResource(String resource) { return(resources.containsKey(resource)); } /** * Returns a list of Resources * * @return Iterator of resources */ public Iterator listResources() { return(resources.keySet().iterator()); } /** * Returns an array of integers of the supported secret types * * @see CredentialSecret * @return array of supported secret types */ public int[] getSupportedSecretTypes() { return(SUPPORTED_SECRETS); } /** * Tells if the specified secret type is supported * * @param int SecretType to check * @see CredentialSecret * @return true if the secret type is supported, false otherwise */ public boolean isSecretTypeSupported(int secretType) { for (int i = 0; i < SUPPORTED_SECRETS.length; i++) { if (secretType == SUPPORTED_SECRETS[i]) { return(true); } } // Not supported. . . return(false); } // Credential Operation Methods. . . /** * Adds a mapped credential of the provided secret type under the * specified resource. * * @param secret Credential Secret to add * @param user User Object * @param resource Resource to add the credential under * @throws SecretTypeNotSupportedException The provided secret type is * not supported. * @throws DataBackendException Problem communicating with the * backend vault or the credential could not * be added */ public void addCredential(CredentialSecret secret, User user, String resource) throws SecretTypeNotSupportedException, DataBackendException { if (!(isSecretTypeSupported(secret.getType()))) { throw(new SecretTypeNotSupportedException("Secret Type /"" + secret.getType() + "/" is not supported")); } // Do we have this resource? if (containsResource(resource)) { // Get the hash for this resource Hashtable credentials = (Hashtable) resources.get(resource); // Add the secret credentials.put(user.getId(), secret); } else { throw(new DataBackendException("The credential could not be added, the resource does not exist")); } } /** * Modifies a mapped credential of the provided secret type under the * specified resource. * * @param secret New Credential Secret * @param user User Object * @param resource Resource to modify the credential under * @throws SecretTypeNotSupportedException The provided secret type is * not supported. * @throws DataBackendException Problem communicating with the * backend vault or if the credential was not * found */ public void modifyCredential(CredentialSecret secret, User user, String resource) throws SecretTypeNotSupportedException, DataBackendException { if (!(isSecretTypeSupported(secret.getType()))) { throw(new SecretTypeNotSupportedException("Secret Type /"" + secret.getType() + "/" is not supported")); } boolean exists = false; // Do we have this resource? if (containsResource(resource)) { // Get the hash for this resource Hashtable credentials = (Hashtable) resources.get(resource); // Does the user have an entry? if (credentials.containsKey(user.getId())) { // Change the secret credentials.put(user.getId(), secret); exists = true; } } if (!exists) { throw(new DataBackendException("The credential was not found")); } } /** * Deletes a mapped credential of the provided secret type under the * specified resource. * * @param secretType Secret Type * @param user User Object * @param resource Resource to delete the credential under * @throws SecretTypeNotSupportedException The provided secret type is * not supported. * @throws DataBackendException Problem communicating with the * backend vault or the credential was not * found */ public void deleteCredential(int secretType, User user, String resource) throws SecretTypeNotSupportedException, DataBackendException { if (!(isSecretTypeSupported(secretType))) { throw(new SecretTypeNotSupportedException("Secret Type /"" + secretType + "/" is not supported")); } boolean exists = false; // Do we have this resource? if (containsResource(resource)) { // Get the hash for this resource Hashtable credentials = (Hashtable) resources.get(resource); // Does the user have an entry? if (credentials.containsKey(user.getId())) { // Change the secret credentials.remove(user.getId()); exists = true; } } if (!exists) { throw(new DataBackendException("The credential was not found")); } } /** * Retrieves a mapped credential of the provided secret type under the * specified resource. * * @param int Secret Type * @param user User Object * @param resource Resource to delete the credential under * @return CredentialSecret containing the mapped credentials * @throws SecretTypeNotSupportedException The provided secret type is * not supported. * @throws DataBackendException Problem communicating with the * backend vault or if the credential does * not exist */ public CredentialSecret getCredential(int secretType, User user, String resource) throws SecretTypeNotSupportedException, DataBackendException { if (!(isSecretTypeSupported(secretType))) { throw(new SecretTypeNotSupportedException("Secret Type /"" + secretType + "/" is not supported")); } CredentialSecret secret = null; // Do we have this resource? if (containsResource(resource)) { // Get the hash for this resource Hashtable credentials = (Hashtable) resources.get(resource); // Does the user have an entry? secret = (CredentialSecret) credentials.get(user.getId()); } if (null == secret) { throw(new DataBackendException("The credential was not found")); } return(secret); } }
使用 JAAS 检索用户凭证
WebSphere Portal 中的单点登录支持允许用户通过 Portal Server 经由 portlet 与许多不同的后端系统进行通信,而不会由每个个别的 portlet 的后端服务器多次提示输入用户名和密码。WebSphere Portal 内部使用 Java 认证和授权服务(JAAS) API 为集成单点登录到 portlet 并通过这些 portlet 到后端应用程序提供框架。
用户经过认证之后,WebSphere Application Server 为该用户生成一个安全性上下文。Websphere Portal 使用此安全性上下文来设置(在门户网站内部)包含多个主体和专用凭证的 JAAS 主题。 当门户网站首先处理 HTTP 请求时,它将设置 JAAS 主题并使它可用作 User 属性,portlet 可以从 PortletSession 对象中访问。从主题中,portlet 可以使用下列方法之一来抽取要传递到它的后端应用程序的主体和凭证。
-
getPrincipals(
java.lang.Class)
-
类是可选的。如果没有指定自变量,此方法返回一个包含所有主体的集合。使用
Class.forName(name)
方法为 getPrincipals 生成一个类自变量。请参阅下例。
PortletContext context = this.getPortletConfig().getContext(); CredentialVaultService service = (CredentialVaultService) context.getService(CredentialVaultService.class); Subject subject = service.getUserSubject(portletRequest); // Get the User DN Set set = subject.getPrincipals(Class.forName("com.ibm.wps.sso.UserDNPrincipal")); UserDNPrincipal userDn = null; if (set.hasNext()) { userDn = (UserDNPrincipal) set.next(); // Got it, print out what it contains. System.out.println("The User DN is " + userDn.getName(); }
下面是一些可以用该方法与
Class.forName()
一起使用的类名称。-
com.ibm.wps.sso.UserDNPrincipal
- 从 LDAP 返回用户的专有名称 com.ibm.wps.sso.GroupDNPrincipal
- 返回 LDAP 组(用户是该组的成员)的专有名称。GroupDNPrincipals 的实例数与用户所属的 LDAP 组的数相同。 com.ibm.wps.sso.UserIdPrincipal
- 返回只有未使用认证代理时用户才使用它登录到门户网站的标识 com.ibm.wps.sso.PasswordCredential
- 返回用户使用它登录到门户网站的密码。只有未使用认证代理时才可用。
来自标准 JDK 的自
java.security.Principal
以下的所有主体。Portlet 调用 getName() 抽取这些主体包含的信息。在 portlet 可以从主题抽取任何信息前,它必须首先获取 PortletSession,然后是 User,最后是主题。
getPrivateCredentials(
java.lang.Class)
-
类是可选的。如果没有指定自变量,此方法返回一个包含所有 PrivateCredentials 的集合。此外使用
Class.forName(name)
从这个方法的类名字符串获取一个 Class 对象。要获取用户的 CORBA 凭证,请将org.omg.SecurityLevel2.Credentials
指定为Class.forName()
的自变量。
基本认证样本
下例演示如何从 PortletSession 抽取主题,然后如何从主题中抽取用户名和密码。接着,Portlet 创建基本认证标题并试图连接到基本认证保护的 URL。URL 指定为 portlet 部署描述符(portlet.xml
)中的配置参数。对于输出,它显示用户名、密码和连接尝试的结果。
在 service() 方法中,JAAS 主题从 CredentialVaultService 中抽取。
Import java.io.*; Import java.net.*; Import java.util.*; Import java.security.*; Import javax.security.auth.*; // for the JAAS Subject import com.ibm.wps.sso.*; // For the Principals contained in the Subject import com.ibm.wps.portletservice.credentialvault.CredentialVaultService; // for the Credential Service import com.ibm.ejs.security.util.Base64Coder; import org.apache.jetspeed.portlet.*; Import org.apache.jetspeed.portlets.*; public class URLSSOSamplePortlet extends AbstractPortlet { /** URL To Access */ private String urlSpec = null; /** * Initialization Routine * * @param PortletConfig Portlet Configuration * @throws UnavailableException */ public void init(PortletConfig portletConfig) throws UnavailableException { super.init(portletConfig); // Get the URL urlSpec = portletConfig.getAttribute("url"); } /** * Retrieves the specified Principal from the provided Subject * * @param Subject subject * @param String Class Name * @return The values of the Principals for the given class name, null if * nothing was returned * @throws PortletException An error occurred retrieving the Principal */ private String[] getPrincipalsFromSubject(Subject subject, String className) throws PortletException { try { // Get the Set of Principals Object[] principals = subject.getPrincipals(Class.forName(className)).toArray(); // Do we have any? if ((null == principals) || (0 == principals.length)) { // No principals of this class name return(null); } // Create the String Array to hold the values String[] values = new String[principals.length]; // Populate the values Array for (int current = 0; current < values.length; current++) { values[current] = ((Principal) principals[current]).getName(); } return(values); } catch (ClassNotFoundException cnfe) { throw(new PortletException("Class " + className + " could not be found", cnfe)); } } /** * Extracts the UserID and Password from the JAAS Subject, creates a Basic * Auth Header from it, and then connects to the resource. * * @param PortletRequest Request Object * @param PortletResponse Response Object * @throws PortletException * @throws IOException */ public void service(PortletRequest portletRequest, PortletResponse portletResponse) throws PortletException, IOException { // Get the Writer from the Response PrintWriter writer = portletResponse.getWriter(); PortletContext context = this.getPortletConfig().getContext(); CredentialVaultService service = (CredentialVaultService) context.getService(CredentialVaultService.class); Subject subject = service.getUserSubject(portletRequest); // Grab the UserID and Password. There will only be one of each // contained in the Subject. String[] userId = getPrincipalsFromSubject(subject, "com.ibm.wps.sso.UserIdPrincipal"); String[] password = getPrincipalsFromSubject(subject, "com.ibm.wps.sso.PasswordCredential"); // The URL writer.println("<TABLE>"); writer.println("<TR><TD><B>URL:</B></TD><TD>" +urlSpec + "</TD></TR>"); // Show the UserId and Password writer.println("<TR><TD><B>UserID:</B></TD><TD>" + userId[0] + "</TD></TR>"); writer.println("<TR><TD><B>Password:</B></TD><TD>" + password[0] + "</TD></TR>"); // Create the Basic Auth Header String basicAuth = Base64Coder.base64Encode(userId[0] + ":" + password[0]); basicAuth = "Basic " + basicAuth; writer.println("<TR><TD><B>Basic Auth Header:</B></TD><TD>" + basicAuth + "</TD></TR>"); writer.println("</TABLE><BR>"); // Create the URL for our protected resource, and get a URLConnection try { URL url = new URL(urlSpec); HttpURLConnection con = (HttpURLConnection) url.openConnection(); // Set our Basic Auth Header con.setRequestProperty("authorization", basicAuth); // Connect con.connect(); // Get the Response Code String responseMessage = con.getResponseMessage(); int responseCode = con.getResponseCode(); // Were we successful? if (HttpURLConnection.HTTP_OK == responseCode) { writer.println("<P>Successfully connected to " + urlSpec + "!</P>"); } else { writer.println("<P>Unable to successfully connect to " + urlSpec + ", HTTP Response Code = " + responseCode + ", HTTP Response Message = /"" + responseMessage + "/"</P>"); } } catch (Exception e) { writer.println("<P>Exception caught in HTTP Connection:</P><TT>"); writer.println(e.getMessage()); e.printStackTrace(writer); writer.println("</TT>"); } } }
LTPA 示例
Lightweight 第三方认证(LTPA)是驻留在相同安全性域中的 WebSphere Application Server 和 Domino Server 之间提供单点登录的机制。LTPA 标记在 HTTP 请求中作为 cookie 执行。本节示例假设:
- portlet 访问相同安装有 WebSphere Portal 的 WebSphere Application Server 上的受保护资源。
- 如果资源驻留在另一个 WebSphere Application Server 或 Domino Server 上,则在其之间启用单点登录。
Object[] temp = subject.getPrivateCredentials(LTPATokenCredential.class).toArray(); LTPATokenCredential ltpaToken = (LTPATokenCredential) temp[0]; // Create the LTPA Cookie Header String cookie = "LtpaToken=" + ltpaToken.getTokenString(); // Create the URL for our protected resource, and get a URLConnection URL url = new URL("http://myserver.example.com/ltpa_protected_resource"); HttpURLConnection con = (HttpURLConnection) url.openConnection(); // Set our LTPA token Cookie con.setRequestProperty("cookie", cookie); // Connect con.connect();
Access Manager 示例
此示例描述当 portlet 在将 Access Manager 用于认证的环境中运行时,如何检索用户凭证。请参阅配置 IBM Tivoli Access Manager 以获取更多信息。
WebSEAL 服务器和 WebSphere Application Server 之间的连接就是结点。WebSEAL 结点允许连接其它的服务器文件系统到 Web 空间并作为单个、统一的对象空间查看资源。Access Manager 通过 WebSEAL 提供一个机制,来通过使用会话 cookie 在各结点间提供单点登录。要实现此目标,WebSEAL 必须是保护 WebSphere Portal 的认证服务器并且 WebSEAL 保护的 portlet 将访问的资源必须驻留在与 Portal Server 相同的 Access Manager 域中。
WebSealLoginModule 为登录的用户将 WebSealCredential 放在 JASS 主题的凭证集中。WebSealCredential 有 PD-S-SESSION-ID、PD-H-SESSION-ID 和 PD-ID cookie。不是所有的 cookie 可以在请求中存在,因此如果它们不显示则 WebSealCredential 在其调用中将返回空。例如,如果 Access Manager 不提供认证,所有这些调用将返回空。portlet 应该仅为这些 getter 方法之一返回为非空的值添加 cookie:
Cookie 名 | WebSealCredential 中的抽取值的方法 |
---|---|
PD-S-SESSION-ID | getPDSSessionId() |
PD-H-SESSION-ID | getPDHSessionId() |
PD-ID | getPDId() |
下列样本从 JASS 主题抽取 WebSealCredential,并打印出每个 cookie 的内容。
// Get the WebSealCredential. . . Object[] creds = subject.getPrivateCredentials(WebSealCredential.class).toArray(); if (1 != creds.length) { writer.println("<p>No WebSEAL Credential Found. . .</p>"); return; } // Cast it. . . WebSealCredential webSealCredential = (WebSealCredential) creds[0]; // Print out the interesting stuff. . . writer.println("<h3>WebSealCredential Information:</h3>/n<table>"); writer.println("<tr><td>PD-S-SESSION-ID</td><td>" + webSealCredential.getPDSSessionId() + "</td></tr>"); writer.println("<tr><td>PD-H-SESSION-ID</td><td>" + webSealCredential.getPDHSessionId() + "</td></tr>"); writer.println("<tr><td>PD-ID</td><td>" + webSealCredential.getPDId() + "</td></tr>"); writer.println("</table>");
相关信息 |
---|