This is what I got from some place, a very good article (yes it's long
and complex, but it covers everything).
(Picked from http://weblogs.asp.net/jdanforth/archive/2005/01/16/354060.aspx)
[Java] Simple WSS4J with Axis Tutorial
This is a simple tutorial for getting started with WSS4J. It's based
on a tutorial posted on the WSS4J mailing list by Rami Jaamour, but
I've added a few clarifications and variants of the code samples. Rami
should get all credits for this tutorial; I'm just a newbie trying to
learn how to use this tool!
Updates
2006-03-29 - If you get an exception like this one below, it is
recommended to look at which Axis version your are using and consider
Axis version 1.2:
Exception in thread "main" java.lang.IllegalAccessError: tried to
access method org.apache.axis.SOAPPart.setCurrentMessage(Ljava/lang/Object;I)V
from class org.apache.ws.axis.security.WSDoAllSender
at org.apache.ws.axis.security.WSDoAllSender.invoke(WSDoAllSender.java:365)
at org.apache.axis.strategies.InvocationStrategy.visit(InvocationStrategy.java:71)
2005-01-29 - This "article" has been updated a couple of times now.
Both with information regarding the xalan.jar problems, and how to set
the UserName Token dynamically.
Credits
As I wrote above, all cred should go to Rami Jaamour because most of
the stuff below is written by him. My thanks to the nice guys in the
WSS4J mailing list - Ashok Shah, Werner Dittmann, Yves Langisch and
others.
The Future
I've added a few things myself to this tutorial, and I'll keep adding
things as I learn more. I'll also connect this tutorial with a
Username Token service written in ASP.NET as soon as possible. After
that we'll see what happens. I'd like to encrypt and sign the stuff
too in the future...
Introduction
WSS4J can be used for securing web services deployed in virtually any
application server, but it includes special support for Axis. WSS4J
ships with handlers that can be used in Axis-based web services for an
easy integration. These handlers can be added to the service
deployment descriptor (wsdd file) to add a WS-Security layer to the
web service. This is a step by step tutorial for deploying a simple
service with Username Token.
Prereqs
To run this tutorial, you must install a JDK (of course). I suggest
JDK 1.4.2_04 or 1.5.0. Then you need an application server. I've
personally used version jakarta-tomcat-4.1.31. Then you need to
download and install Axis (version 1.2) and WSS4J. Getting hold of
WSS4J and the other jars you may need can be quite tricky. One way is
to download Maven and checkout and build WSS4J through it. That's what
I did (not without problems though).
If you have problems getting the needed jar files let me know and I'll
try to add them to this space for download. I've compiled the
wss4j.jar package and made it available for download here.
You don't really need a Java code editor, but it helps. Personally I
use Eclipse and Lomboz (a J2EE plug-in for Eclipse).
Installing WSS4J
1. Download the WSS4J binaries or build it from sources
2. Copy the contents (the jar files) of the WSS4J lib directory to
your Axis WEB-INF/lib directory. Many jar files will already exist.
Most of them will already exist there but you can just overwrite them
all.
3. You may need to restart Tomcat unless you have automatic
deployment/class loading turned on. Check the Axis Happiness Page
(typically at http://localhost:8080/axis), make sure that the XML
Security (xmlsec.jar) is listed under the "Optional Components"
section.
Creating the service
1. This tutorial will secure the StockQuoteService which ships with
the sample code with Axis. If you deploy the sample web apps that
ships with Axis you don't need to do anything more. Look at the Axis
docs on how to install it properly. Unless you have one already,
create a deployment descriptor (deploy.wsdd) file with the following
contents:
<deployment xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
<service name="stock-wss-01" provider="java:RPC" style="document" use="literal">
<parameter name="className" value="samples.stock.StockQuoteService"/>
<parameter name="allowedMethods" value="getQuote"/>
<parameter name="scope" value="application"/>
</service>
</deployment>
It doesn't matter where you put this file.
2. deploy the service (using AxisAdmin):
java org.apache.axis.client.AdminClient
-lhttp://localhost:8080/axis/services/AdminService deploy.wsdd
The AdminClient class depends on a load of jar-files, so to deploy
this I created a bat-file that looked like this:
setlocal
set CLASSPATH=%CLASSPATH%;C:\axis-1_2RC2\lib\axis.jar;C:\axis-1_2RC2\lib\jaxrpc.jar;C:\axis-1_2RC2\lib\commons-logging.jar;C:\axis-1_2RC2\lib\commons-discovery.jar;C:\axis-1_2RC2\lib\saaj.jar;
java org.apache.axis.client.AdminClient
-lhttp://localhost:8080/axis/services/AdminService test-deploy.wsdd
endlocal
You have to change the bat-file to reflect where you've put your axis
jar files naturally.
Creating the Client
1. Use WSDL2Java to generate the client service bindings (a number
of soap client classes):
java org.apache.axis.wsdl.WSDL2Java -o .
-Nhttp://fox:8080/axis/services/stock-wss-01 samples.stock.client
http://fox:8080/axis/services/stock-wss-01?wsdl
Again, the wsdl2java needs a number of jar files to work
properly, so I created a new bat-file to help out with that. The
bat-file looks like this:
setlocal
set CLASSPATH=%CLASSPATH%;C:\axis-1_2RC2\lib\axis.jar;C:\axis-1_2RC2\lib\jaxrpc.jar;C:\axis-1_2RC2\lib\commons-logging.jar;C:\axis-1_2RC2\lib\commons-discovery.jar;C:\axis-1_2RC2\lib\saaj.jar;C:\axis-1_2RC2\lib\wsdl4j.jar;
java org.apache.axis.wsdl.WSDL2Java -o .
-Nhttp://localhost:8080/axis/services/stock-wss-01
samples.stock.client
http://localhost:8080/axis/services/stock-wss-01?wsdl
endlocal
A bunch of java classes will be created under
samples/stock/client, including the StockQuoteServiceServiceLocator.
2. Write a simple java console application that uses the generated
service locator. For example:
package samples.stock.client;
import java.rmi.RemoteException;
import javax.xml.rpc.ServiceException;
public class StockServiceClient {
public StockServiceClient() {
}
public static void main(String[] args) throws
ServiceException, RemoteException {
if (args.length == 0) {
System.out.println("Usage:\njava StockServiceClient [symbol]");
return;
}
StockQuoteServiceService locator = new
StockQuoteServiceServiceLocator();
StockQuoteService service = locator.getStockWss01();
float quote = service.getQuote(args[0]);
System.out.println("stock quote service returned " +
args[0] + ": " + quote);
}
}
3. run the client:
java samples.stock.client.StockServiceClient XXX
If all went well, you should get the result:
stock quote service returned IBM: 55.25
When using "XXX" as parameter, the service won't try to go out on the
Internet to get the real quotes, but just returns a float with the
value of 55.25.
What you've created so far is a very simple web service with a simple
client that calls it. WSS4J has not been used yet, so this web service
call is unprotected. Now it's time to add a Username Token to the soap
call.
Configuring the Service for Username Token
1. Modify the deployment descriptor you created above to look like this:
<deployment xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
<service name="stock-wss-01" provider="java:RPC"
style="document" use="literal">
<requestFlow>
<handler type="java:org.apache.ws.axis.security.WSDoAllReceiver">
<parameter name="passwordCallbackClass" value="PWCallback"/>
<parameter name="action" value="UsernameToken"/>
</handler>
</requestFlow>
<parameter name="className" value="samples.stock.StockQuoteService"/>
<parameter name="allowedMethods" value="getQuote"/>
<parameter name="scope" value="application"/>
</service>
</deployment>
WSDoAllReceiver is an Axis handler located in wss4j.jar package.
This is the standard way to deploy an Axis handler. For more details
please refer to the Axis handler for WSS4J documentation.
2. Create a class named PWCallback.java and compile it and put the
resulting PWCallback.class file into your Axis WEB-INF/classes
directory. In this example I used the default package for simplicity,
but you might need to use the fully qualified class name (be
consistent with the deployment descriptor).
The following code snippet shows a simple password callback class:
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.ws.security.WSPasswordCallback;
public class PWCallback implements CallbackHandler {
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof WSPasswordCallback) {
WSPasswordCallback pc = (WSPasswordCallback)callbacks[i];
// set the password given a username
if ("wss4j".equals(pc.getIdentifer())) {
pc.setPassword("security");
}
} else {
throw new
UnsupportedCallbackException(callbacks[i], "Unrecognized Callback");
}
}
}
}
3. Redeploy the service using the bat file you created earlier.
Your service should now be expecting a WSS Username Token in the
incoming soap request, and clients should send the username "wss4j"
and password "security" to get through.
Configuring the Client for Username Token
1. run the client we created again:
java samples.stock.client.StockServiceClient IBM
You should now get an error:
Exception in thread "main" AxisFault
faultCode: {http://schemas.xmlsoap.org/soap/envelope/}Server.generalException
faultSubcode:
faultString: WSDoAllReceiver: Request does not contain required
Security header
This is because your client is not configured to send a Username
Token yet, so the service is rejecting the request. To fix this, you
need to create a callback class in the client, which adds the Username
Token to the outgoing soap request.
2. Create a deployment descriptor file (client_deploy.wsdd) for the client:
<deployment xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
<transport name="http"
pivot="java:org.apache.axis.transport.http.HTTPSender"/>
<globalConfiguration >
<requestFlow >
<handler type="java:org.apache.ws.axis.security.WSDoAllSender" >
<parameter name="action" value="UsernameToken"/>
<parameter name="user" value="wss4j"/>
<parameter name="passwordCallbackClass"
value="samples.stock.client.PWCallback"/>
<parameter name="passwordType" value="PasswordDigest"/>
</handler>
</requestFlow >
</globalConfiguration >
</deployment>
3. Create the samples.stock.client.PWCallback class:
package samples.stock.client;
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.ws.security.WSPasswordCallback;
/**
* PWCallback for the Client
*/
public class PWCallback implements CallbackHandler {
/**
* @see javax.security.auth.callback.CallbackHandler#handle(javax.security.auth.callback.Callback[])
*/
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof WSPasswordCallback) {
WSPasswordCallback pc = (WSPasswordCallback)callbacks[i];
// set the password given a username
if ("wss4j".equals(pc.getIdentifer())) {
pc.setPassword("security");
}
} else {
throw new
UnsupportedCallbackException(callbacks[i], "Unrecognized Callback");
}
}
}
}
4. Define the system property axis.ClientConfigFile for your client:
java -Daxis.ClientConfigFile=client_deploy.wsdd -classpath
$AXISCLASSPATH samples.stock.client.StockServiceClient
Make sure that your CLASSPATH includes the jar files under WEB-INF/lib.
Another way to do this is to specify the wsdd file in your
StockServiceClient to the service locator programmatically:
...
import org.apache.axis.EngineConfiguration;
import org.apache.axis.configuration.FileProvider;
...
EngineConfiguration config = new FileProvider("client_deploy.wsdd");
StockQuoteServiceService locator = new
StockQuoteServiceServiceLocator(config);
...
5. Run the client, you should get no errors:
stock quote service returned XXX: 55.25
Your client is now sending a Username Token in the wsse request
header with the username "wss4j" (see client_deploy.wsdd) and password
"security" (see the PWCallback implementation).
Another way to do this is to have the client application set the
username and CallbackHandler implementation programmatically instead
of using the client_deploy.wsdd file:
...
import org.apache.axis.client.Stub;
...
Remote remote = locator.getPort(StockQuoteService.class);
Stub axisPort = (Stub)remote;
axisPort._setProperty(UsernameToken.PASSWORD_TYPE,
WSConstants.PASSWORD_DIGEST);
axisPort._setProperty(WSHandlerConstants.USER, "wss4j");
axisPort._setProperty(WSHandlerConstants.PW_CALLBACK_REF, pwCallback);
where "pwCallback" is a reference to a PWCallback
implementation. See the Axis handler for WSS4J documentation for more
details on this.
UPDATE: I've tried to set the callback using the techinque
above, but without much success. I'll continue trying, and when I get
it working I'll update this section again :)
UPDATE 2: After some testing and teaking and good ideas from
people, I got the thing above working. It's all explained in another
blog post.
6. Try modifying your client's PWCallback to return the wrong
password, or send the wrong username. The service should reject your
requests.
Known problems
When I first ran this tutorial myself, I got a stacktrace error that
didn't interrupt the program, but printed out a warning about the
xalan.jar package. It looks something like this:
- Unable to patch xalan function table.
java.lang.NoSuchFieldException: m_functions
at java.lang.Class.getField(Unknown Source)
at org.apache.xml.security.Init.registerHereFunction(Init.java:429)
at org.apache.xml.security.Init.init(Init.java:124)? (and so on)
This may have to do with how the xalan.jar package is deployed on your
system and what version of xalan you use and the version of JDK. I got
the tip from Ashok Shah to make sure I use Java version 1.4.2_04
instead of 1.4.2_06 that I used. I've not tried this yet, but I will
do.
UPDATE: I tried to put the xalan.jar in the JAVA_HOME/lib/endorsed/
directory, but it didn't work much better. So I updated to JDK 5.0 and
made sure that the xalan.jar package from the WSS4J distribution was
available to tomcat and to my client, and behold - it works :)
UPDATE 2: I got a tip from Martin Stemplinger that the xalan.jar
should probably go into the JAVA_HOME/jre/lib/endorsed/ directory.
I've not tested it myself, but it sounds right.
(Picked from http://weblogs.asp.net/jdanforth/archive/2005/01/16/354060.aspx) |