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

developerWorks 中国  >  WebSphere  >

使用 portlet 服务访问可靠的远程 Web 应用程序

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

Wes Wardell (wardell@ca.ibm.com), 软件开发人员, IBM

2005 年 2 月 01 日

本文演示了 portlet 服务在封装 portlet 与远程 Web 应用程序之间的交互上起到的作用。描述了连接到一个为了确保安全而使用轻量级目录访问协议 (Lightweight Directory Access Protocol,LDAP) 目录以及轻量级第三方认证 (Lightweight Third Party Authentication,LTPA) 的 WebSphere Application Server 应用程序。样本代码以及配置实例都包括在示范连接会话 EJB 或 servlet 之中。

引言

考虑您现有的 Web 应用程序中的一种情况, 它部署在 IBM® WebSphere® Application Server 上, 可以使员工能够提交安排度假的申请。公共目录是用于保护应用程序的。假设您想要把员工感兴趣的各种类型的内容重新配置成门户或者工作区环境,并包括进行度假申请的功能。而不是重新配置处理度假申请的人力资源(HR)应用程序,可以编写使用远程应用程序的 portlet。图 1 是集成员工门户和现有人力资源应用程序的简单图示。


图 1. 向体系结构中添加门户
向体系结构中添加门户

要与进行了安全配置的现有 Web 应用程序到门户集成,必须确定您的 portlet 如何连接到 Web 应用程序以及如何处理用户认证。通过编写 portlet 服务连接到 Web 应用程序,来隐藏进行集成的每一个 portlet 的细节。同时这样做也使得将来利用其他方法使用 Web 应用程序时,所有的 portlet 的代码都无需更新。

这篇文章说明了 portlet 服务在安全的环境中如何用于连接远程 Web 应用程序的会话 bean。接下来将描述怎样更新 portlet 服务以如何连接到 servlet,而不是要求对使用 portlet 服务的 portlet 作任何改变。

要发挥这篇文章的最大功效,您应当熟悉一下企业应用程序安全性的概念、portlet 开发、以及 portlet 服务开发。





回页首


Web 应用程序配置

对于本文中的这个实例来说,对远程 Web 应用程序做了如下的假设:

  1. 部署在 IBM WebSphere Application Server V5.1 上。
  2. 启用了安全性。
  3. 为了认证,Web 应用程序以及门户都采用相同的 LDAP 目录和 LTPA 机制。
  4. 应用程序的业务逻辑处于会话 bean 层之中或之后的,例如 Web 应用程序利用会话虚包 (Session Facade) 模式。
  5. 访问会话 bean 是受限制的。单独的门户用户不能访问,但是单 LDAP 用户标识能够用于访问所有的会话 bean。





回页首


什么是 portlet 服务?

向 PortletService 接口提供扩展以及实现向门户添加您自己的 portlet 服务的 WebSphere Portal。CredentialVaultService 是 WebSphere Portal 提供的一个 portlet 服务实例。要获取有关 portlet 服务的详细信息,请参阅产品文档。

portlet 服务提供了业务方法,您的 portlet 为使用由远程 Web 应用程序提供的业务功能需要调用它们。这些方法连接到您的远程 Web 应用程序上的会话 bean。这些方法的实现依赖于您的应用程序的需求。





回页首


编写 portlet 服务

因为门户用户无法访问远程 Web 应用程序,所以 portlet 服务需要使用不同的用户标识进行认证。该用户标识以及密码是从 WebSphere Portal 凭证保险库(credential vault)中获得的,如图 2 中所示。对于要从凭证保险库中获得用户标识和密码的 portlet 服务来说,它需要 portlet 中的 PortletRequest 以及 PortletConfig 对象。因此,将 portlet 设置在调用任何业务方法之前的方法添加到 portlet 服务中。


图 2. 设置共享凭证保险库 slot
设置共享凭证保险库 slot

在业务方法中,需要程序化访问会话 bean 的用户标识注册,并且接下来在认证 Subject 的上下文中调用关于会话 bean 的方法。会话 bean 仅能看到刚才已认证的用户——而不是进入门户系统的用户。

以提交度假申请为例,创建名为 HRPortletService 的 portlet 服务。VacationRequest portlet 以及 HRPortletService 大体上完成了提交申请的任务,如下所示:

  1. VacationRequest 获得 HRPortletService 实例。
  2. VacationRequest 向 HRPortletService 提供当前的 PortletConfig 以及使用 setter 方法的 PortletRequest 对象。这些是稍后依据 portlet 服务成功地检索共享凭证所必需的。
  3. VacationRequest 为了提交度假申请调用 HRPortletService 方法,submitRequest 通过远程人力资源应用程序以预期值对象形式发送。在 submitRequest 之中, 进行如下操作:
    • HRPortletService 获得使用 PortletConfig 的登录 Subject 以及之前特定的 PortletRequest 对象。
    • HRPortletService 查找使用登录主题的人力资源应用程序会话。
    • HRPortletServiceSubject 的安全上下文中调用 submitRequest 会话 bean 的业务方法。

