Skip to content
April 10, 2012 / Shailendra Singh

Using CAS with WebSphere Portal 6.1 for SSO

This post does not explain how to configure WebSphere portal 6.1 with CAS server for SSO as there is detailed explanation given for same at this page. The purpose of this post is to provide some additional details.

One can achieve SSO between two or more WebSphere portal servers without using any external SSO mechanism provided following conditions are satisfied –

  • SSO is enabled for all the servers and all servers are in same (SSO) domain.
  • All servers are having same security configuration.
  • LTPA keys are same in all the servers.

In this case, if you log in to one portal server and try to access a protected resource in any of other portal servers in SSO domain, you will observe that none of the other servers will prompt for username and password. This happens because of a built-in SSO mechanism called LTPA tokens in WebSphere application/portal server.  So then why do we need an external SSO mechanism? The reason is that LTPA is specific to IBM family of products and can not be used with other servers. For example, if you want to achieve SSO between an application running in a tomcat server and a WebSphere portal, you can not use LTPA mechanism for SSO.

What exactly happens when an unauthenticated client try to access a protected portal resource?

Consider the above diagram. When WebSphere app/portal server receives a request for a protected resource from an unauthenticated client, request will follow route 2 as show in above diagram. In this case, WebSphere server will invoke TAI if there is a TAI configured. If TAI asserts user identity for this request, WebSphere server will invoke login modules to create Subject for the client.

Once the client is authenticated successfully, all the requests to server will follow route 1.

Assuming that WebSphere server is having a CAS TAI configured, this is what TAI will do for following possible states of an unauthenticated request –

1) There is neither a ticket parameter (this is appended by CAS server to the redirect url after authenticating the client) in the request url nor there is a LTPA token in request cookies.

This situation occurs when an unauthenticated client sends first request for a protected resource to a portal server. In this case portal will redirect user to either login portlet (this is default configuration) or to a login screen if portal has been configured for a login screen. However if portal is using CAS for SSO, it should redirect user to CAS server login screen. Where should one put the code for redirecting user to CAS server login screen? Normally this code is put either in theme assigned to login portlet page or in the login screen if portal has been configured for login screen. But it is also possible to do this redirection in TAI itself. To achieve this, you will need to do some changes in CasTAIHelper and CASTAI511 classes which are part of CAS client for WebSphere server package available from CAS website. These changes are as following –

CasTAIHelper


protected static String CAS_LOGIN_URL;

public static int initialize(Properties props) {
    ......
    CAS_LOGIN_URL = props.getProperty("CAS_LOGIN_URL");
    ......
    if (CAS_LOGIN_URL == null) {
       System.err.println("CAS_LOGIN_URL property information missing");
       return -1;
    }
}

public static boolean isTargetInterceptor(HttpServletRequest request) {
    if(null != request.getParameter("ticket") || null == request.getUserPrincipal()) {
        return true;
    }
    return false;
}

I have added a property called CAS_LOGIN_URL which stores the url of CAS login screen. This property should be efined as a custom property for CAS TAI in WebSphere server admin console. Also isTargetInterceptor() method is checking whether there is any user principal available in request or not. If its not there, then this method will return true which will result in WebSphere server invoking negotiateValidateandEstablishTrust() on TAI class (see below code).

CASTAI511

public TAIResult negotiateValidateandEstablishTrust(HttpServletRequest request, HttpServletResponse response) throws WebTrustAssociationFailedException {
         TAIResult taiResult = null;
         if (null != request.getParameter("ticket")) {
             ServiceTicketValidator sv = new ServiceTicketValidator();
             String ticket;
             try {
                 ticket = CasTAIHelper.getTicket(request);
             } catch (ServiceTicketLostException e) {
                 throw new WebTrustAssociationFailedException(e.getMessage());
             }
             String uid;
             try {
                 uid = CasTAIHelper.negotiateValidateandEstablishTrust(ticket, request, sv);
             } catch (CasException e1) {
                 throw new WebTrustAssociationFailedException(e1.getMessage());
             }
             System.out.println("Principal: '" + uid + "'");
             taiResult = TAIResult.create(200, uid);
         } else {
              try {
                 StringBuffer reqURL = request.getRequestURL();
                 String queryString = request.getQueryString();
                 if(null != queryString && !queryString.equals("")){
                      reqURL.append("?");
                      reqURL.append(request.getQueryString());
                  }
                 response.sendRedirect(CasTAIHelper.CAS_LOGIN_URL + "?service=" + reqURL.toString());
                 //This is important. Without this redirect will not work.
                 taiResult = TAIResult.create(HttpServletResponse.SC_MOVED_TEMPORARILY);
              } catch (Exception ex) {
                  //handle exception
              }
         }

         return taiResult;
}

In a normal web application we use response.sendRedirect(url) to redirect client to a different page. However doing this alone in TAI is not sufficient to redirect user to some other page. You need to create an instance of TAIResult by calling create(int status) method of this class. Value of status code should be 302 which is also the value of HttpServletResponse.SC_MOVED_TEMPORARILY constant.

2) Request url is having ticket parameter but there is no LTPA cookie in the request

This occurs when CAS server redirects client to portal server after successful authentication. In this case TAI will assert user identity to WebSphere portal server. Behavior of TAI in this case has been already explained in many articles so it is not covered here.

