After declaring the handler class, you need to instantiate it and add the instance to the client's or Web service's handler chain, either via the @HandlerChain
annotation or programmatically. Listing 5 demonstrates the programmatic approach by adding a SOAPLoggingHandler
instance to UCClient
's (see Part 2) handler chain.
Listing 5. Adding a SOAPHandler
instance to UCClient
's handler chain
import java.net.URL;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.ws.Binding;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Service;
import javax.xml.ws.handler.Handler;
import ca.javajeff.uc.UC;
public class UCClient
{
public static void main(String[] args) throws Exception
{
URL url = new URL("http://localhost:9901/UC?wsdl");
QName qname = new QName("http://uc.javajeff.ca/",
"UCImplService");
Service service = Service.create(url, qname);
qname = new QName("http://uc.javajeff.ca/", "UCImplPort");
UC uc = service.getPort(qname, UC.class);
// UC uc = service.getPort(UC.class);
BindingProvider bp = (BindingProvider) uc;
Binding binding = bp.getBinding();
List<Handler> hc = binding.getHandlerChain();
hc.add(new SOAPLoggingHandler());
binding.setHandlerChain(hc);
System.out.printf("DC to DF: 37 DC = %f DF%n", uc.c2f(37.0));
System.out.printf("CM to IN: 10 CM = %f IN%n", uc.cm2in(10));
System.out.printf("DF to DC: 212 DF = %f DC%n", uc.f2c(212.0));
System.out.printf("IN to CM: 10 IN = %f CM%n", uc.in2cm(10));
}
}
Listing 5's main()
method accesses UCClient
's handler chain and inserts an instance of SOAPLoggingHandler
into this chain by completing the following steps:
- Cast the proxy instance returned from
getPort()
tojavax.xml.ws.BindingProvider
because the proxy instance's class implements this interface.BindingProvider
provides access to the protocol binding and associated context objects for request and response message processing. - Call
BindingProvider
'sBinding getBinding()
method to return the protocol binding instance, which is an instance of a class that ultimately implements thejavax.xml.ws.Binding
interface – the class actually implementsBinding
'sjavax.xml.ws.soap.SOAPBinding
subinterface. - Invoke
Binding
'sList<Handler> getHandlerChain()
method on this instance to return a copy of the handler chain. - Instantiate
SOAPLoggingHandler
and add this instance to thejava.util.List
instance ofHandler
instances. - Pass this list of handlers to
Binding
'svoid setHandlerChain(List<Handler> chain)
method.
Compile the contents of Listing 5 (see Part 2). Assuming that UCPublisher
is running (see Part 2), run UCClient
(see Part 2). You should observe the following output (reformatted for readbility):
Outbound message:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<S:Body xmlns:ns2="http://uc.javajeff.ca/">
<ns2:c2f>
<arg0>37.0</arg0>
</ns2:c2f>
</S:Body>
</S:Envelope>
Inbound message:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<S:Body xmlns:ns2="http://uc.javajeff.ca/">
<ns2:c2fResponse>
<return>98.6</return>
</ns2:c2fResponse>
</S:Body>
</S:Envelope>
DC to DF: 37 DC = 98.600000 DF
Outbound message:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<S:Body xmlns:ns2="http://uc.javajeff.ca/">
<ns2:cm2in>
<arg0>10.0</arg0>
</ns2:cm2in>
</S:Body>
</S:Envelope>
Inbound message:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<S:Body xmlns:ns2="http://uc.javajeff.ca/">
<ns2:cm2inResponse>
<return>3.937007874015748</return>
</ns2:cm2inResponse>
</S:Body>
</S:Envelope>
CM to IN: 10 CM = 3.937008 IN
Outbound message:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<S:Body xmlns:ns2="http://uc.javajeff.ca/">
<ns2:f2c>
<arg0>212.0</arg0>
</ns2:f2c>
</S:Body>
</S:Envelope>
Inbound message:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<S:Body xmlns:ns2="http://uc.javajeff.ca/">
<ns2:f2cResponse>
<return>100.0</return>
</ns2:f2cResponse>
</S:Body>
</S:Envelope>
DF to DC: 212 DF = 100.000000 DC
Outbound message:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<S:Body xmlns:ns2="http://uc.javajeff.ca/">
<ns2:in2cm>
<arg0>10.0</arg0>
</ns2:in2cm>
</S:Body>
</S:Envelope>
Inbound message:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<S:Body xmlns:ns2="http://uc.javajeff.ca/">
<ns2:in2cmResponse>
<return>25.4</return>
</ns2:in2cmResponse>
</S:Body>
</S:Envelope>
IN to CM: 10 IN = 25.400000 CM
The S:
and ns2:
namespace prefixes are generated by JAX-WS.
Creating a customized lightweight HTTP server for authentication
You can create a customized lightweight HTTP server that offers additional features for testing a Web service, and replace the default lightweight HTTP server that's started in response to an Endpoint.publish()
invocation with your server. What makes this possible is that Endpoint
's void publish(Object serverContext)
method can accept as its argument an instance of a class that subclasses the abstract com.sun.net.httpserver.HttpContext
class.
This section shows you how to create a customized Lightweight HTTP Server for the purpose of demonstrating basic authentication with a Web service.
Authentication 101
Authentication is the process or action of verifying the identity of a user or a process. RFC 1945: Hypertext Transfer Protocol – HTTP/1.0 introduced support for authentication via a simple challenge-response mechanism that a server can use to challenge a client's request to access some resource. A client can use this mechanism to provide credentials (typically username and password) that authenticate (prove) the client's identity. When the supplied credentials satisfy the server, the user is authorized (allowed) to access the resource.
HTTP 1.0 introduced the basic authentication scheme by which a client identifies itself via a username and a password. The basic authentication scheme works as follows:
- The
WWW-Authenticate
header specifiesBasic
as the token and a singlerealm="quoted string"
pair that identifies the realm (a protected space to which a resource belongs, such as a specific group of Web pages) referred to by the browser address. - In response to this header, the browser displays a dialog box in which a username and password are entered.
- Once entered, the username and password are concatenated into a string (a colon is inserted between the username and password), the string is base64-encoded, and the result is placed in an
Authorization
header that's sent back to the server. - The server base64-decodes these credentials and compares them to values stored in its username/password database. When there's a match, the application is granted access to the resource (and any other resource belonging to the realm).
Java supports basic authentication via the abstract java.net.Authenticator
and java.net.PasswordAuthentication
classes. Authenticator
represents an object that knows how to obtain authentication for a network connection. This task is performed by overriding its PasswordAuthentication getPasswordAuthentication()
method, which returns the password and user name supplied by the user or null
when no such information is provided.
Demonstrating basic authentication via a customized lightweight HTTP server
Suppose you want to test basic authentication with the UC Web service introduced in Part 2 of this series. On the client side, you install a default authenticator that supplies a username and password to the Web service. Listing 6 reveals this authenticator in the context of UCClient
.
Listing 6. Supporting basic authentication with the UCClient
application
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.ws.Binding;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Service;
import javax.xml.ws.handler.Handler;
import ca.javajeff.uc.UC;
public class UCClient
{
public static void main(String[] args) throws Exception
{
Authenticator auth;
auth = new Authenticator()
{
@Override
protected PasswordAuthentication getPasswordAuthentication()
{
return new PasswordAuthentication("x", new char[] { 'y' });
}
};
Authenticator.setDefault(auth);
URL url = new URL("http://localhost:9901/UC?wsdl");
QName qname = new QName("http://uc.javajeff.ca/",
"UCImplService");
Service service = Service.create(url, qname);
qname = new QName("http://uc.javajeff.ca/", "UCImplPort");
UC uc = service.getPort(qname, UC.class);
// UC uc = service.getPort(UC.class);
BindingProvider bp = (BindingProvider) uc;
Binding binding = bp.getBinding();
List<Handler> hc = binding.getHandlerChain();
hc.add(new SOAPLoggingHandler());
binding.setHandlerChain(hc);
System.out.printf("DC to DF: 37 DC = %f DF%n", uc.c2f(37.0));
System.out.printf("CM to IN: 10 CM = %f IN%n", uc.cm2in(10));
System.out.printf("DF to DC: 212 DF = %f DC%n", uc.f2c(212.0));
System.out.printf("IN to CM: 10 IN = %f CM%n", uc.in2cm(10));
}
}
For simplicity, Listing 6 embeds x
as the username and y
as the password in the source code. A more useful and secure application would prompt for this information. At runtime the JVM invokes getPasswordAuthentication()
to obtain these credentials and make them available to the HTTP server when requested to do so.
This method will not be called if the HTTP server doesn't make a request, and Part 2's version of UCPublisher
will never cause the HTTP server to make this request. However, you can install a customized server that will result in this request, and Listing 7 presents an enhanced UCPublisher
application that accomplishes this task.
Listing 7. Supporting basic authentication with the UCPublisher
application
import java.io.IOException;
import java.net.InetSocketAddress;
import javax.xml.ws.Endpoint;
import com.sun.net.httpserver.BasicAuthenticator;
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpServer;
import ca.javajeff.uc.UCImpl;
public class UCPublisher
{
public static void main(String[] args) throws IOException
{
HttpServer server = HttpServer.create(new InetSocketAddress(9901), 0);
HttpContext context = server.createContext("/UC");
BasicAuthenticator auth;
auth = new BasicAuthenticator("myAuth")
{
@Override
public boolean checkCredentials(String username, String password)
{
return username.equals("x") && password.equals("y");
}
};
context.setAuthenticator(auth);
Endpoint endpoint = Endpoint.create(new UCImpl());
endpoint.publish(context);
server.start();
}
}
The main()
method first creates an HttpServer
instance that describes an HTTP server connected to port 9901 of the local host. This method next creates the /UC
context, and returns the resulting HttpContext
subclass object.
Continuing, the abstract com.sun.net.httpserver.BasicAuthenticator
class is anonymously subclassed to describe a server side implementation of HTTP basic authentication; its boolean checkCredentials(String username, String password)
method is called to verify the given name and password in the context of the basic authenticator's realm. This method returns true
for valid credentials, and false
when they are invalid.