WSO2 Identity Server + Claim aware proxy services with ESB

WSO2 Identity Server 2.0 is a free and open source identity and entitlement management server, available to download from here...

Let me first briefly explain the the use case.

1. A proxy service created in WSO2 ESB requires a security token issued by the WSO2 Identity Server for authentication.

2.At the same time, the security policy in the proxy service, specifies - it requires a given set of claim values with the security token.

<sp:RequestSecurityTokenTemplate xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
<t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>
<t:KeyType>http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey</t:KeyType>
<t:KeySize>256</t:KeySize>
<t:Claims Dialect="http://wso2.org/claims" xmlns:ic="http://schemas.xmlsoap.org/ws/2005/05/identity">
<ic:ClaimType Uri="http://wso2.org/claims/givenname" />
</t:Claims>
</sp:RequestSecurityTokenTemplate>
3. Identity Server is connected to an LDAP user store and all user attributes reside there.

4. User needs to authenticate to Identity Server, first and obtain the Security Token with claims.

5. Then user sends it to the ESB proxy service.

Let's see how we can achieve this in a step by step manner.

Step -1

Set up LDAP server. All the detail required explained here.

Step -2
Configure WSO2 Identity Server to talk to the LDAP Server and do the claim mapping. All the detail required explained here.

Step -3
Configure WSO2 Identity Server STS. The first part of this explains all the steps you need.

Before that you need to get the public certificate [wso2carbon.cert.cer] of the ESB from here.

