Thursday, January 20, 2011

Custom Identity Assertion for OAM-OES SSO integration

Introduction
OAM and OES both provide mechanisms to protect Web Applications. Some customers have expressed the need to achieve SSO between these two Access Management solutions. This article intends to describe how one would implement such integration.
Overview
The solution mostly involves custom components from the OES side so we will list the ones required from OAM which are mostly just configurable components provided out of the box by OAM. So from OAM’s stand point all is needed is an Access Gate which will be tied to the custom Identity Asserter that will be built for OES. To accomplish this the Access Server SDK must be installed on the server where OES is installed. Locate the Access Server SDK distribution coming with OAM’s installation packages and install it locally on the OES server. Then configure an Access Gate in OAM’s Access System Console and then run the ConfigureAccessGate command line tool from the Access Server SDK access/oblix/ConfigureAccessGate/ directory. Make note of the installation directory of this access gate because you will have to provide that somehow to your implementation of the SSPI Identity Assertion Provider that will consume the SSO token from OAM, details following. For details on how to configure OAM’s Access Gate and install and configure the Access Server SDK you can refer to the OAM’s product documentation.

So far what you have done is to achieve connectivity between the OES server and the Access Server from OAM. This is needed to validate the ObSSOCookie coming from OAM which will be passed in the request to the WS-SSM of OES as an Identity Assertion with a Token Type set as ObSSOCookie, we will cover this in more detail in sections to come within this article.

Now we are ready to discuss the OES side components. First and foremost, we need an Identity Assertion Provider which is a SSPI implementation of an Identity Assertion provider. The code is presented below (Thanks to Chris Johnson and Josh Bregman for providing the code for this solution):

package com.oracle.access;
import java.util.Arrays;
import java.util.Enumeration;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.AppConfigurationEntry;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import weblogic.management.security.ProviderMBean;
import weblogic.security.service.ContextHandler;
import weblogic.security.spi.AuthenticationProviderV2;
import weblogic.security.spi.IdentityAsserterV2;
import weblogic.security.spi.IdentityAssertionException;
import weblogic.security.spi.PrincipalValidator;
import weblogic.security.spi.SecurityServices;
import com.oblix.access.*;

public class OAMIdentityAsserterProviderImpl implements AuthenticationProviderV2 {
/**
* The attribute of the DN used to identify the
* user in WebLogic. example: cn
*/
private String userAttribute;

public AppConfigurationEntry getAssertionModuleConfiguration() {
// TODO Auto-generated method stub
return null;
}

public IdentityAsserterV2 getIdentityAsserter() {
// TODO Auto-generated method stub
return new Foo(userAttribute);
}

public AppConfigurationEntry getLoginModuleConfiguration() {
// TODO Auto-generated method stub
return null;
}

public PrincipalValidator getPrincipalValidator() {
// TODO Auto-generated method stub
return null;
}

public String getDescription() {
// TODO Auto-generated method stub
return null;
}

public void initialize(ProviderMBean mbean, SecurityServices arg1) {
OAMIdentityAsserterMBean config = (OAMIdentityAsserterMBean)mbean;
String installDir = config.getAccessGateSDKInstallDir();
System.out.println(System.getProperty("java.library.path"));
System.out.println("Initalizing connection to OAM using "+installDir+" updated 3.");
try {
ObConfig.initialize(installDir);
System.out.println("Initialized");
} catch (Throwable t) {
t.printStackTrace();
throw new RuntimeException(t);
}
this.userAttribute = config.getNameAttribute();
}

public CallbackHandler assertIdentity(String arg0, Object arg1, ContextHandler ctxHandler) throws IdentityAssertionException {
// TODO Auto-generated method stub
System.out.println("Asserting ID for "+arg0+" value="+arg1);
String [] names = ctxHandler.getNames();
System.out.println("Attributes: "+Arrays.asList(names));
HttpServletRequest req = (HttpServletRequest)ctxHandler.getValue("HttpServletRequest");
if (req!=null) {
Enumeration e = req.getHeaderNames();
while (e.hasMoreElements()) {
String header = (String)e.nextElement();
String value = req.getHeader(header);
System.out.println(header+"=>"+value);
}
}
try {
String token = null;
if (arg1 instanceof byte []) {
token = new String((byte[])arg1);
} else if (arg1 instanceof String) {
token = (String)arg1;
} else {
System.out.println("Unknown token "+arg1.getClass().getName());
}
System.out.println("Token="+token);
if (token.equals("loggedoutcontinue")) {
HttpServletResponse resp = (HttpServletResponse)ctxHandler.getValue("HttpServletResponse");
System.out.println("Sending redirect to "+req.getRequestURI()); resp.sendRedirect(req.getRequestURI());
resp.getWriter().close();
throw new IdentityAssertionException("No Identity. Redirecting");
}
ObUserSession session = new ObUserSession(token);
String userName = session.getUserIdentity();
System.out.println(userName);
return new ObSSOTokenCallbackHandler(session,userAttribute);
} catch (Exception e) {
e.printStackTrace();
throw new IdentityAssertionException(e.getMessage());
}
}

public void shutdown() {
// TODO Auto-generated method stub
}
}

