Friday, December 2, 2011

Invoking Secured Web Services with WSO2 Mashup Server

WSO2 Mashup Server is based on WSO2 Carbon Platform which is then built on top of Apache Axis2 engine. Apache Rampart is the security module of Axis2. It secures SOAP messages according to specifications in the WS-Security stack. When you create a complex mashup which invokes many enterprise level web services, then WS* security will be a key concern.

Using WSRequest hostobject of the Mashup Server, you can easily access web services secured with many security scenarios. You can either dynamically pick the policy from the service wsdl or set it manually.

In both cases, you can set all the rampart related configurations using a simple json object notation. In this post, I have described how you can use WSO2 Mashup Server to invoke secured services from a mashup.

All the samples described here can be deployed on WSO2 Mashup Server 2.3.3 or above which can be downloaded from here.

Structure of the Keystores

Lets consider an encryption scenario as below.

Request Flow

When, client invokes the service, it encrypt the payload using client’s private key. Then at the server, it should have client’s public key certificate in order to decrypt the payload.

Response Flow

When server sends back the response to the client, server uses its private key to encrypt the response. Then the client who invoked the secured service should have the public key certificate of the server to decrypt the response.

Likewise, depending on the security policies being used between client and server, it will need to have client’s public key certificate in at the server and server’s public key certificate at the client. But for several other scenarios, a trusted third party certificate might be used.

Anyway, we are going to have the following structure in our client and server keystore as it will be used to test all the scenarios available via Mashup Server’s management console.

client.jks (password - client)

alias key
client client's private key (password - client)
server server's public key

 

server.jks (password - server)

alias key
server server's private key (password - server)
client client's public key

Generating keystores

Please follow this post and create client.jks and server.jks with the structure
described above.

Creating a sample mashup to be used as the service

In this tutorial, I will use a simple mashup as the secured service. i.e A mashup which takes a string name as the input and responds a greeting for that name, will be created and deployed in WSO2 Mashup Server. Once we deployed any mashup in WSO2 Mashup Server, it will behave as any other Axis2 service. Then, we can easily do most of the administrative tasks we want using management console.

So, please create a mashup with the content below.
this.serviceName = "helloService";
    function sayHello(name) {
    return "Hello " + name "!";
}
Creating mashup client

Introduction to WSRequest Hostobject

The WSRequest object is similar to the XMLHTTPRequest object. Its usage typically involves specifying the endpoint address and setting up options on how to frame the message, invoking the operation with a specific XML payload, and then checking and extracting information from the result.

It can also be considered as the Javascript wrapper for Axis2 ServiceClient which allows to do web service invocation. So, in WSO2 Mashup Server, the Web Service client is WSRequest. You can invoke services either synchronously or asynchronous, send custom HTTP/SOAP headers and even secure services invocation from a mashup with the help of WSRequest Hostobject.

Following is the Javascript interface of the WSRequest Hostobject. For more information please refer this.
{
    property EventListener onreadystatechange;
    property unsigned short readyState;
    function void open ( object options | String httpMethod, String url [, boolean async [, String username[, String password]]]);
    function void send ( XML payload | XMLString payload ); // To be used when the open method of WSRequest is used
    function void openWSDL ( String wsdlURL, ?boolean async, [Object options,[QName serviceName, [String endpointName]]]);
    function void send ( String operationName | QName operationName, XML payload | XMLString payload ); //To be used when the openWSDL method of WSRequest is used
    readonly property String responseText;
    readonly property Document responseXML;
    readonly property XML responseE4X;
    readonly property WebServiceError error;
}
Invoking the sample Service

Here I will describe how you can use WSRequest Hostobject to invoke a web service. You can find more information about service invocation using WSRequest Hostobject at Mashup Server’s documentation pages. But it would be better to write the client mashup to work with a normal service before extending it to access a secured service.

Following code fragment will invoke the above deployed HelloService and return the response. In the following client, the payload has been hard coded for the simplicity.
function invokeService(){
    var client = new WSRequest();
    var options = new Array();
    options["useSOAP"] = 1.2;
    options["action"] = "urn:sayHello";

    var payload =
    <sayHello>
        <name>Ruchira</name>
    </sayHello>;

    var result;

    try {
        client.open(options,"http://localhost:9763/services/admin/helloService", false);
        client.send(payload);
        result = client.responseE4X;
    } catch (e) {
        system.log(e.toString(),"error");
        return e.toString();
    }
    return result;
}
Create a file named helloClient.js in repository/deployment/server/jsservice/admin directory and add the above code fragment. Once you invoke the "Tryit" page of the newly created service, you should get the "Hello Ruchira!" greeting from the service.

