Friday, June 5, 2009

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");
}
}
}

5 comments:

ami said...

Prabath,

How to create a client stub for the identity server. I mean, if for eg to create a client stub for opensso there is something called OpenSSO Enterprise Client SDK. Please help in this regard.

One more question is can the Identity server be deployed on Tomcat. if yes can you provide a small tutorial.

Thanks,
Amith

ami said...

Hi,



I am a developer trying out wso2 identity server. I had few doubts where I was stuck. Please help to come out of it.



I am trying to create a client sdk to communicate with the wso2 identity server. Is there any tutorial for doing the same?
Is it necessary to use wso2 Governance for creating the client?
Can I extend the Identity server for other Authentication methods? Does the Identity server follow JAAS implementation? If no can I know the details on the implementation?
Is the Wso2 ESB necessary for using the Identity server?


Please post the answers.



Thanks,

Amith

ami said...

hi prabath,

How to crate clint for wso2 identity server

zhangrui said...
This comment has been removed by the author.
Sweetxml said...

Hi Prabath Siriwardena
I found your blog via your userprofile at StackOverflow, where you commented on the subject I'm looking for an answer to.

I've been away from Axis2 for some time, but now I need to access an ADFS STS, initialized by an X509 Certificate and hereafter to get the token 'augmented' with claims. I don't have any metadata/policy-files, but I have a .NET client running where it's done like

var factory = new WSTrustChannelFactory(new CertificateWSTrustBinding(SecurityMode.TransportWithMessageCredential), issuerAddress);
factory.TrustVersion = TrustVersion.WSTrust13;
factory.Credentials.ClientCertificate.Certificate = clientCertificate;
var rst = new RequestSecurityToken
{
RequestType = WSTrust13Constants.RequestTypes.Issue,
AppliesTo = new EndpointAddress(appliesTo),
KeyType = WSTrust13Constants.KeyTypes.Symmetric,
ReplyTo = replyTo
};

and then I get the securitytoken issued by the channel I create.


and then a second request using the security token I just got.

Could you please show me how to do that with Axis2/Rahas? There's not that much info/examples on that out there, and I would prefer no to have to change framework (like Metro).

Thx in advance.

Best Regards
Brian