Accessing secure remote Web applications using a portlet service

This article illustrates the use of a portlet service to encapsulate the interaction between portlets and a remote Web application. It describes connecting to a WebSphere Application Server application that uses an LDAP directory and LTPA for security. Sample code and configuration examples are included to demonstrate connecting to either a session EJB or to a servlet.

Share:

Wes Wardell (wardell@ca.ibm.com), Software Developer, IBM

Wes Wardell is a software developer in the IBM Software Group Solution Test team at the IBM Toronto Lab. Wes earned a Bachelor of Science degree in Computing and Computer Electronics from Wilfrid Laurier University, Waterloo, Ontario in 1996.



26 January 2005

Introduction

Consider a situation where you have an existing Web application, deployed on IBM® WebSphere® Application Server, which enables employees to submit requests for scheduling vacations. The corporate directory is used to secure the application. Suppose you want to deploy a portal or workplace environment that pulls together various types of content of interest to employees, including the ability to make vacation requests. Rather than redeploying the human resources (HR) application that handles vacation requests, you can write portlets which make use of the application remotely. Figure 1 illustrates a simple view of integrating this employee portal with the existing HR application.

Figure 1. Adding a portal to the architecture
Adding a portal to the architecture

In order to integrate an existing Web application, which has been set up with security, into a portal, you have to determine how your portlets will connect to the Web application and handle user authentication. By writing a portlet service to connect to the Web application you hide the details of this integration from the individual portlets. You also allow yourself to change the way that you use the Web application in the future without updating the code in all of the portlets.

This article illustrates how a portlet service can be used to connect to the session beans of a remote Web application in a secure environment. It will then describe how to update the portlet service to connect to servlets instead, without requiring any changes to the portlets that use the portlet service.

To get the most out of this article, you should be familiar with enterprise application security concepts, portlet development, and portlet service development.


Web application configuration

For the example in this article a number of assumptions are made about the remote Web application:

  1. It is on IBM WebSphere Application Server V5.1.
  2. Security is enabled.
  3. The Web application and portal both use the same LDAP directory and LTPA mechanism for authentication.
  4. The business logic of the application is in or behind a layer of session beans; for example, the Web application makes use of the Session Facade pattern.
  5. Access to the session beans is restricted. Individual portal users do not have access, but a single LDAP user id can be used to access all the session beans.

What is a portlet service?

A PortletService interface is provided with WebSphere Portal which you can extend and implement to add your own portlet service to the portal. The CredentialVaultService is an example of a portlet service that is provided by WebSphere Portal. Refer to the product documentation for details on portlet services.

The portlet service provides the business methods that your portlets need to call in order to use the business functionality provided by the remote Web application. These connect to the session beans of your remote Web application. The implementation of these methods depends on the requirements of your application.


Writing the portlet service

Because the portal users do not have access to the remote Web application, the portlet service needs to authenticate with a different user id. This user id and password are obtained from the WebSphere Portal credential vault, shown in Figure 2. For the portlet service to get these from the credential vault, it needs the PortletRequest and PortletConfig objects from the portlet. Therefore, you add methods to the portlet service which the portlets set prior to calling any of the business methods.

Figure 2. Setting up the shared credential vault slot
Setting up the shared credential vault slot

Within the business methods you need to programmatically login with the user id that has access to the session beans, and then call the methods on the session beans within the context of the authenticated Subject. The session beans only see the user that you just authenticated--not the user that is logged into the portal.

For the example of submitting a vacation request, create a portlet service named HRPortletService. The VacationRequest portlet and HRPortletService perform the task of submitting the request roughly as follows:

  1. VacationRequest gets an instance of HRPortletService.
  2. VacationRequest supplies HRPortletServicewith the current PortletConfig and PortletRequest objects using setter methods. These are needed later by the portlet service to successfully retrieve the shared credential.
  3. VacationRequest calls the HRPortletServicemethod for submitting a vacation request, submitRequest, passing in the value object expected by the remote HR application. Within submitRequest, the following occurs:
    • HRPortletService gets the login Subject using the PortletConfig and PortletRequest objects it was given earlier.
    • HRPortletService looks up the HR application session bean using the login Subject
    • HRPortletService calls the submitRequest business method of the session bean within the Subject's security context.

Before looking at the HRPortletService.submitRequest method, let's look at the methods that it needs to call. The two methods shown below are used together to get the login Subject. The first logs into the Web application and returns the Subject object, which is used later to make calls to the session bean. The second method is called by the first method to obtain the credential from the credential vault that is used to login to the remote Web application hosted on the fictional mywebapp.ibm.com server. The code that follows was tested in WebSphere Portal 5.0.2, and it is also supported in WebSphere Portal V5.1.

Listing 1. Logging in to the remote Web application
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;
}

After logging in, submitRequest needs to look up the remote home for the session bean. The method below performs this task. It makes use of the Subject object obtained from the getLoginSubject method above. This Subject object represents the authenticated user that has access to the session bean. The remote session bean is returned to be used to call the bean's business methods.

Listing 2. Looking up the remote session 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;
}

You can use the above methods in all your portlet service business methods to make calls to the remote Web application. This call is illustrated below in the implementation of the submitRequest method, which submits an HRRequest object to the Web application for processing:

Listing 3. Submitting the request to the Web application
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();
    }
  }
}

The code uses a PrivilegedAction in the getSessionEJB method to look up the session bean's home, and again in the submitRequest method for calling the session bean's submitRequest method. All interaction with the session bean must be within the context of the Subject object. The bean lookup and submitRequest method call were not combined so that the session bean lookup code would not need to be duplicated each time you need to call a different method on the session bean.


Using the portlet service

As outlined, to make use of this portlet service you first need to get an instance of the service. Then, you pass the PortletConfig and PortletRequest objects to the service, which needs them for getting the credential out of the credential vault. Finally, you call the business method on the portlet service. An example code fragment from the VacationRequest portlet which makes use of the submitRequest method is included here:

Listing 4. Calling the portlet service from a 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);

Changing the portlet service to use servlets

In the previous section, you used a portlet service to connect to the session beans of a remote Web application. This section describes the changes necessary to the portlet service to call servlets instead. Because all the code for accessing the remote Web application was placed in a portlet service, only the portlet service needs to be updated. The portlets are unaffected.

Changes to the assumptions for the Web application

For this section, the following assumptions are made about the Web application, replacing numbers four and five in Web application configuration.

  1. The business logic of the application is in, or behind, a layer of servlets.
  2. Access to the servlets is restricted to authenticated users. The individual authenticated portal users have access to the servlets.

Changes to the portlet service

The getLoginSubject and getSessionEJB methods implemented above are no longer necessary. Use an HttpURLConnection to call the remote Web application's servlets. For the objects that you need to pass to and from the Web application, serialize them to the connection's input and output streams. To gain access to the Web application, retrieve the LTPA token from the PortletRequest object and add it to the HttpURLConnection to the Web application. Here's the code for retrieving the token:

Listing 5. Retrieving the LTPA token to access the Web application
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;
}

Here is the submitRequest method that you saw earlier, updated to connect to a servlet:

Listing 6. Connecting to a remote 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();
    }
  }
}

Conclusion

In this article you've seen how a portlet service can be used to encapsulate the integration of your portal with a remote Web application. Within this portlet service, you have used two different implementations for connecting to a remote Web application in a secure environment.

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into WebSphere on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere
ArticleID=33338
ArticleTitle=Accessing secure remote Web applications using a portlet service
publish-date=01262005