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


@RolesAllowed({"committer"})
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/users.properties" 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.io.IOException;
import java.util.Properties;
import java.util.Vector;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;

import org.apache.axis2.context.MessageContext;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.WSSecurityEngineResult;
import org.apache.ws.security.WSUsernameTokenPrincipal;
import org.apache.ws.security.handler.WSHandlerConstants;
import org.apache.ws.security.handler.WSHandlerResult;


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://127.0.0.1:4201");

//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)
wsSecEngineResults.get(j);
int action = ((Integer)wser.get(WSSecurityEngineResult.TAG_ACTION)).intValue();
if(action == WSConstants.UT) {
WSUsernameTokenPrincipal principal = (WSUsernameTokenPrincipal) wser
.get(WSSecurityEngineResult.TAG_PRINCIPAL);
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 :


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

<module ref="rampart"/>

<wsp:Policy wsu:Id="UTOverTransport" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<wsp:ExactlyOne>
<wsp:All>
<sp:TransportBinding xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">
<wsp:Policy>
<sp:TransportToken>
<wsp:Policy>
<sp:HttpsToken RequireClientCertificate="false"/>
</wsp:Policy>
</sp:TransportToken>
<sp:AlgorithmSuite>
<wsp:Policy>
<sp:Basic256/>
</wsp:Policy>
</sp:AlgorithmSuite>
<sp:Layout>
<wsp:Policy>
<sp:Lax/>
</wsp:Policy>
</sp:Layout>
<sp:IncludeTimestamp/>
</wsp:Policy>
</sp:TransportBinding>
<sp:SignedSupportingTokens xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">
<wsp:Policy>
<sp:UsernameToken sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient" />
</wsp:Policy>
</sp:SignedSupportingTokens>

<ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy">
<ramp:passwordCallbackClass>org.acme.SimpleEJBService</ramp:passwordCallbackClass>
</ramp:RampartConfig>

</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>


</service>


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.axiom.om.impl.builder.StAXOMBuilder;
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("javax.net.ssl.trustStore", "/path/to/wso2wsas.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "wso2wsas");

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

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

//Engage Rampart
client.engageModule("rampart");

Options options = client.getOptions();


//Set user name and password
options.setUserName("jonathan");
options.setPassword("secret");

//Load and set UsernameToke/HTTPS policy
options.setProperty(
RampartMessageData.KEY_RAMPART_POLICY,
loadPolicy("/path/to/simpl/usernametoken/over/https/policy.xml"));

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

System.out.println(resp.get_return());
}

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(Utils.java:479)
at org.apache.axis2.description.OutInAxisOperationClient.handleResponse(OutInAxisOperation.java:351)
at org.apache.axis2.description.OutInAxisOperationClient.send(OutInAxisOperation.java:397)
at org.apache.axis2.description.OutInAxisOperationClient.executeImpl(OutInAxisOperation.java:214)
at org.apache.axis2.client.OperationClient.execute(OperationClient.java:163)
at org.acme.HelloEJBStub.sayHello(HelloEJBStub.java:433)
at org.acme.Client.main(Client.java:32)


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

1 comment:

785jbgdpt2 said...

Re-Spins can deliver massive wins as they are configured with various superior features… more on this beneath. San Manuel Casino patrons would be the first in California to enjoy the “Adam Levine Video Slots”, which may be discovered contained in the Rockin' Casino, positioned on the second ground between Just Barbeque and Rock & Brews. To have fun the exclusivity of Adam Levine’s slot machine, San Manuel Casino will host a free slot pull on Monday, Sept. 23, 2019. Club Serrano members could have the prospect to play this thrilling new recreation and receive swag from San Manuel Casino group 카지노사이트 members, whereas supplies final. The best video slots are developed by a number of the} greatest names in the on line casino gaming business. Check out titles fromMicrogaming,NetEntandPlayTechfor a number of the} prime rated slots obtainable for 2022.