One of the advantages with message level security is that it provides the granularity that we need. One example is the ability to apply different security policies to request and response messages in Web service communication, which the today's discussion is based on.
Example use case:
Let's say we have a web service called 'StudentService' which exposes the method: 'getStudent'. It gets input parameters as student id and student user name. And returns a student object with information: student age, grade and full name.
Coming to the security requirements, let's say we need to send the request- which contains student id and user name, confidentially and obtain the student information signed by the service to ensure integrity and non-repudiation.
Yes, we can apply a sign and encrypt policy to the service and achieve this. But wait... then we are unnecessarily encrypting the response message as well. Since encryption comes at a cost, it is good if we can apply encryption only when it is required.
How to achieve:
To achieve the above hypothetical requirement, we can use the ability to apply policies to different policy subjects in the binding hierarchy as described in
this article by Nandana.
With that, we can apply Encrypt Only policy to the request message and Sign Only policy to the response message of 'getStudent' operation.
For this, we can use WSO2
AppServer which is powered by Web Services/SOAP/WSDL engine:
Axis2 and
Rampart -the security module of Axis2, as described below..
Walk through:
I will demonstrate how to achieve the above with a sample.
Resources: You can find all the resources we will use in this sample -including source code of service & client, policies, wsdl, captured request and response and service archive in the
resources directory uploaded.
1. Service
Following is the simple logic of our hypothetical StudentService's get student method:
public class StudentService {
public Student getStudent(String studentName, String studentId){
Student student = null;
if(("alice".equals(studentName))&&("123ABC".equals(studentId))){
student = new Student();
student.setAge(25);
student.setFullName("Alice Power");
student.setGrade("A");
}
return student;
}
2. Policy
Following is the services.xml of the web service where we have embedded the security policies for in and out messages.
I have shorten the services.xml for the brevity but you can find the full version in the
resources directory.
..............................
..............................
................................
................................
org.wso2.security.sample.StudentService
Please note the following important lines wrt above services.xml
line 03: Rampart module is engaged to the service to process security in the in and out flows of the service.
line 05-line22: defines the first policy attachment -with message-in security policy.
line 07 & 08: defines policy subjects as operation:getStudent/in of soap11 and soap 12 binding.
line 10-21: defines the security policy applies to those policy subjects, which is Encrypt Only security policy.
line 16-19: defines the rampart configuration as a policy assertion in the security policy. Note that I have used the default keystore shipped with AppServer for cryptographic configurations.
line 23-line 40: defines the second policy attachment -with message-out security policy.
Same as above you will notice the same structure where Sign Only security policy is applied to policy subjects -operation:getStudent/out of soap11 and soap12 bindings.
line 43-47: defines service specific stuff like service class, operation name and message receiver.
Ok... we are done with the service now.
I have provided a build.xml from which you can create the axis2 service archive of the 'Student Service'. Make sure you have installed
apache ant and run ant command from the directory where build.xml resides in the provided resources.
Download and run WSO2 AppServer and host this service archive.
Note that, though I'va used WSO2 AppServer for the convenience, you can also host this service in Apache Axis2 server with rampart configurations properly set in the policy above, according to your custom keystore information.
3. Generated WSDL
Now try to access the service wsdl and you will notice that security policies are attached to the relevant points that we defined in the services.xml above.
Here I will include only the parts of the wsdl, and you can find the complete wsdl in the
resources directory.
At the beginning of the wsdl, security policies are defined inside "
wsdl:definitions" as shown below. Each policy definition contains "
wsu:Id" attribute which is used to reference policy inside the wsdl.
In the "wsdl:binding" section, correct policy is attached to the corresponding policy attachment point (as we defined in the services.xml), using "wsp:PolicyReference" element with "wsu:Id" attribute defined above. Following listing extracted from the wsdl shows this.
4. Client
We are done analyzing the server side... Let's see how to write a client who understands and supports the security requirements communicated by the service.
Following is the code listing for the client.. too long, yes I know. Let me take you through the important steps of it as below:
package org.wso2.security.sample;
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.AxisFault;
import org.apache.axis2.addressing.EndpointReference;
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;
import org.apache.rampart.policy.model.CryptoConfig;
import org.apache.rampart.policy.model.RampartConfig;
import javax.xml.stream.XMLStreamException;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Properties;
public class Client {
private static final String RESOURCES_DIR = "src" + File.separator + "main" + File.separator +
"resources" + File.separator;
private static final String MODULES_DIR = RESOURCES_DIR + "modules";
private static final String KEYSTORE_PATH = RESOURCES_DIR + "keystore" + File.separator + "wso2carbon.jks";
private static final String POLICY_DIR_PATH = RESOURCES_DIR + "policy" + File.separator;
private static final String IN_POLICY_PATH = POLICY_DIR_PATH + "in_sec_policy.xml";
private static final String OUT_POLICY_PATH = POLICY_DIR_PATH + "out_sec_policy.xml";
private static final String END_POINT_ADDR = "http://192.168.1.5:9762/services/StudentService";
public static void main(String[] args) {
try {
//create configuration context
ConfigurationContext ctx =
ConfigurationContextFactory.createConfigurationContextFromFileSystem(MODULES_DIR, null);
//create service client
ServiceClient serClient = new ServiceClient(ctx, null);
//engage modules
serClient.engageModule("addressing");
serClient.engageModule("rampart");
//load in/out policies
Policy in_sec_policy = loadPolicy(IN_POLICY_PATH);
Policy out_sec_policy = loadPolicy(OUT_POLICY_PATH);
//add rampart config assertion to the ws-sec policies
RampartConfig rampartConfigAssretion = buildRampartConfig();
in_sec_policy.addAssertion(rampartConfigAssretion);
out_sec_policy.addAssertion(rampartConfigAssretion);
//set in/out security policies in client opts
serClient.getOptions().setProperty(RampartMessageData.KEY_RAMPART_IN_POLICY, in_sec_policy);
serClient.getOptions().setProperty(RampartMessageData.KEY_RAMPART_OUT_POLICY, out_sec_policy);
//set action
serClient.getOptions().setAction("urn:getStudent");
serClient.getOptions().setTo(new EndpointReference(END_POINT_ADDR));
//invoke the service
OMElement response = serClient.sendReceive(getPayLoad());
System.out.println(response);
} catch (AxisFault axisFault) {
axisFault.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
} catch (FileNotFoundException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
} catch (XMLStreamException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
private static Policy loadPolicy(String filePath)
throws XMLStreamException, FileNotFoundException {
StAXOMBuilder builder = new StAXOMBuilder(filePath);
return PolicyEngine.getPolicy(builder.getDocumentElement());
}
private static RampartConfig buildRampartConfig() {
RampartConfig rampartConfig = new RampartConfig();
rampartConfig.setUserCertAlias("wso2carbon");
rampartConfig.setEncryptionUser("wso2carbon");
Properties cryptoProperties = new Properties();
cryptoProperties.put("org.apache.ws.security.crypto.merlin.keystore.type", "JKS");
cryptoProperties.put("org.apache.ws.security.crypto.merlin.file", KEYSTORE_PATH);
cryptoProperties.put("org.apache.ws.security.crypto.merlin.keystore.password", "wso2carbon");
CryptoConfig cryptoConfig = new CryptoConfig();
cryptoConfig.setProvider("org.apache.ws.security.components.crypto.Merlin");
cryptoConfig.setProp(cryptoProperties);
rampartConfig.setEncrCryptoConfig(cryptoConfig);
rampartConfig.setSigCryptoConfig(cryptoConfig);
return rampartConfig;
}
/**
* This is the request we need to send
* *
* alice*
* 123ABC* *
* @return
*/
private static OMElement getPayLoad() {
OMFactory omFactory = OMAbstractFactory.getOMFactory();
OMNamespace namespace = omFactory.createOMNamespace("http://sample.security.wso2.org", "p");
OMElement parentElement = omFactory.createOMElement("getStudent", namespace);
OMElement child1 = omFactory.createOMElement("studentName", namespace);
child1.setText("alice");
OMElement child2 = omFactory.createOMElement("studentId", namespace);
child2.setText("123ABC");
parentElement.addChild(child1);
parentElement.addChild(child2);
return parentElement;
}
}
line 46: engages rampart to service client which processes security of the in-coming and out-going messages.
line 49 & 50: loads in-policy and out-policy from file. Note that in-policy of server side applies to out-policy of client side and out-policy of server side applies to in-policy of client side. I have included client side policy files in the resources directory.
line 53-55: programetically inserts rampart configurations as policy assertions to both in-policy and out-policy.
line 58 & 59: attaches client side in-security policy and out-security policy to the axis2 service client, through client options.
line 62-66: creates the request and invokes the 'StudentService'.
How to run the client:
- To run the client from the provided client source, you need to set necessary axis2 and rampart libraries in your classpath.
- If you use WSO2 AppServer to host the service, you can easily run ant command inside [AppServer_home]/bin which will copy necessary libraries into [AppServer_home]/repository/lib.
- Add [AppServer_home]/repository/lib & [AppServer_home]/lib/endorsed folders into the classpath.
- You can point to the necessary libraries shipped with Axis2 and Rampart distributions as well -not necessarily need AppServer.
- Run the client -et VoilĂ , if your client prints the received response correctly, you have invokes the secured service successfully :).
But we can't be happy until we confirm that the messages exchanged between client and the server are secured in the way we expected.
5. Request & Response Messages
We can monitor request and response messages using tcpmon -how to use tcpmon is out of the scope of this post...
Following are SOAP Body of 1. request and 2. response messages that I captured during client-service communication. Entire messages are included inside
resources directory.
1. Request SOAP Body:
7drXxTdkthvhnTGpBnNcn3iOxgBR/zq72+Drc3vvB6ONN2U13I9SCjNziZ3M/82PDIonsKdx/aSOQ9RuEt8cAHmdg5DyK2Z7jmVpo2u8tAltaReSVYHRt5d4JqA2Admp4OCWayi/XMBFfH3G74BCujTkiYS+azi2HJxF17oAbIX/4gUoZvvoZEkd/XzRsQf0YyTZGULWIuz0mzpXjchq4GQe1XFQrhPk5ZTxi/+wmcPsW8p/s6WVkKSuXeVwajQlihMPHFSji8IbONUWw2OQNoS7/0z8slC2XDGrwWUbp2BiondpjA15ogVM7CWY9D+Tc6hbwAziIexHAN+s9p5Ox+tdpQiJej/vVZbJVGL2Oifrx+nio+/oTQPRE52s0iSEX/GsuufWjVLxLPhhB7jVnEtF4Rx0MJi3aZG+WP5OWaViEZTqKYwvzcG0o3paLFV7+eQ5ZHtUOz5HhjEDXNMrj9IpPx4GAtRO0aZLxcqt9uA=
Entire SOAP body is encrypted adhering to the in-security policy of the service.
2. Response SOAP Body:
25
Alice Power
A
Here the response SOAP body is in plain text, yet the message is signed. You can gain more information about message signature by referring to the SOAP header of the response message which is included in the
resources.
Alright.. So I hope you got an understanding about achieving message level security with WS-Security & Rampart by going through this end to end sample of applying different security policies to in,out messages of a web service.
I would like to thank Manjula & Nandana from whom I initially got to know about the $subject.
Thanks to you for patience reading and have a good day....!!!