Login to IS as an admin, and import the above certificate to wso2carbon.jks from Configure/Key Stores. [Yes - WSO2 ESB and IS use two different key stores - that's why we need to perform this step here]

Make sure, in the first step - while adding the trusted 'Endpoint Address' - select 'wso2carbon.cert' as the 'Certificate Alias'.

Also - while applying the security policy to the STS - make sure you select the group where ldap users[ldapuserole] belong to.

Step -4

Create and apply security for the proxy service. You need to follow exact steps defined here [first part] - but need to get the service security policy[service.policy.xml] from here, when you are overriding.

That's it - now let's write the client code.

You may also need to download Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files 5.0 from here and copy the two jar files from the extracted jce directory (local_policy.jar and US_export_policy.jar) to $JAVA_HOME/jre/lib/security. For JDK 6 it's here.

You need to do above, both at the client side as well as the server side - if you are running on two machines.

Also while running the client code, make sure bouncycastle jar is in the classpath.

You can download both sts.policy.xml and service.policy.xml files from here.

package org.apache.ws.axis2;

import java.util.Properties;

import javax.xml.namespace.QName;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.neethi.Policy;
import org.apache.neethi.PolicyEngine;
import org.apache.rahas.RahasConstants;
import org.apache.rahas.Token;
import org.apache.rahas.TokenStorage;
import org.apache.rahas.TrustUtil;
import org.apache.rahas.client.STSClient;
import org.apache.rampart.RampartMessageData;
import org.apache.rampart.policy.model.RampartConfig;
import org.apache.rampart.policy.model.CryptoConfig;
import org.apache.ws.secpolicy.Constants;
import org.opensaml.XML;

public class IdentitySTSClient {

/**
* @param args
*/

final static String RELYING_PARTY_SERVICE_EPR = "http://localhost:8280/services/echo";
final static String ESB_TRANS_EPR = "http://localhost:8280/services/test";
final static String STS_EPR = "https://localhost:9443/services/wso2carbon-sts";

/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
ConfigurationContext confContext = null;
Policy stsPolicy = null;
STSClient stsClient = null;
Policy servicePolicy = null;
Token responseToken = null;
String trustStore = null;

// You need to import the Identity Server, public certificate to this key store.
// By default it's there - if you use wso2carbon.jks from [ESB_HOME]\resources\security
trustStore = "wso2carbon.jks";
// We are accessing STS over HTTPS - so need to set trustStore parameters.
System.setProperty("javax.net.ssl.trustStore", trustStore);
System.setProperty("javax.net.ssl.trustStorePassword", "wso2carbon");

// Create configuration context - you will have Rampart module engaged in the
// client.axis2.xml
confContext = ConfigurationContextFactory.createConfigurationContextFromFileSystem("repo",
"repo/conf/client.axis2.xml");

stsClient = new STSClient(confContext);

stsClient.setRstTemplate(getRSTTemplate());
stsClient.setAction(RahasConstants.WST_NS_05_02 + RahasConstants.RST_ACTION_SCT);

// This is the security policy we applied to Identity Server STS.
// You can see it by https://[IDENTITY_SERVER]/services/wso2carbon-sts?wsdl
stsPolicy = loadSTSPolicy("sts.policy.xml");

// This is the security of the relying party web service.
// This policy will accept a security token issued from Identity Server STS
servicePolicy = loadServicePolicy("service.policy.xml");

responseToken = stsClient.requestSecurityToken(servicePolicy, STS_EPR, stsPolicy,
RELYING_PARTY_SERVICE_EPR);

System.out.println(responseToken.getToken());

TokenStorage store = TrustUtil.getTokenStore(confContext);
store.add(responseToken);

ServiceClient client = new ServiceClient(confContext, null);
Options options = new Options();
options.setAction("urn:echoString");
options.setTo(new EndpointReference(RELYING_PARTY_SERVICE_EPR));
options.setProperty(org.apache.axis2.Constants.Configuration.TRANSPORT_URL, ESB_TRANS_EPR);
options.setProperty(RampartMessageData.KEY_RAMPART_POLICY, servicePolicy);
options.setProperty(RampartMessageData.KEY_CUSTOM_ISSUED_TOKEN, responseToken.getId());
client.setOptions(options);

client.engageModule("addressing");
client.engageModule("rampart");

OMElement response = client.sendReceive(getPayload("Hello world1"));
System.out.println("Response : " + response);
}

private static Policy loadSTSPolicy(String xmlPath) throws Exception {
StAXOMBuilder builder = null;
Policy policy = null;
RampartConfig rc = null;

builder = new StAXOMBuilder(xmlPath);
policy = PolicyEngine.getPolicy(builder.getDocumentElement());
rc = new RampartConfig();
// User from the LDAP user store
rc.setUser("prabath");
// You need to have password call-back class to provide the user password
rc.setPwCbClass(PWCBHandler.class.getName());
policy.addAssertion(rc);
return policy;
}

private static Policy loadServicePolicy(String xmlPath) throws Exception {
StAXOMBuilder builder = null;
Policy policy = null;
RampartConfig rc = null;
CryptoConfig sigCryptoConfig = null;
String keystore = null;
Properties merlinProp = null;
CryptoConfig encrCryptoConfig = null;

builder = new StAXOMBuilder(xmlPath);
policy = PolicyEngine.getPolicy(builder.getDocumentElement());
rc = new RampartConfig();
rc.setUser("wso2carbon");
rc.setEncryptionUser("wso2carbon");
// You need to have password call-back class to provide the user password
rc.setPwCbClass(PWCBHandler.class.getName());

keystore = "wso2carbon.jks";
merlinProp = new Properties();
merlinProp.put("org.apache.ws.security.crypto.merlin.keystore.type", "JKS");
merlinProp.put("org.apache.ws.security.crypto.merlin.file", keystore);
merlinProp.put("org.apache.ws.security.crypto.merlin.keystore.password", "wso2carbon");

sigCryptoConfig = new CryptoConfig();
sigCryptoConfig.setProvider("org.apache.ws.security.components.crypto.Merlin");
sigCryptoConfig.setProp(merlinProp);

encrCryptoConfig = new CryptoConfig();
encrCryptoConfig.setProvider("org.apache.ws.security.components.crypto.Merlin");
encrCryptoConfig.setProp(merlinProp);

rc.setSigCryptoConfig(sigCryptoConfig);
rc.setEncrCryptoConfig(encrCryptoConfig);

policy.addAssertion(rc);
return policy;
}

private static OMElement getRSTTemplate() throws Exception {
OMFactory fac = OMAbstractFactory.getOMFactory();
OMElement element = null;
OMElement elem = fac.createOMElement(Constants.RST_TEMPLATE);
TrustUtil.createTokenTypeElement(RahasConstants.VERSION_05_02, elem).setText(XML.SAML_NS);
TrustUtil.createKeyTypeElement(RahasConstants.VERSION_05_02, elem,
RahasConstants.KEY_TYPE_SYMM_KEY);
TrustUtil.createKeySizeElement(RahasConstants.VERSION_05_02, elem, 256);
element = TrustUtil.createClaims(RahasConstants.VERSION_05_02, elem,"http://wso2.org");
addClaimType(element,"http://wso2.org/claims/givenname");
return elem;
}

private static void addClaimType(OMElement parent,String uri) {
OMElement element = null;
element = parent.getOMFactory().createOMElement(new QName("http://schemas.xmlsoap.org/ws/2005/05/identity", "ClaimType", "wsid"),
parent);
element.addAttribute( parent.getOMFactory().createOMAttribute("Uri",null,uri));
}

private static OMElement getPayload(String value) {
OMFactory factory = null;
OMNamespace ns = null;
OMElement elem = null;
OMElement childElem = null;

factory = OMAbstractFactory.getOMFactory();
ns = factory.createOMNamespace("http://echo.services.core.carbon.wso2.org", "ns1");
elem = factory.createOMElement("echoString", ns);
childElem = factory.createOMElement("in", null);
childElem.setText(value);
elem.addChild(childElem);
return elem;
}
}

package org.apache.ws.axis2;

import org.apache.ws.security.WSPasswordCallback;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;

public class PWCBHandler implements CallbackHandler {

public void handle(Callback[] callbacks) throws UnsupportedCallbackException {
WSPasswordCallback cb = (WSPasswordCallback) callbacks[0];
if ("prabath".equals(cb.getIdentifier())) {
cb.setPassword("prabath");
} else {
cb.setPassword("wso2carbon");
}
}
}