Once you succeed with the above step, you can move to the next step which secures the helloService and modifies helloClient to invoke the secured service.

Secure Services

Secured Web services can be categorized into following forms depending on the method they used.

HTTP Basic Auth

Your service might have been secured using HTTP Basic Authentication. In that case, you can simply pass the username and password into the open() method of WSRequest Hostobject and invoke the service.

WS-Security

In most of the Enterprise level use-cases, HTTP Basic Authentication might not be a good solution, then WS-Security comes into the picture. Using WS-Security, you can have a greater flexibility with your service security. So, most of the today’s services are based on WS-Security.

Likewise, a web service can be secured either with HTTP Basic Authentication or using WS-Security. WSRequest hostobject can be used to invoke services secured with any of the above methods.

Securing the mashup service
  • Uploading Keystore into WSO2 Mashup Server
    1. Go to Configure > Keystores
    2. Go through "Add New Key store" wizard and add the server.jks created above
  • Apply a security scenario using Management Console
    1. Go to Main > Manage > Web Services > List
    2. Select the above created service i.e. "helloService" and click on "Unsecured" link on the service listing
    3. Go through the security engaging wizard and engage one of the scenarios listed there
  • View the WSDL
    1. If you view the WSDL 1.0 or 2.0 of the service now, you will be able to see the newly applied policy in
      the WSDL.
Invoking secured service

WSRequest Hostobject offers two methods to deal with security. If you know the policy being used in the service, then you can use open() method and set the policy manually. If you don’t, then you can use openWSDL() method which will then extract the policy definition from the given WSDL and invoke the service. But performance wise, open() method is more better than openWSDL() as openWSDL method always read the WSDL before invoking the service.

open() method

Method signature is as bellow.
function void open ( object options | String httpMethod, String url [, boolean async [, String username[, String password]]])
When you use this method to invoke a secured service, then you have two options as below.
  1. Specify policy definition with rampart configuration as an E4X XML object in the options object using "policy" property. You can also use ${resources.dir} to specify the path of the resources folder in keystore paths. i.e. Assume your keystore named client.jks is in *.resources folder, then you can use ${resources.dir}/client.jks as the keystore path in your rampart configuration.
  2. Specify policy definition using "policy" property and rampart configuration as a JSON object using "rampart" property. The structure of the rampart configuration JSON has been described under the section named "Rampart
    Configuration" below. Here, you don’t need to specify rampart configuration in policy definition.
In both cases, you can also keep the policy definition as a separate XML file within your resources folder and set it to "policy" property by creating an XML object with the help of File Hostobject.

If you are invoking an HTTP Basic Authentication enabled service, then just passing the username and password as method parameters would be sufficient and "policy" and "rampart" properties won’t need to be specified.
var options = new Object();
…..
options["policy"] =
<Policy>.....</Policy>;
options["rampart"] = { ….. };
…..
wsRequest.open(options, serviceUrl, false);
Please refer WSRequest documentation at [2] for more information about other input parameters and properties of the options object.

openWSDL() method
function void openWSDL ( String wsdlURL, ?boolean async, [Object options,[QName serviceName, [String endpointName]]]);
When openWSDL() method is used, you need to specify the URL of the secured service WSDL. Then it will read the WSDL and find the attached policy definitions from it. In this case, you only need to specify the "rampart" property in your options object.

Specifying "policy" property too, will override the policy definition extracted from the WSDL.

Rampart Configuration

Following is the format of the rampart configuration JSON. Dummy values and comments have been added to explain it more precisely.

{ 
    user : "admin", //username for to use UT and other scenarios
    userPassword : "admin", //in UT, this is the user password
    keyPassword : "client", //private key password
    userCertAlias : "client",
    stsAlias : "sts",
    encryptionUser : "server",
    timestampTTL : "10000",
    timestampMaxSkew : "10",
    timestampPrecisionInMilliseconds : "100",
    signatureCrypto : {
        type : "jks", //keystore type
        file : "client.jks", //keystore file relative to resource folder
        password : "client", //keystore password
        enableCryptoCaching : true,
        cacheRefreshInterval : 3000
    },
    encryptionCrypto : {
        type : "jks", //keystore type
        file : "client.jks", //keystore file relative to resource folder
        password : "client", //keystore password
        enableCryptoCaching : true,
        cacheRefreshInterval : 3000
    },
    decryptionCrypto : {
        type : "jks", //keystore type
        file : "client.jks", //keystore file relative to resource folder
        password : "client" //keystore password
    },
    stsCrypto : {
        type : "jks", //keystore type
        file : "client.jks", //keystore file relative to resource folder
        password : "client" //keystore password
    },
    kerberosConfig : {
        /**
        "key" : "value" properties, you can also use ${resources.dir}
        in property values
        */
        "client.principal.name" : "client",
        "client.principal.password" : "client",
        "service.principal.name" : "service",
        "java.security.auth.login.config" : "jassconfig",
        "javax.security.auth.useSubjectCredsOnly" : "true",
        "kdc.des.aes.factor" : "4",
        "java.security.krb5.conf" : "/home/ruchira/wso2/conf"
    }
} 
Secure Client using Open() method
(you need to set the correct scenario xml there in the line number 6)
function invokeSecureServiceOpen() {
    /**
    * Reads the policy.xml from resources folder and creates
    * E4X XML object from it
    */
    var policyFile = new File("scenarios/scenario2-policy.xml");
    policyFile.openForReading();
    var policy = new XML(policyFile.readAll());

    var client = new WSRequest();
    var options = new Array();
    options["useSOAP"] = 1.2;
    options["action"] = "urn:sayHello";
    options["policy"] = policy;
    options["rampart"] = {
        user : "admin", //username for to use UT and other scenarios
        userPassword : "admin", //in UT, this is the user password
        keyPassword : "client", //private key password
        userCertAlias : "client",
        encryptionUser : "server",
        signatureCrypto : {
            type : "jks", //keystore type
            file : "client.jks", //keystore file relative to resource folder
            password : "client", //keystore password
            enableCryptoCaching : true,
            cacheRefreshInterval : 3000
        },
        encryptionCrypto : {
            type : "jks", //keystore type
            file : "client.jks", //keystore file relative to resource folder
            password : "client", //keystore password
            enableCryptoCaching : true,
            cacheRefreshInterval : 3000
        },
        decryptionCrypto : {
            type : "jks", //keystore type
            file : "client.jks", //keystore file relative to resource folder
            password : "client" //keystore password
        },
        stsCrypto : {
            type : "jks", //keystore type
            file : "client.jks", //keystore file relative to resource folder
            password : "client" //keystore password
        }
    };

    var payload =
    <sayHello>
        <name>Ruchira</name>
    </sayHello>;

    var result;

    try {
        client.open(options,"http://localhost:9763/services/admin/helloService", false);
        client.send(payload);
        result = version.responseE4X;
    } catch (e) {
        system.log(e.toString(),"error");
        return e.toString();
    }
    return result;
}
Secure Client using openWSDL() method
function invokeSecureServiceOpenWSDL() {
    var client = new WSRequest();
    var options = new Array();
    options["useSOAP"] = 1.2;
    options["action"] = "urn:sayHello";
    options["rampart"] = {
    user : "admin", //username for to use UT and other scenarios
    userPassword : "admin", //in UT, this is the user password, else private key
    keyPassword : "client", //private key password
    userCertAlias : "client",
    encryptionUser : "server",
    signatureCrypto : {
            type : "jks", //keystore type
            file : "client.jks", //keystore file relative to resource folder
            password : "client", //keystore password
            enableCryptoCaching : true,
            cacheRefreshInterval : 3000
        },
        encryptionCrypto : {
            type : "jks", //keystore type
            file : "client.jks", //keystore file relative to resource folder
            password : "client", //keystore password
            enableCryptoCaching : true,
            cacheRefreshInterval : 3000
        },
        decryptionCrypto : {
            type : "jks", //keystore type
            file : "client.jks", //keystore file relative to resource folder
            password : "client" //keystore password
        },
        stsCrypto : {
            type : "jks", //keystore type
            file : "client.jks", //keystore file relative to resource folder
            password : "client" //keystore password
        }
    };

    var payload =
    <sayHello>
        <name>Ruchira</name>
    </sayHello>;

    var result;

    try {
        var service = new QName("http://services.mashup.wso2.org/helloService","helloService");
        request.openWSDL("http://localhost:9763/services/admin/helloService?wsdl", false, options, service, "SOAP12Endpoint");
        request.send("sayHello", payload);
        result = version.responseE4X;
    } catch (e) {
        system.log(e.toString(),"error");
        return e.toString();
    }
    return result;
}
Testing the service

After successfully created the two operations with open and openWSDL methods, now you can invoke the client service using it’s tryit page which will then invoke the secured "helloService".

No comments:

Post a Comment