探讨 HRPortletService.submitRequest 方法之前,让我们先探讨一下它所需要调用的方法。下面所示的两种方法一起使用,用于登录 Subject。第一种方法登录到 Web 应用程序并返回 Subject 对象,该对象稍后用于调用会话 bean。通过第一种方法调用第二种方法来获得凭证保险库中的凭证,凭证保险库用于登录由虚构的 mywebapp.ibm.com 服务器托管的远程 Web 服务。随后的代码经 WebSphere Portal 5.0.2 测试,并且也为 WebSphere Portal V5.1 所支持。


清单 1. 登录远程 Web 应用程序
				
private javax.security.auth.Subject getLoginSubject(PortletConfig pc,
  PortletRequest portletRequest) throws HRPortletServiceException
{
  StringBuffer userId = new StringBuffer("");
  StringBuffer password = new StringBuffer("");
  UserPasswordPassiveCredential credential = getCredential(pc,portletRequest);
  if( credential != null) {
    userId.append(credential.getUserId());
    password.append(String.valueOf(credential.getPassword()));
  }
  LoginContext lc;
  javax.security.auth.Subject s=null;
  try
  {
    System.out.println("Login as user: " + userId);
    lc = new LoginContext("ClientContainer", 
      new WSCallbackHandlerImpl(userId.toString(),"mywebapp.ibm.com",
      password.toString()));
    lc.login();
    s = lc.getSubject();
  }
  catch (LoginException le1)
  {
    System.err.println("Cannot create LoginContext: "+le1.getMessage());
    System.err.println("Throwing HRPortletServiceException");
    throw new HRPortletServiceException();
  }	 
  return s;
}	
private UserPasswordPassiveCredential getCredential(PortletConfig pc,
  PortletRequest portletRequest)
{
  CredentialVaultService vaultService = null;
  try
  {
    vaultService = (CredentialVaultService) 
    pc.getContext().getService(CredentialVaultService.class);
  }
  catch (PortletServiceNotFoundException e)
  {
    System.err.println("credential vault service not found");
  }
  catch (PortletServiceUnavailableException unavail)
  {
    System.err.println("credential vault service unavailable: "+unavail);
  }
  UserPasswordPassiveCredential credential = null;
  try 
  {
    if( vaultSlot != null ) {
      credential = (UserPasswordPassiveCredential)vaultService.getCredential(vaultSlot,
        "UserPasswordPassive",new HashMap(),portletRequest);
    }
  }
  catch (CredentialSecretNotSetException e)
  {
    System.err.println("no credential secret: "+e);
  }
  catch (PortletServiceException e2)
  {
    System.err.println("error with portletservice: "+e2);
  }
  return credential;
}

登录之后,submitRequest 需要查找会话 bean 的 remote home。下面的方法完成了这一任务。它利用了从上文中的 getLoginSubject 方法获得的 Subject 对象。该 Subject 对象代表了可以访问会话 bean 的已认证的用户。返回的远程会话 bean 用于调用 bean 的业务方法。


清单 2. 查找远程会话 bean
				
private HRRequestSessionRemote getSessionEJB(javax.security.auth.Subject s)
{
  Object sessionObj = null;
  sessionObj = com.ibm.websphere.security.auth.WSSubject.doAs(s,
    new java.security.PrivilegedAction() 
    {
      public Object run() {
        HRRequestSessionRemote session = null;
        try
        {
          Hashtable env = new Hashtable();
          env.put(Context.INITIAL_CONTEXT_FACTORY,
            "com.ibm.websphere.naming.WsnInitialContextFactory");
          env.put(Context.PROVIDER_URL, "corbaloc:iiop:mywebapp.ibm.com:2809");
          Context initialContext = null;
          initialContext = new InitialContext(env);
			  
          Object o = initialContext.lookup(serverJNDIName);
          HRRequestSessionRemoteHome home=null;
          home =(HRRequestSessionRemoteHome) PortableRemoteObject.narrow(o,
            HRRequestSessionRemoteHome.class);
          session = home.create();
        }
        catch (Exception sessionExc)
        {
          System.err.println("Error getting session: " + sessionExc);
        }
        return session;
      }
    }
  ); //end doAs
  return (HRRequestSessionRemote) sessionObj;
}

可以在您所有的 portlet 服务业务方法中使用以上的方法来调用远程 Web 应用程序。上述 submitRequest 方法的实现已经说明了这种调用,它向用于处理的 Web 应用程序提交 HRRequest 对象。


清单 3. 向 Web 应用程序提交申请
				
public void submitRequest(HRRequest newRequest) throws HRPortletServiceException
{
  if (newRequest != null)
  {
    final HRRequest newReq = newRequest;
    javax.security.auth.Subject s = getLoginSubject(pc,request);
    final HRRequestSessionRemote session = getSessionEJB(s);
    if (session != null)
    {
      //doAs programmatic login userid
      com.ibm.websphere.security.auth.WSSubject.doAs(s,
        new java.security.PrivilegedAction() 
        {
          public Object run() {
            Vector reqs=null;
            try
            {
              session.submitRequest(newReq);
            }
            catch (RemoteException runExc)
            {
              System.out.println("Submit request run failed:" + runExc);
            }
            return null;
          }
        }
      ); //end doAs				
    }
    else
    {
      System.err.println("Error obtaining session bean to submit request for: "+
        newReq.getRequestorShortName());
      throw new HRPortletServiceException();
    }
  }
}

