This is the first of a series of blogs about using
Rampart effectively for securing web services. In this blog, I am explaining how to use an Username Token with transport level security to secure a web service. You can find all the resources related to this post
here.
Securing Approach
Username Token is a Signed Supporting token that can be used to send the username/password to the other end. Then the recipient can check whether this is coming from a valid user. In this example we are are achieving transport level security through HTTPS. In HTTPS the service and the client are communicating through a pre-established a secured tunnel. So the confidentiality and the integrity of the messages are protected.
Security Policy Used
Following listing is the security policy that we are using in this blog.
If you observe this security policy carefully, you will notice that two main policy assertions are specified.
TransportBinding assertion is used to enforce the transport level security.
HttpsToken inside the
Transport Token declares the requirement of using HTTPS.
SignedSupportingTokens assertion contains the
UsernameToken assertion which establishes the requirement of providing a username token.
In the client side, a custom tag is appended to the policy.
alice
org.apache.rampart.utSample.PWDCBHandler
Here I am using the policy based configuration approach instead of programmatically setting the parameters. I am setting the
alice as the user and
org.apache.rampart.utSample.PWDCBHandler as the password callback handler. So the
alice and the password returned from the password callback handler will be sent to the server as the username/password pair.
In the server side I am setting the Password Callback handler to be used for verifying the password.
org.apache.rampart.utSample.PWDCBHandler
These custom assertions will not be appearing in the WSDL. They are used within Rampart to get the configuration details.
Creating the service
We are using a simple "echo" service as the service. The service class that we are using in this blog is given below.
public class SimpleService {
public String echo(String arg) {
return arg;
}
}
You can find the
sample01.aar file that contains this service in the
resources. Deploy it in
Tomcat +
Axis2 or in
WSO2 Web Services Application Server (WSO2 WSAS). If you are using Tomcat, you must enable SSL first. This
guide will take you through the steps required to enable SSL in Tomcat.
Client Code
package org.apache.rampart.utSample;
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.rampart.RampartMessageData;
public class UTClient {
public static void main(String[] args) throws Exception {
String repo = "/path/to/repo";
String trustStore = "/path/to/server's_keystore";
String storePass = "keyStore_passwprd";
String policyFile = "/path/to/UT-Policy.xml";
String EPR = "https://localhost:8443/axis2/services/sample01";
System.setProperty("javax.net.ssl.trustStore", trustStore);
System.setProperty("javax.net.ssl.trustStorePassword", storePass);
ConfigurationContext ctx = ConfigurationContextFactory.createConfigurationContextFromFileSystem(repo, null);
ServiceClient client = new ServiceClient(ctx, null);
Options options = new Options();
options.setAction("urn:echo");
options.setTo(new EndpointReference(EPR));
options.setProperty(RampartMessageData.KEY_RAMPART_POLICY, loadPolicy(policyFile));
client.setOptions(options);
client.engageModule("addressing");
client.engageModule("rampart");
OMElement response = client.sendReceive(getPayload("Hello world"));
System.out.println(response);
}
private static Policy loadPolicy(String xmlPath) throws Exception {
StAXOMBuilder builder = new StAXOMBuilder(xmlPath);
return PolicyEngine.getPolicy(builder.getDocumentElement());
}
private static OMElement getPayload(String value) {
OMFactory factory = OMAbstractFactory.getOMFactory();
OMNamespace ns = factory.createOMNamespace("http://sample01.policy.samples.rampart.apache.org", "ns1");
OMElement elem = factory.createOMElement("echo", ns);
OMElement childElem = factory.createOMElement("param0", null);
childElem.setText(value);
elem.addChild(childElem);
return elem;
}
}
Lets walk through the important code segments of the Client.
Following code lines are used to set the trust store properties. Since we are using HTTPS as the transport, we need to trust the certificate used by the server. So I am adding the keystore used by the server as a trusted store.
System.setProperty("javax.net.ssl.trustStore", trustStore);
System.setProperty("javax.net.ssl.trustStorePassword", storePass);
Then you need to specify a repository in order to locate the module archives of Rampart, Addressing and Rahas. This
tutorial explains how to create a Axis2 Repository.
ConfigurationContext ctx = ConfigurationContextFactory.createConfigurationContextFromFileSystem(repo, null);
You should engage Rampart and Addressing modules in the client side. You should point to a valid repository in the previous step in order to successfully engage these modules.
client.engageModule("addressing");
client.engageModule("rampart");
Following is the Password Callback handler I am using at the client side.
package org.apache.rampart.utSample;
import org.apache.ws.security.WSPasswordCallback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.UnsupportedCallbackException;
import java.io.IOException;
public class PWDCBHandler implements CallbackHandler {
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
WSPasswordCallback cb = (WSPasswordCallback) callbacks[0];
if ("alice".equals(cb.getIdentifier())) {
cb.setPassword("bobPW");
} else {
cb.setPassword("bobPW");
}
}
}
This class implements the
javax.security.auth.callback.CallbackHandler interface .
All these source codes can be found with the
resources.
Request & Response
Since we are using SSL, only the encrypted form of the request and response can be captured. But the request and response in plain text should appear as follows.
Request
2009-08-18T15:12:58.053Z
2009-08-18T15:17:58.053Z
alice
bobPW
http://localhost:8081/axis2/services/sample01
urn:uuid:B429936843779AAE931250608376908
urn:echo
Hello world
Observe the UsernameToken element in the security header. It contains the username/password pair.
Response
2009-08-18T15:12:58.544Z
2009-08-18T15:17:58.544Z
urn:echoResponse
urn:uuid:B429936843779AAE931250608376908
Hello world
Now we have gone through all the steps required to invoke a service with username token over HTTPS.
All the source files, policy files, and other resources can be found
here.