This code works as follows: The first thing it does is to retrieve the configuration directory for the Access Gate configured as described in previous sections of this post when the Access Server SDK was installed and an Access Gate was configured. When you create a security provider in WebLogic, there is a utility that creates the provider as a Managed Bean (MBean creation utility). This creates the provider with a set of properties that can be configured via the WebLogic System Console to supply configuration values need by providers. This is how it retrieves the installation directory of the Access Server SDK to retrieve the configuration to connect to OAM’s Access Server. The rest is just processing the ObSSOCookie. In order to consume the cookie as part of this implementation there is also a CallbackHandler that retrieves the ObSSOCookie from the HttpServletRequest. The code for this handler is shown below:

package com.oracle.access;
import java.io.IOException;
import java.util.StringTokenizer;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import com.oblix.access.ObUserSession;

public class ObSSOTokenCallbackHandler implements CallbackHandler {

private ObUserSession session;
private String userNameAttribute;

public ObSSOTokenCallbackHandler(ObUserSession session, String userNameAttribute) {
super();
this.session = session;
this.userNameAttribute = userNameAttribute;
}

public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
// TODO Auto-generated method stub
System.out.println("There are "+callbacks.length);
for (int i=0; i Callback c = callbacks[i]; i++) {
if (c instanceof NameCallback) {
try {
System.out.println("Handling Name Callback for "+session.getUserIdentity()+" "+userNameAttribute);
NameCallback nc = (NameCallback)c;
nc.setName(getName());
} catch (Exception e) {
e.printStackTrace();
}
} else {
System.out.println("Can't handle callback "+c.getClass().getName());
}
}
}

private String getName() throws Exception {
String dn = session.getUserIdentity();
StringTokenizer parser = new StringTokenizer(dn,"=,");
while (parser.hasMoreTokens()) {
String token = parser.nextToken();
if (token.equals(userNameAttribute)) {
return parser.nextToken();
}
}
throw new Exception("Couldn't find "+userNameAttribute+" in "+dn);
}
}


The Class above has the purpose of extracting the ObSSOCookie from the HttpRequest and creating an ObSession object with it. Then it retrieves the identity within the cookie and parses the resulting DN to extract the user name which will be used as the principal for the authenticated subject. Now, once all these components are in place we need to tell OES when to call this provider, which is anytime it sees a Token Type of ObSSOCookie. OES represents Security Tokens via implementations of the CredentialHolder interface. We need to create an implementation of the CredentialHolder interface as follows:

package com.bea.security.ssmws.credentials;
public class OAMSSOCookieIndentityHolderImpl implements CredentialHolder
{
private String m_cookie;
public static final String m_Type = "ObSSOCookie"; // This should match the Token Type expected by the OAMIdentityAsserter configured
public void setCookie(String cookie)
{
this.m_cookie = cookie;
}

public String getCookie()
{
return this.m_cookie;
}

public Object getObject()
{
return getCookie();
}
public void setObject(Object cred)
{
setCookie((String)cred);
}

public String getType()
{
return this.m_Type;
}

public String getAsString()
{
return this.m_cookie;
}
}


As you can see in the code above this class has the only purpose of representing the ObSSOCookie as an acceptable token type. The next step is to configure the castor.xml files to allow OES to successfully de-serialize the incoming token from the xml request submitted to the WS-SSM. This is accomplished by modifying the following files:
Open the file C:\bea\1032\ales32-ssm\webservice-ssm\lib\com\bea\security\ssmws\soap\.castor.xml and modify it as follows:
<class name="com.bea.security.ssmws.credentials.OAMSSOCookieIndentityHolderImpl ">
<map-to cst:xml="ObSSOCookie" />
<field name="cookie" type="java.lang.String" >
<bind-xml node="text"/>
</field>
</class>


Open the file C:\bea\1032\ales32-ssm\webservice-ssm\lib\com\bea\security\ssmws\credentials\.castor.xml and modify it as follows:
<class name="com.bea.security.ssmws.credentials. OAMSSOCookieIndentityHolderImpl ">
<map-to cst:xml="ObSSOCookie" cst:ns-uri="http://security.bea.com/ssmws/ssm-soap-types-1.0.xsd" />
<field name="cookie" type="java.lang.String" >
<bind-xml node="text"/>
</field>
</class>

The next step is to test this. So when making a Web Service method call to the WS-SSM the SOAP message should contain an identity assertion named as ObSSOCookie, see XML fragment below:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<isAccessAllowed xmlns="http://security.bea.com/ssmws/ssm-soap-types-1.0.xsd">
<IdentityAssertion>
<ObSSOCookie>34EFB3201BCA31F21A3E320A21B31D19</ ObSSOCookie >
</IdentityAssertion>
<RuntimeResource>
<ResourceString>app/claim </ResourceString>
<AuthorityName>ARME_RESOURCE_AUTHORITY </AuthorityName>
</RuntimeResource>

</soap:Body>

</soap:Envelope>


The tag name needs to reflect exactly the name of the token configured in the Identity Assertion Provider of the Web Logic Security configuration and also the configuration changes made in castor.xml files.


No comments:

Post a Comment

Note: Only a member of this blog may post a comment.