代码在 getSessionEJB 方法中使用 PrivilegedAction 查找会话 bean 的 home,并且为了调用会话 bean 的 submitRequest 方法在 submitRequest 方法中再次使用。所有会话 bean 的交互必须存在于 Subject 对象的上下文中。该 bean 的查找以及 submitRequest 方法的调用并没有结合起来因此会话 bean 的查找代码将需要在每次必须调用关于会话 bean 的不同方法时被复制。





回页首


使用 portlet 服务

正如上文所列,要使用这一 portlet 服务首先需要获得服务的实例。接下来,向服务传递 PortletConfig PortletRequest 对象,需要它们是为了获得凭证保险库之外的凭证。最终,调用关于 portlet 服务的业务方法。源自使用 submitRequest 方法的 VacationRequest portlet 的实例代码片段都包含在此:


清单 4. 从 portlet 中调用 portlet 服务
				
if (portletConfig!=null) {
  if (portletConfig instanceof PortletConfig) {
    PortletContext portletContext= portletConfig.getContext();
    if (portletContext!=null) {
      hrService = (HRPortletService) portletContext.getService(HRPortletService.class);
    }
  }
}
hrService.setPortletConfig(portletConfig);
hrService.setPortletRequest(portletRequest);
hrService.submitRequest(hrRequest);				





回页首


将 portlet 服务改为使用 servlet

在之前的章节中,您使用 portlet 服务连接远程 Web 应用程序的会话 bean。本章节描述了用 portlet 服务调用 servlet 时必须要做的修改。因为所有用于访问远程 Web 应用程序的代码都位于 portlet 服务之中,所以只需要更新 portlet 服务机即可。portlet 是不受影响的。

修改 Web 应用程序的原型

对于本章节来说,下面原型的产生是关于 Web 应用程序的,代替 Web 应用程序配置中的标号 4 和标号 5。

  1. 应用程序的业务逻辑是在 servlet 层之中或之下的。
  2. 访问 servlet 受到已认证用户的限制。个别的已认证门户用户可以访问 servlet。

修改 portlet 服务

以上实现了的getLoginSubject 方法以及 getSessionEJB 方法都不需要了。可以使用 HttpURLConnection 方法调用远程 Web 应用程序的 servlet。对于那些需要向 Web 应用程序中传入以及从 Web 应用程序中传出的对象,应将它们序列化为输入输出流。要获准访问 Web 应用程序,从 PortletRequest 对象中检索 LTPA 令牌并将其添加到 Web 应用服务程序的 HttpURLConnection 中。这里就是检索所采用的代码:


清单 5. 为访问 Web 应用程序而检索 LTPA 令牌
				
private String getLtpaToken() {
  Cookie[] cookies = portletRequest.getCookies();
  String LtpaToken = null;
  if (cookies != null && cookies.length > 0) {
    for (int i = 0; i < cookies.length; i++) {
      if (cookies[i].getName().equalsIgnoreCase("LtpaToken")){	
        LtpaToken = cookies[i].getValue();
      }
    }
  }
  return LtpaToken;
}

这就是您之前看到的 submitRequest 方法,被更新为连接到 servlet:


清单 6. 连接到远程 servlet
				
public void submitRequest(HRRequest newRequest)
		throws HRPortletServiceException {
  URL url=null;
  HttpURLConnection con=null;
  OutputStream os =null;
  OutputStreamWriter osw = null;
  ObjectOutputStream oos = null;
  ObjectInputStream results = null;
  try
  {
    url = new URL(submitRequestsURL);
				
    con = (HttpURLConnection)url.openConnection();
    con.setRequestProperty("Cookie","LtpaToken="+getLtpaToken());
    con.setDoInput(true);
    con.setDoOutput(true);
    con.setAllowUserInteraction(true);
	
    os = con.getOutputStream();
    osw = new OutputStreamWriter(os);
    oos = new ObjectOutputStream(os);
    oos.writeObject(newRequest);
    osw.flush();
	
    results = new ObjectInputStream(con.getInputStream());
  }
  catch(Exception e)
  {
    e.printStackTrace();
  }
  finally
  {
    try
    {
      if(os!=null)
        os.close();
      if(osw!=null)
        osw.close();
      if(oos!=null)
        oos.close();
      if(results!=null)
        results.close();
    }
    catch(Exception e)
    {
      e.printStackTrace();
    }
  }
} 





回页首


结束语

在本篇文章中您看到了如何使用 portlet 服务将您的门户和远程 Web 应用程序的集成封装起来。在这一 portlet 服务中,在安全的环境下为了连接远程 Web 应用程序使用了两种不同的实现方法。



参考资料



关于作者

Wes Wardell 是 IBM 多伦多实验室的 IBM Software Group Solution Test 小组的一位软件开发人员。Wes 于 1996 年在安大略的 Wilfrid Laurier 大学获得计算机科学的理学士学位。




对本文的评价

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

建议?




回页首


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