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.
No comments:
Post a Comment