This article is based on a chapter from JBoss in Action: Configuring the JBoss Application Server by Javid Jamae and Peter Johnson. It is being reproduced here by permission from Manning Publications. Manning early access books and ebooks are sold exclusively through Manning. For more information, visit www.manning.com/jamae/. |
In this article, we show you how to secure a web service using WS-Security. WS-Security is a specification from OASIS (Organization for the Advancement of Structured Information Standards, http://www.oasis-open.org) that describes enhancements to SOAP messaging to provide message integrity and confidentiality. It provides mechanisms that can be used to accommodate a wide variety of security models and encryption technologies. You will use WS-Security to encrypt the web message and sign it. You will do this in two steps, first securing the web service and then adding authentication.
For our example we use JBoss Web Services 2.0.x. JBoss Application Server 4.2.2 and the JBoss Application Server 5.0 beta and release candidate contain this version of JBoss Web Services.
First, you need a web service to secure. Listing 1 provides a simple POJO hello web service.
package org.jbia.ws; import javax.jws.*; @SOAPBinding(style=SOAPBinding.Style.RPC) @WebService public class Hello { @WebMethod public String sayHello(String name) return "Hello " + name; }
Listing 1: A simple hello web service
You’ll also need a web.xml, provided in listing 2.
<web-app> <servlet> <servlet-name>Hello</servlet-name> <servlet-class>org.jbia.ws.Hello</servlet-class> </servlet> <servlet-mapping> <servlet-name>Hello</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> </web-app>
Listing 2: The web.xml file for the hello web service
Compile the web service, place it and the web.xml file into a WAR file, and put the WAR file into the server/xxx/deploy directory, where xxx is the configuration directory name, such as default. Once the web service is deployed, you can generate the stubs required for the client by entering the following command:
<jboss_home>/bin/wsconsume –k http://localhost:8080/hello/hello?wsdl
The wsconsume utility places the stubs into a directory named output. Listing 3 provides a client of that web service. Place the client source file in the output/org/jbia/ws directory.
package org.jbia.ws; public class Client { public static void main(String[] args) { if (args.length > 0) { HelloService svc = new HelloService(); Hello hello = svc.getHelloPort(); for (int i = 0; i < args.length; i++) { System.out.println(hello.sayHello(args[i])); }}}}
Listing 3: A client for the hello web service
Compile the client along with the Java source files generated by the wsconsume utility. Then test the client by running the following command:
<jboss_home>/bin/wsrunclient –classpath output org.jbia.ws.Client Javid Peter
You should see this response:
Hello Javid Hello Peter
Now that you have a working web service and its client, we can show you how to secure it.
Encrypting web messages
If your web service transmits confidential information such as medical records, you’ll want to encrypt the message so that the contents can’t be monitored during transport. In this section, we show you how to encrypt the hello web service.
One of the unique aspects of encrypting a web service is that it can be done in two different ways. First, you can use SSL to transport messages using HTTPS. The mechanisms used to set this up are much the same as for using SSL with a web application. You can also use WS-Security; the contents of the message are encrypted by the JAX-WS implementation on both the client and the server. These two methods are illustrated in figure 1.
Figure 1: Web service requests and responses go though both the JAX-WS and transport layers, and thus either layer can be used to encrypt and decrypt the requests and responses.
The steps to encrypt the messages are to generate the security certificates and to configure the server and client to use those certificates. We walk you through all the steps to secure the web service, even the steps to generate the certificates.
Generating the certificate
A web service request and response consists of two messages, each of which has to be encrypted. This is illustrated in figure 1. Although you could use the same certificate in both cases, you usually wouldn’t want to do so in a production environment because it requires both the server and the client to have the same private key. Usually you want to keep your private key, well, private. Therefore, with a single client and a single server you need two certificates so that’s what you generate.
You need two keystores and two truststores. Each keystore contains its own certificate and the public key of the certificate in the other keystore. The truststores contain the public keys of their corresponding certificates. This configuration is illustrated in figure 2.
Figure 2: Note the relationships among the certificates stored in the keystores and truststores. The sender uses the receiver’s public key, which is stored in the keystore, to encrypt the message. The receiver uses its certificate, which contains both its public and private keys, to decrypt the message.
Here are the commands used to set up this configuration, using the keytool utility that ships with the JDK:
keytool -genkey -alias server -keyalg RSA -keystore server.keystore keytool -genkey -alias client -keyalg RSA -keystore client.keystore keytool -export -alias server -keystore server.keystore -file server_pub.key keytool -export -alias client -keystore client.keystore -file client_pub.key keytool -import -alias client -keystore server.keystore -file client_pub.key keytool -import -alias server -keystore client.keystore -file server_pub.key keytool -import -alias client -keystore client.truststore -file client_pub.key keytool -import -alias server -keystore server.truststore -file server_pub.key
When you’re creating the certificates (the first two commands), the keytool command asks for a password for both for the keystore and for the certificate. Remember the passwords you used. You’ll need them later.
Securing the server using WS-Security
You have to complete two steps: configure the server to use its keystore and truststore and configure the web service to use that configuration.
The jboss-wsse-server.xml file identifies the keystore and the truststore to the server. For a POJO web service, place this file into the WEB-INF directory; for an EJB web service, place it into the META-INF directory. In this file, you also indicate that you want messages to be encrypted. Listing 4 shows the contents of the file.
Listing 4: Encryption-related security configuration file: jboss-wsse-server.xml
<jboss-ws-security xmlns_xsi="http://www.w3.org/2001/XMLSchema-instance" xsi_schemaLocation="http://www.jboss.com/ws-security/config http://www.jboss.com/ws-security/schema/jboss-ws-security_1_0.xsd"> // Reference #1 <key-store-file>WEB-INF/server.keystore</key-store-file> // Reference #2 <key-store-password>password</key-store-password> // Reference #3 <key-store-type>jks</key-store-type> // Reference #4 <trust-store-file>WEB-INF/server.truststore</trust-store-file> // Reference #5 <trust-store-password>password</trust-store-password> // Reference #6 <trust-store-type>jks</trust-store-type> <key-passwords> // Reference #7 <key-password alias="server" password="serverpwd" /> </key-passwords> <config> // Reference #8 <encrypt type="x509v3" alias="client" /> <requires> // Reference #9 <encryption /> </requires> </config> </jboss-ws-security>
The locations of the keystore (#1) and truststore (#4) files are relative to the base directory of the WAR file. The keystore and truststore use the same password (#3, #6); you probably want to use stronger passwords. The <key-store-type> (#2) and <trust-store-type> (#5) default to JKS, so you could leave these tags out. The server key password is provided by the <key-passwords> tag (#7) because that password is used to access the server certificate in the keystore. The <encryption/> tag (#9) requests that the message be encrypted using the alias provided by the <encrypt> tag (#8). The client’s public key is used to encrypt the message on the server and is decrypted at the client using the client’s private key from the client’s keystore.
Add the @EndpointConfig annotation to the Hello class to indicate that you want to use WS-Security. Listing 5 is an excerpt from the updated Hello class, highlighting the added lines.
Listing 5: Encryption-related changes to the client
... // Reference #1 import org.jboss.ws.annotation.EndpointConfig; ... // Reference #2 @EndpointConfig(configName="Standard WSSecurity Endpoint") public class Hello {...}
The import statement imports the annotation class (#1), and the configName element identifies the configuration you want to use (#2). The valid configurations can be found in the file server/xxx/deploy/jbossws.sar/META-INF/standard-jaxws-endpoint-config.xml. Listing 6 is an excerpt from that file, showing the Standard WSSecurity Endpoint configuration.
Listing 6: Endpoint handler configuration file: standard-jaxws-endpoint-config.xml
<jaxws-config ...> ... <endpoint-config> // Reference #1 <config-name>Standard WSSecurity Endpoint</config-name> <post-handler-chains> <javaee:handler-chain> <javaee:protocol-bindings> ##SOAP11_HTTP </javaee:protocol-bindings> <javaee:handler> <javaee:handler-name> WSSecurity Handler </javaee:handler-name> <javaee:handler-class> // Reference #2 org.jboss.ws.extensions.security.jaxws. WSSecurityHandlerServer </javaee:handler-class> </javaee:handler> </javaee:handler-chain> </post-handler-chains> </endpoint-config> </jaxws-config>
The configuration name given here (#1) matches the configuration name used in the EndpointConfig annotation. The WSSecurityHandlerServer class (#2) handles the encryption and decryption of the messages.
You can add other handler chains to this configuration and even write your own handler by extending the org.jboss.ws.core.jaxws.handler.GenericSOAPHandler. Such a handler has access to and can manipulate the full SOAP message.
Now that you have all the files, you can package them into the hello.war file, as shown in figure 3, and deploy the WAR file.
Figure 3: Here are the contents of hello.war when using WS-Security.
Note that the standard-jaxws-endpoint-config.xml file isn’t included in the WAR file; it’s picked up from its default location. If you’d like to place that file into the WAR file, you could provide the location using the configFile element on the @EndpointConfig annotation. Once the WAR file deploys, you can access the WSDL file through a browser using the URL http://localhost:8080/jbossws/services.
Securing the client using WS-Security
The client source files don’t require any changes to encrypt the message. The only thing you have to do is configure WS-Security. You use two files to correspond to the two configuration files used for the server.
First, provide the information regarding the keystore and truststore. You can do this by creating a jboss-wsse-client.xml file and placing the necessary information into it, as shown in listing 7.
Listing 7: Client configuration file: jboss-wsse-client.xml
<?xml version="1.0" encoding="UTF-8"?> <jboss-ws-security xmlns_xsi="http://www.w3.org/2001/XMLSchema-instance" xsi_schemaLocation="http://www.jboss.com/ws-security/config http://www.jboss.com/ws-security/schema/ jboss-ws-security_1_0.xsd"> <key-store-file>META-INF/client.keystore</key-store-file> <key-store-password>password</key-store-password> <key-store-type>jks</key-store-type> <trust-store-file> META-INF/client.truststore </trust-store-file> <trust-store-password>password</trust-store-password> <trust-store-type>jks</trust-store-type> <key-passwords> // Reference #1 <key-password alias="server" password="clientpwd" /> </key-passwords> <config> <encrypt type="x509v3" alias="server"/> <requires> <encryption/> </requires> </config> </jboss-ws-security>
The contents of this file look similar to that used by the server, the only difference being that the keystore and truststore are located in the META-INF directory. The server public key (#1) is used to encrypt the message, which is decrypted at the server using the server’s private key.
You can leave out the information about the keystore, truststore, their passwords, and types, and provide that information using the following system properties:
- org.jboss.ws.wsse.keyStore
- org.jboss.ws.wsse.keyStorePassword
- org.jboss.ws.wsse.keyStoreType
- org.jboss.ws.wsse.trustStore
- org.jboss.ws.wsse.trustStorePassword
- org.jboss.ws.wsse.trustStoreType
If you specify this information both in the configuration file and as system properties, the configuration file takes precedence. Additionally, because the same class handles the jboss-wsse-client.xml and jboss-wsse-server.xml files, the system properties could be used for the server also. Because the server might serve multiple Web Services, each with their own WS-Security configuration, it makes sense that the settings in the configuration file take precedence over the system properties.
You have to state that you want to use WS-Security by creating a META-INF/standard-jaxws-client-config.xml file. An example of this file can be found at server/xxx/deploy/jbossws.sar/META-INF/standard-jaxws-client-config.xml. Copy this file to your project and edit it, removing the configurations that you don’t want. The only configuration you should leave is Standard WSSecurity Client, as shown in listing 8.
Listing 8: Client configuration file: standard-jaxws-client-config.xml
<?xml version="1.0" encoding="UTF-8"?> <jaxws-config xmlns_xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns_javaee="http://java.sun.com/xml/ns/javaee" xsi_schemaLocation="urn:jboss:jaxws-config:2.0 jaxws-config_2_0.xsd"> <client-config> <config-name>Standard WSSecurity Client</config-name> <post-handler-chains> <javaee:handler-chain> <javaee:protocol-bindings> ##SOAP11_HTTP </javaee:protocol-bindings> <javaee:handler> <javaee:handler-name> WSSecurityHandlerOutbound </javaee:handler-name> <javaee:handler-class> // Reference #1 org.jboss.ws.extensions.security.jaxws. WSSecurityHandlerClient </javaee:handler-class> </javaee:handler> </javaee:handler-chain> </post-handler-chains> </client-config> </jaxws-config>
The WSSecurityHandlerClient (#1) is the client-side handler that corresponds to the WSSecurityHandlerServer server-side handler. Both of these classes defer to the WSSecurityHandler class to handle the messages.
All that’s left to do is package the files into a JAR file as illustrated in figure 4.
Figure 4: Here are the contents of the client.jar file when using WS-Security.
Once you have the JAR file, you can run the client, once again using wsrunclient. It should work. You can verify that
the messages are encrypted by turning on message tracing. Uncomment the Enable JBossWS message tracing entry in the jboss-log4j.xml file before starting the application server. Then look for the org.jboss.ws.core.MessageTrace entries in the server.log file.
Signing the messages using WS-Security
WS-Security provides a mechanism to sign a message, providing an alternate means of authenticating the user. To illustrate how this works, we modify the example that encrypts messages.
For signing a message, the sender uses his or her private key, and the receiver uses the sender’s public key to verify the sender’s identity. This means that the client’s public key must be in the server’s truststore and the server’s public key must be in the server’s truststore. This configuration is illustrated in figure 5.
Figure 5: Here are the relationships among the key and trust stores for signing messages. The only difference between this and figure 1 is that the other system’s public key has been added to the truststore.
Assuming that the keystores and truststores are already set up for encryption, here are the additional commands used to create this configuration:
keytool -import -alias server -keystore client.truststore -file server_pub.key keytool -import -alias client -keystore server.truststore -file client_pub.key
Once the keys are set up, you must modify the configuration files to use the keys to sign the messages. Listing 9 shows an excerpt from the updated jboss-wsse-server.xml file.
Listing 9: WS-Security configuration file, jboss-wsse-server.xml, changes
<jboss-ws-security ...> ... <config> // Reference #1 <sign type="x509v3" alias="server" /> <encrypt type="x509v3" alias="client" /> <requires> <signature /> <encryption /> </requires> </config> </jboss-ws-security>
The server key is used to sign messages sent by the server (#1). The keystore and truststore-related settings are the same as for the earlier encryption example; only the two lines identified were added.
The changes to the jboss-wsse-client.xml file are similar, as shown in listing 10.
Listing 10: WS-Security configuration file, jboss-wsse-client.xml, changes
<jboss-ws-security ...> ... <config> // Reference #1 <sign type="x509v3" alias="client" /> <encrypt type="x509v3" alias="server" /> <requires> <signature /> <encryption /> </requires> </config> </jboss-ws-security>
In this case the client key is used to sign the messages (#1).
Package up the server and deploy it, package up the client, and then run the client. The messages are now signed. You can verify this by looking at the SOAP messages in the server.log file (after turning on message tracing as indicated at the end of section 9.5.3); you’ll see a <ds:Signature> entry has been added to the message.
Summary
So there you have it, a simple web service secured using WS-Security. Even though the web service we used was a POJO, you can use the same steps to secure an EJB-based web service. Use the same configuration files, placing them in the META-INF directory instead of the WEB-INF directory.
About the Authors
Javid Jamae started his career in software in the mid 90s programming in C, C++, and PERL, but quickly moved to Java programming. He is a certified JBoss instructor and teaches courses in Hibernate and the JBoss Application Server. Javid is also an Agile evangelist and spends a large portion of his time transforming, coaching, and training organizations in using Agile methodologies.
Peter Johnson started working in Java in 1998 and was lead designer on projects such as a JDBC driver for the DMSII database that runs on Unisys mainframes. He is the chief architect on a team that analyzes Java applications and evaluates various open source software for enterprise readiness. Peter is a JBoss committer, working on the new admin console.