3) Request is having LTPA cookie but there is no ticket parameter in the url.

This will occur when you log in to a portal server in a SSO domain and then try to access a protected resource in some other server of same SSO domain. In this case you have to decide whether you want to use TAI for identity assertion or by pass it to let the LTPA mechanism log user in the portal server.

As i mentioned in the beginning of this post that its possible to achieve SSO among a group of portal servers without using any TAI. So how does SSO works in this case? Consider the diagram shown above. When a user log in to a portal server, it will create an LTPA token after successful authentication and sent this token as a cookie in the response. So when you hit a protected url of some other server in same SSO domain, client will send this LTPA cookie to that server. During the authentication process, WebSphere server will invoke various login modules (a concept of JAAS) in a sequence in which these login modules have been configured. One of the these modules is LTPA login module (com.ibm.ws.security.server.lm.ltpaLoginModule) which uses this token to log user in to the portal. If you are interested in more details then use following trace string to see what is happening in the background –

com.ibm.ws.security.ltpa.*=all:com.ibm.ws.security.server.lm.ltpaLoginModule=all

If you are using CAS TAI, then what should you do in this case?  According to me negotiateValidateandEstablishTrust() method of CAS TAI should be invoked only if there is no valid LTPA token cookie available in the request for current server. This means code of  isTargetInterceptor() method of CasTAIHelper class should be as following –

public static boolean isTargetInterceptor(HttpServletRequest request) {
    if(null != request.getParameter("ticket")) {
        return true;
    }

    //pseudo code
    check if LTPA cookie is available
    Yes
       Validate LTPA cookie for this server
          Validation is successful
              return false (Now LTPA login module will log user in to the portal)
          Validation failed
              return true (this will redirect user to CAS login screen)
    No
      return true (this will redirect user to CAS login screen)

    return false;
}

As there is no public API/SPI available in WebSphere portal for validating a LTPA token, validating LTPA
token in TAI does not look feasible. But since this is already happening in LTPA login module, it should be possible to validate LTPA token in TAI. I will update this post if i find a solution for validating LTPA token in TAI.

Ideally there should be a component in WebSphere server which is executed before TAI and can be activated/deactivated through configuration. In active state, it should check whether there is any LTPA token in the request or not. If a valid LTPA token is available in the request for this server, TAI should not be invoked. In case this component is deactivated, processing should happen normally.

Importing CAS certificate in WebSphere server trust store
As TAI communicates with CAS validation service over https, you will need to import either CAS server or CA (Certificate Authority) certificate in WebSphere portal server trust store for successful communication over https with CAS validation service.  Instead of doing it through commands using jdk keytool utility, you can do it easily using WebSphere server admin console. Here are the steps for importing the certificate in WebSphere trust store –

a. Log on to admin console.

b. Go to Security –> SSL certificate and key management section.

c. Click on Key stores and certificates link.

d. Click on NodeDefaultTrustStore link.

e. Click on Signer certificates link.

f. Now click on Retrive from port button.

g. You have two choices at this stage –

1. If you want to import CAS server certificate in WebSphere trust store, then enter CAS server host name and https port details on this page and click on Retrieve signer information button. This will show CAS server certificate details on this page. Now enter a name for alias and click on OK button to import this certificate in trust store.

2. If you want to import CA certificate in trust store, first click on Signer certificates link in breadcrumb and then on Add button. Make sure you have copied CA certificate to <portal_installation_root>\wp_profile\etc folder on portal server and it is either in Base64-encoded format or in Binary DER format. Specify an alias for this certificate and click on first Apply  and then on OK button to import the CA certificate.

So which of the above options should one choose? Generally a server certificate issued by a CA will be valid for one or two years only while most ( or probably all) of the CA certificates are valid for longer time (15 years or more). So if you are importing CAS server certificate then you will need to repeat this process every one or two years for all the portal machines (and other servers) which are using this CAS server for SSO. On the other hand importing CA certificate in trust stores of CAS client servers will make sure that you don’t repeat the import process for every CAS server client every time when CAS server certificate is expired.

August 9, 2013 / Shailendra Singh

How to access jcr repository in websphere portal

Create a servlet and add following code to doGet() method –

WebSphere Portal 7

import com.presence.connect.wmmcomms.UserManagementUtils;

UserManagementUtils.authenticate(user_id, pwd);

Repository repo = RepositoryServiceUtils.getJCRRepository();

String[] workspaces = repo.login().getWorkspaces();

for(String workspace : workspaces) {
     System.out.println("workspace name : " + workspace);
}

WebSphere Portal 8

import com.presence.connect.wmmcomms.AuthenticationUtils;

import com.ibm.icm.jcr.RepositoryFactory;
import com.ibm.icm.jcr.Repository;

import javax.jcr.Credentials;
import javax.jcr.Ticket;

AuthenticationUtils.authenticate(user_id, pwd, request, response);
			 
Repository repository = RepositoryFactory.getRepository();
Credentials credentials = repository.createSystemCredentials();
Ticket session = repository.login(credentials);
		    
for(String workspace : session.getWorkspaces()) {
     write(response, "Workspace : " + workspace);
}