Configuring Web Services

The annotation support introduced in Java SE 5.0 and embraced by a wide variety of Java technologies makes creating and consuming Web Services in Java easy. Web Services are defined by using the Java API for XML-Based Web Services (JAX-WS) as delineated in JSR-181.

A web service is nothing more than a remote method invocation (RMI) performed over HTTP using a text-based (XML document) transport mechanism. There are two key concepts to Web Services

A web service is a collection of endpoints, each endpoint is implemented in Java as a class. An endpoint ca contain one or more web methods. You can use an interface to define an endpoint and use a class to implement that endpoint. The endpoint interface is always used on the client side to construct a proxy that can marshall the arguments to the web method and unmarshall the result. The Web Services Description Language (WSDL) file is an XML document that describes the web service. The Universal Description, Discovery and Integration (UDDI) registry is a mechanism used to publish Web Services (think of it as a phone book).

Simple Object Access Protocol (SOAP) is a protocol that enables the exchange of data between to heterogeneous system (different types of systems). It provides two different SOAP binding styles

Document this style of web services tends to be coarse grained, the client packages up all the information into a single object which is then passed to the web method. The web method has all the information to perform the task. Document-style calls tend to asynchronous, the client makes the call and then goes off to do other things, it then checks later to see if there was a response to the call or registers to be notified when the response comes in.
Remote Procedure Call (RPC) clients typically pass numerous parameters to a web method, and those parameters typically use simple data types such as Strings and integers. These web services tend to chatty or fine grained, in that the client calls on the service frequently to perform a single task.

Developing a Web Service

The example is a simple service that returns the sales tax for a purchase on the customers state. You input the two-character postal state code and the service will return the sales rate tax. There will also be two clients one written in Java and the other in C#.

There are two approaches to developing a web service

Top-Down approach You first develop the WSDL and use a utility such as wsconsume to generate the necessary glue code and stubs. You then fill in the code for the business logic in the stub classes. This approach works best when you are collaborating with various other entities to define web services because WSDL becomes the contract between those involved.
Bottom-up approach You code the web services first then generate the WSDL from the web service. You can generate the WSDL using the utility wsprovide or you can package the web service and deploy it. The Web Services deployer will automatically generate the WSDL. This approach works best if you are defining a web service that you would like others to use and there is no preexisting WSDL.

Bottom-Up Approach

This example uses the bottom-up approach

SalesTax.java

package org.jbia.ws;

import java.util.HashMap;
import javax.jws;

@WebService
public class SalesTax {
  private HashMap<String, Double> tax;
  public SalesTax() {init();}
  
  public void init() {
    tax = new HashMap<String, Double>();
    tax.put("CA", 7.75);
    tax.put("NH", 0.0);
  }

  @WebMethod
  public double getRate(String state) {
    Double rate = tax.get(state);
    if (rate == null) rate = -1.0;
    return rate;
  }
}

Note: @WebService and @WebMethod define the web service and what methods it supports, this web service is a POJO and not a EJB but it could easy be.

Change the SOAP Binding

package org.jbia.ws;

import java.util.HashMap;
import javax.jws;
import javax.jws.soap.SOAPBinding;

@SOAPBINDING(Style=SOAPBINDING.Style.RPC)
@WebService()
public class SalesTax {
  private HashMap<String, Double> tax;
  public SalesTax() {init();}
  
  public void init() {
    tax = new HashMap<String, Double>();
    tax.put("CA", 7.75);
    tax.put("NH", 0.0);
  }

  @WebMethod
  public double getRate(String state) {
    Double rate = tax.get(state);
    if (rate == null) rate = -1.0;
    return rate;
  }
}

web.xml <web-app>
  <servlet>
    <servlet-name>SalesTax</servlet-name>
    <servlet-class>org.jbia.ws.SalesTax</servlet-class>
  </servlet>
  </servlet-mapping>
    <servlet-name>SalesTax</servlet-name>
    <url-pattern>/tax</uri-pattern>
  </servlet-mapping>
</web-app>

Now create a salestax.war file as detailed below, and deploy to server/xxx/deploy directory, the application server will automatically create the WSDL file.

You can create the WSDL manually and include it in your .war file

manually create the WSDL file wsprovide -o wsgen -c XXX -w org.jbia.ws.SalesTax

Note:
-o = the output goes in the wsgen directory
-c = provides the class path where you can find the endpoint class, SalesTax in this case
-w = indicates to generate a WSDL file

If creating the WSDL manually you need to edit the file and confirm that the URL for the web service is correct.

There are a number utilities provided by the application server

wsconsume Generates stubs or interfaces from a WSDL file, used in a top-down approach
wsprovide Generates a WSDL file from web services classes, used in bottom-up approach
wsrunclient Runs a web service client and provides the necessary class path for that client
wstools Script used for JSR-109 web services development.

Top-Down Approach

The next example is the same but using a top-down approach, you start with the WSDL file, you then run wsconsume to generate the class stubs from the WSDL and provide the business logic for the web methods.

 

wsconsume -o stubs -k wsgen/SalesTaxService.wsdl

Note:
-o = causes the generated files to be placed in the directory named stubs
-k = indicates that the generated Java source files are to be kept

A SalesTax.java file will have been created, and you need to change this file

Simple web service with top-down changes package org.jbia.ws;

import java.util.HashMap;
import javax.jws.*;

@WebService(endpointInterface="org.jbia.ws.SalesTax",
            portName="salesTaxPort",
            wsdlLocation="WEB-INF/wsdl/SalesTaxService.wsdl"
)
public class SalesTaxImpl implements salesTax {
  private HashMap<String, Double> tax;
  public SalesTaxImpl() { ... }
  public void init() { ... }
  public double getRate(String state) { ... }
}

The web.xml file will have to change

web.xml <web-app>
  <servlet>
    <servlet-name>SalesTax</servlet-name>
    <servlet-class>org.jbia.ws.SalesTaxImpl</servlet-class>
  </servlet>
  </servlet-mapping>
    <servlet-name>SalesTax</servlet-name>
    <url-pattern>/tax</uri-pattern>
  </servlet-mapping>
</web-app>

Creating the Client

The client is a simple command-line application, first you need to generate the stubs for the client from WSDL, make sure the application has been deployed and the server running

Generate the stubs wsconsume http://localhost:8080:salestax/tax?wsdl

The stub files will be used when you compile the client application so make sure they are in the classpath

Java client code package org.jbia.ws;

public class Client {
  public static void main(String[] args) {
    if (args.length() > 0) {
      SalesTaxService svc = new SalesTaxService();
      SalesTax tax = svc.getSalesTaxPort();
      
      for(int i = 0; i < args.length(); i++) {
        double rate = rate.getRate(args[i]);
        System.out.println("Sales tax for " + args[i] + " is " + rate);
      }
    }
  }
}

Now to run the client application

Run the client wsrunclient -classpath $JBOSS_HOME/client/jbossall-client.jar:. ./client.jar org.jbia.ws.Client CA NH TX

A c# client would look like below

C# client code using System;
using System.Collections.Generic;
using System.Text;
using TaxClient.salestax;

namespace org.jbia.ws {
  class Client {
    static void Main(string[] args) {
      if (args.Length == 0) {
        Console.WriteLine("usage: TaxClient <list-of-states>");
      } else {
        SalesTaxService svc = new SalesTaxService();
        
        for (int i = 0; i < args.Length; i++) {
          getRate rr = new getRate();
          rr.arg0 = args[i];
          getRateResponse resp = svc.getRate(rr);
          double rate = resp.@return;
          Console.WriteLine("Sales tax for " + args[i] + " is " + rate);
        }
      }
    }
  }
}

JBoss Annotations

There are three JBoss specific annotations that you should know about

The @WebContext annotation can have a number of elements

Element Name Default Value Description
contextRoot
Name of the JAR or EAR file
The context used in the URL to access the web service.
virtualHosts
-none-
Specifies the virtual hosts to which the web service is to be bound
urlPattern
name of the class
The name appended to the context root to form the full URL
authMethod
-none-
Identifies if the client needs to be authenticated to use the web service
transportGuarantee
NONE

The level of the transport mechanism

  • NONE - data is passed as plain text
  • INTEGRAL- data cannot be modified in transit
  • CONFIDENTIAL - data is encrypted before being transmitted
secureWSDLAccess
True
indicates if authentication is also required to access the WSDL, only used if the endpoint is secure.

The annotations only come into play if the endpoints is a EJB, to convert the SalesTax POJO web service into an EJB we do the following

convert SalesTax to a EJB import org.jboss.wsf.spi.annotation.WebContext;
import javax.ejb.Stateless;

@Stateless
@WebContext(contextRoot="/salestax", url Pattern="/tax")
@SOAP Binding(style=SOAPBinding.Style.RPC)
@WebService()
public class SalesTax { ... }

With this setup you package it as a EJB JAR file and you do not need the web.xml file.

The org.jboss.ws.annotation.EndpointConfig annotation is used to identify the configuration to use with the endpoint. It has a number of elements and attributes

EndpointConfig Elements
Element Default Description
configName -none- identifies the configuration to use
configFile server/xxx/deploy/jbossws.sar/META-INF/standard-jaxws-endpoint-config.xml identifies the file containing the endpoint configurations
EndpointConfig Attributes
Attribute Description
<config-name> Identifies the configuration, this name is used in the configName element of the EndpointConfig annotation
<pre-handler-chains>

Identifies code that will process the message before its passed to the endpoint, typical handlers include

  • Addressing service handler - adds the addressing information to the message context as the value for JAXWSAConstants.SERVER_ADDRESSING_PROPERTIES_INBOUND property
  • security handler - handles access control
<post-handler-chains> Identifies code that processes the result after the endpoint has responded to the message and before the response is returned to the client
<features> Identifies particular features to use
<property> Used to identify name/value pairs of properties

Securing a Web Service

Securing a web service means authorization, authentication and encryption, JBoss uses the same mechanism as Tomcat, it uses security realms. The file server/xxx/conf/login-config.xml has a number of realms already defined, you can of course copy an existing realm and tailor it to your needs, I already a topic on this in securing web application so i am not going to repeat myself here.

In order for the above example to access a secure web service you need to add additional lines of code, that will supply the credentials needed to authenticate

Security changes to Java client code

...
SalesTax tax = svc.getSalesTaxPort();

BindingProvider bp = (BindingProvider) tax;
Map<String, Object> rc = bp.getRequestContext();
rc.put(BindingProvider.USERNAME_PROPERTY, "<username>");
rc.put(BindingProvider.PASSWORD_PROPERTY, "<password>");

for (int i = 0; i < args.Length; i++) { .. }
...

Note: you need to change the username and password to reflect what you configured in the realm.

The BindingProvider interface owns a map containing properties used for the request where you set the username and password. Recompile the code and run it, hopefully in the log file you should see org.jboss.security.auth.spi.UserRolesLoginModule entries.

To make the EJB secure you only have to make three changes

securing the EJB ...
@WebService()
@WebContext(contextRoot="/salestax",
            urlPattern="/tax",
            authMethod="BASIC",
            secureWSDLAccess = false)
@SecurityDomain(value = "JBossWS")
@Stateless
public class SalesTax { ... }

The authmethod corresponds to the <auth-method> tag in the web.xml for the POJO web service. The secureWSDLAccess element is set to false so that the client and others can access the WDSL without supplying credentials. The @SecurityDomain annotation identifies the name of the login module used in the server/xxx/conf/login-config.xml file.

Encrypting SOAP Messages

You need to setup two keystores and two truststores and each keystore contains its own certificate and the public key of the certificate in the other keystore.The truststore contain the public keys of their corresponding certificates.

I have already discussed how to create certificates in securing applications, so I point you in that direction rather that repeat myself. Once you have the created the certificates, keystores and truststores you need to perform two steps

The jboss-wsse-server.xml file identifies the keystore and truststore to the server, for a POJO web service the file needs to go in the WEB-INF directory, for a EJB web service you need to place it in the META-INF directory.

jboss-wsse-server.xml <jboss-ws-security
  xmnls="http://www.jboss.com/ws-security/config"
  xmnls:xsi="http://ww.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>WEB-INF/server.keystore</key-store-file>
  <key-store-type>jks</key-store-type>
  <key-store-password>password</<key-store-password>
  
  <trust-store-file>WEB-INF/server.truststore</trust-store-file>
  <trust-store-type>jks</trust-store-type>
  <trust-store-password>password</<trust-store-password>

  <key-passwords>
    <key-password alias="server" password="serverwd" />
  </key-passwords>

  <config>
    <encrypt type="x509v3" alias="client" />
    <requires>
      <encryption />
    </requires>
  </config>
</jboss-ws-security>

Most of the file above is self explaining the <encryption /> tag requests that the message be encrypted using the alias in the <encrypt> tag. You can also provide <signature /> and <sign> tags to perform authentication.

You then need to add the @EndpointConfig annotation to the SalesTax class to indicate that you want to use WS-Security

changes to the client ...
import org.jboss.ws.annotation.EndpointConfig;
...
@EndpointConfig(configName="Standard WSSecurity Endpoint")
public class SalesTax { ... }

Valid configurations can be found in server/xxx/deployers/jbossws.deployer/META-INF/standard-jaxws-endpoint-config.xml, notice the config-name matches.

standard-jaxws-endpoint-config.xml

</jaxws-config>
  ...
  <endpoint-config>
    <config-name>Standard WSSecurity Endpoint</config-name>
    <post-handler-chains>
      <javaee:handler-chain>
        <javaee:protocol-bindings>##SOAP11_HTTP ##SOAP11_HTTP_MTOM</javaee:protocol-bindings>
        <javaee:handler>
          <javaee:handler-name>WSSecurity Handler</javaee:handler-name>
          <javaee:handler-class>org.jboss.ws.extensions.security.jaxws.WSSecurityHandlerServer</javaee:handler-class>
        </javaee:handler>
        <javaee:handler>
          <javaee:handler-name>Recording Handler</javaee:handler-name>
          <javaee:handler-class>org.jboss.wsf.framework.invocation.RecordingServerHandler</javaee:handler-class>
        </javaee:handler>
       </javaee:handler-chain>
    </post-handler-chains>
  </endpoint-config>
</jaxws-config>

Note: the WSSecurityHandlerServer class handles the encryption and decryption

Once all the above has been completed you should package the POJO application into a WAR file as below

The steps to encrypting an EJB service is similar but should be packaged as a JAR file as below