Monday, February 11, 2008

Access a Restricted EJB Method as a Web Service with UsernameToken Authentication

What if we need to expose an EJB as a web service? No problem ... we can simply use the org.apache.axis2.rpc.receivers.ejb.EJBMessageReceiver provided by Axis2. But what if the EJB's methods are access restricted?

I figured out that it is very easy to write a custom wrapper web service to expose such a protected EJB and use standard UsernameToken authentication on it.

Following are the steps I followed to try this out:

Step 1 : Basic EJB sample with OpenEJB

First I followed this "hello world" sample using OpenEJB and setup the OpenEJB container and deployed the EJB.

Step 2 : Modified "HelloBean" to restrict access to "sayHello" method

public String sayHello() {
return "Hello World!!!";

Now only users with committer role can access this method.
The users used by the default login module implementation are listed in "conf/" of the OpenEJB distribution.

Step 3 : Develop and deploy a service to wrap the EJB

We basically have to write a service that is a client to this EJB. I engaged Rampart on this service and configured it with WS-SecurityPolicy to expect a UsernameToken. And then obtained the user name and password from security processing results and used those credentials in invoking the EJB.

Have a look at the following service class :

package org.acme;

import java.util.Properties;
import java.util.Vector;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;

import org.apache.axis2.context.MessageContext;

public class SimpleEJBService implements CallbackHandler {

public String sayHello() throws Exception {

Properties props = new Properties();
props.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.RemoteInitialContextFactory");
props.put(Context.PROVIDER_URL, "ejbd://");

//Obtain the principal
WSUsernameTokenPrincipal principal = getPrincipal();

//Set the username and password
props.put(Context.SECURITY_PRINCIPAL, principal.getName());
props.put(Context.SECURITY_CREDENTIALS, principal.getPassword());

Context ctx = new InitialContext(props);
Object ref = ctx.lookup("HelloBeanRemote");

//Invoke method
Hello h = (Hello)PortableRemoteObject.narrow(ref, Hello.class);
String result = h.sayHello();
return result;


* Traverse the security processing results of rampart and pick the UsernameToken information.
private WSUsernameTokenPrincipal getPrincipal() {
MessageContext msgCtx = MessageContext.getCurrentMessageContext();
Vector results = null;
if ((results = (Vector) msgCtx
.getProperty(WSHandlerConstants.RECV_RESULTS)) == null) {
throw new RuntimeException("No security results!!");
} else {
for (int i = 0; i < results.size(); i++) {
//Get hold of the WSHandlerResult instance
WSHandlerResult rResult = (WSHandlerResult) results.get(i);
Vector wsSecEngineResults = rResult.getResults();

for (int j = 0; j < wsSecEngineResults.size(); j++) {
//Get hold of the WSSecurityEngineResult instance
WSSecurityEngineResult wser = (WSSecurityEngineResult)
int action = ((Integer)wser.get(WSSecurityEngineResult.TAG_ACTION)).intValue();
if(action == WSConstants.UT) {
WSUsernameTokenPrincipal principal = (WSUsernameTokenPrincipal) wser
return principal;

return null;

public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
//Do nothing since we simply want to move forward
//user name and password to the EJB container


The services.xml is as follows :

<parameter name="ServiceClass" locked="false">org.acme.SimpleEJBService</parameter>
<operation name="sayHello">
class="org.apache.axis2.rpc.receivers.RPCMessageReceiver" />

<module ref="rampart"/>

<wsp:Policy wsu:Id="UTOverTransport" xmlns:wsu="" xmlns:wsp="">
<sp:TransportBinding xmlns:sp="">
<sp:HttpsToken RequireClientCertificate="false"/>
<sp:SignedSupportingTokens xmlns:sp="">
<sp:UsernameToken sp:IncludeToken="" />

<ramp:RampartConfig xmlns:ramp="">



Note that I didn't bother with authentication of the incoming UsernameToken because this will be handled by the login module of the EJB container.

I first copied all (probably we don't need all of them ...) openejb-* jars from the OpenEJB distro and the jar'ed org.acme.Hello interface to the "lib" directory of WSO2WSAS-2.2 standalone and deployed the service.

Step 4 : Web service client

Finally I generated a client stub using the WSDL2Java tool and developed my client code

package org.acme;

import org.acme.HelloEJBStub.SayHelloResponse;
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 Client {

public static void main(String[] args) throws Exception {

//This is because we use a self signed SSL cert in WSO2WSAS
System.setProperty("", "/path/to/wso2wsas.jks");
System.setProperty("", "wso2wsas");

ConfigurationContext ctx = ConfigurationContextFactory.createConfigurationContextFromFileSystem("/path/to/my/client/repo");

HelloEJBStub stub = new HelloEJBStub(ctx);
ServiceClient client = stub._getServiceClient();

//Engage Rampart

Options options = client.getOptions();

//Set user name and password

//Load and set UsernameToke/HTTPS policy

//Invoke service operation
SayHelloResponse resp = stub.sayHello();


private static Policy loadPolicy(String xmlPath) throws Exception {
StAXOMBuilder builder = new StAXOMBuilder(xmlPath);
return PolicyEngine.getPolicy(builder.getDocumentElement());


Note that I have used "jonathan" as the user name and "secret" as the password. This user is there by default in the OpenEJB distro.

That's it !!! ... When I ran the client I got the following response:

Hello World!!!

And when I tried chaing the username I get :

Exception in thread "main" org.apache.axis2.AxisFault: This principle is not authorized.
at org.apache.axis2.util.Utils.getInboundFaultFromMessageContext(
at org.apache.axis2.description.OutInAxisOperationClient.handleResponse(
at org.apache.axis2.description.OutInAxisOperationClient.send(
at org.apache.axis2.description.OutInAxisOperationClient.executeImpl(
at org.apache.axis2.client.OperationClient.execute(
at org.acme.HelloEJBStub.sayHello(
at org.acme.Client.main(

Now it is clear that the login module was not able to authenticate the user.

No comments: