Archive

Posts Tagged ‘JAX-WS’

How to develop a standalone SSL web service

12/08/2010 14 comments

How to develop a standalone SSL web service

Hi! Today, I´m gonna show you how to develop a simple SSL web service. Please note that this is not a “enterprise” way of developing a secure web service, but this may be very useful for testing purposes.

Constraints

To develop this service, you need to use a Sun/Oracle JVM (I tested with 1.6 VM, but should work with 1.5, too). This is not gonna work in any other kind of VM. Also, you need to have JAX-WS in your classpath.

The code

The code begins with the development of the web service itself. I used a standard JAX-WS service as an example, so my service looks like this:


@WebService
public class SOAPService {

	public String test() {
		return "Hello, SSL world!";
	}	
}

Next, we may use Java´s Endpoint class to create our object as an web service. The code is like

Endpoint endpoint = Endpoint.create(new SOAPService());

At this point, you need to create the server, and to create the server, you need a .jks (Java Keystore) file. You may create this file using JDK´s (or JRE´s) tooling, but I prefer using a GUI to do so. Personally, I like Lazgo´s KeyStore Explorer (available here). I won´t get in details here on how to create the JKS file; if you don´t know how to do it, you may want to have a look here.

Note: be aware that the CN attribute of your certificate must be equal to your host´s name!

After creating the JKS file, we are ready to create our HTTPS server. I won´t explain this code very much, as it should be pretty self-explanatory:


public static void main(String[] args) throws Exception {
		Endpoint endpoint = Endpoint.create(new SOAPService());
		SSLContext ssl =  SSLContext.getInstance("SSLv3");
		
		
		KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 
		KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());


		//Load the JKS file (located, in this case, at D:\keystore.jks, with password 'test'
		store.load(new FileInputStream("D:\\keystore.jks"), "test".toCharArray()); 

		//init the key store, along with the password 'test'
		kmf.init(store, "test".toCharArray());
		KeyManager[] keyManagers = new KeyManager[1];
		keyManagers = kmf.getKeyManagers();
		
		

		//Init the trust manager factory
		TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());

		//It will reference the same key store as the key managers
		tmf.init(store);
		
		TrustManager[] trustManagers = tmf.getTrustManagers();
		
		
		ssl.init(keyManagers, trustManagers, new SecureRandom());

		//Init a configuration with our SSL context
		HttpsConfigurator configurator = new HttpsConfigurator(ssl);
		

		//Create a server on localhost, port 443 (https port)
		HttpsServer httpsServer = HttpsServer.create(new InetSocketAddress("localhost", 443), 443);
		httpsServer.setHttpsConfigurator(configurator);
		
		
		//Create a context so our service will be available under this context
		HttpContext context = httpsServer.createContext("/test");
		httpsServer.start();
		

		//Finally, use the created context to publish the service
		endpoint.publish(context);
		
		

	}


And voilà! That should be enough to your service be available under SSL. With this code, the wsdl of the service should be available under https://localhost:443/test?wsdl.

Please note that, at the time that I developed this service, I had some trouble with the generated WSDL, specifically with the port address and references to schema files. A reasonable work around for this problem (if you have it too) is to download the files (WSDL, schemas, and so on) and fix it by hand.

See ya!

How to intercept web services ingoing/outgoing messages

Hi, everybody! I´m just passing around to show a technique to easily intercept JAX-WS web services messages. It is a quick piece of knowledge, and it is based on three simple steps.

I know that you always say, first, what I need to know…

That´s right. First of all, be aware that I tested it with EJB´s services, and I don´t know if it works with other types of exposed web services. Also, note that I present here a solution that works with annotated web services.

Ok, show me the solution

Step #1: Annotate your web service´s class with @HandlerChain:

package com.alesaudate.webservices;

@WebService
@HandlerChain(file="handlers.xml") //Here, you need to reference a configuration file. In this case, it got to be in the same package as the class
public class MyService {

//...


}

Step #2: Create a configuration file, like:

<?xml version="1.0" encoding="UTF-8"?>
<jws:handler-chains xmlns:jws="http://java.sun.com/xml/ns/javaee">

  <jws:handler-chain>
    <jws:handler>
      <jws:handler-name>MimeHandler</jws:handler-name>
      <jws:handler-class>com.alesaudate.webservices.MimeTypeHandler</jws:handler-class>      
    </jws:handler>
</jws:handler-chain>
  
</jws:handler-chains>

(Note that it need to be named “handlers.xml”, as referenced in the annotation)

Step #3: Create a interceptor for the class:

package com.alesaudate.webservices;

import java.util.Set;

import javax.xml.namespace.QName;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;

public class MimeTypeHandler implements SOAPHandler<SOAPMessageContext>{

	public Set<QName> getHeaders() {
		return null;
	}

	public void close(MessageContext context) {
		
	}

	public boolean handleFault(SOAPMessageContext context) {
		return true;
	}

	public boolean handleMessage(SOAPMessageContext context) {
		try {
                                          context.getMessage().getMimeHeaders().setHeader("Content-Type", "text/xml");
		} catch (SOAPException e) {
			throw new RuntimeException(e);
		}
		return true;
	}
	

}

Pretty easy, right? I´m taking for granted that the interceptor and the configuration file are pretty self-explanatory, but if you have any doubts, don´t hesitate asking me, OK?

Bye!

How to create a contract-first web service (or: how to create a web service that handles XML)

08/31/2010 1 comment

Hello! Today, I´m gonna show you a sample on how to develop a contract-first web service in java. To do so, you are gonna need:

  • An Apache Tomcat (or any Application Server that is compatible with JAX-WS)
  • A JAX-WS runtime (I used the RI, that I got from here – most application servers already have)

Now, as it is a contract-first web service, we need the contract. I used this one:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://alesaudate.com/webservices" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="blog" targetNamespace="http://alesaudate.com/webservices">
  <wsdl:types>
    <xsd:schema targetNamespace="http://alesaudate.com/webservices">
      <xsd:element name="operation">
        <xsd:complexType>
          <xsd:sequence>
            <xsd:element name="request" type="xsd:string"/>
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>
      <xsd:element name="operationResponse">
        <xsd:complexType>
          <xsd:sequence>
            <xsd:element name="response" type="xsd:string"/>
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>
    </xsd:schema>
  </wsdl:types>
  <wsdl:message name="operation">
    <wsdl:part element="tns:operation" name="parameters"/>
  </wsdl:message>
  <wsdl:message name="operationResponse">
    <wsdl:part element="tns:operationResponse" name="parameters"/>
  </wsdl:message>
  <wsdl:portType name="blog">
    <wsdl:operation name="blogOperation">
      <wsdl:input message="tns:operation"/>
      <wsdl:output message="tns:operationResponse"/>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="mySOAPBinding" type="tns:blog">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="blogOperation">
      <soap:operation soapAction="http://alesaudate.com/webservices/SampleOperation"/>
      <wsdl:input>
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output>
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="blogService">
    <wsdl:port binding="tns:mySOAPBinding" name="blogSOAP">
      <soap:address location="http://localhost:8080/WebServices/provider"/>
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

Now, we must provide an implementation class (or, how I like to call, where the magic happens =P ). Here is my implementation:

package com.alesaudate.webservices;

import javax.xml.soap.SOAPMessage;
import javax.xml.ws.Provider;
import javax.xml.ws.ServiceMode;
import javax.xml.ws.WebServiceProvider;
import javax.xml.ws.Service.Mode;

@ServiceMode(Mode.MESSAGE)
@WebServiceProvider(portName="blogSOAP", 
		serviceName="blogService",  
		targetNamespace="http://alesaudate.com/webservices", 
		wsdlLocation="WEB-INF/wsdl/blog.wsdl")
public class MySOAPProvider implements Provider<SOAPMessage>{

	@Override
	public SOAPMessage invoke(SOAPMessage request) {
		try {
			request.writeTo(System.out);
		} catch (Exception e) {
			e.printStackTrace();
		} 
		return request;
	}

}

Please note that this refers to a WEB-INF directory. So, as you may have guessed by now, it MUST run on a web project (.war). Other forms of java files, like .jar or .ear are unable to run this code.

Now, we must provide what I call “the glue”: files that provide the binding. For the RI implementation, we must provide a file called sun-jaxws.xml and place it under the WEB-INF directory. For our project, it has the following structure:

<endpoints
    xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime"
    version="2.0">

    <endpoint
        name="example"
        implementation="com.alesaudate.webservices.MySOAPProvider"
        wsdl="WEB-INF/wsdl/blog.wsdl"
        service="{http://alesaudate.com/webservices}blogService"
        port="{http://alesaudate.com/webservices}blogSOAP"
        url-pattern="/provider" />

</endpoints>

We also need to insert the right entries into web.xml:

<listener>
        <listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>provider</servlet-name>
        <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>provider</servlet-name>
        <url-pattern>/provider</url-pattern>
    </servlet-mapping>

The URL pattern, here, is the address where our web service will answer requests and will provide it´s contract.

Having reached this point so far, we need to place JAX-WS lib´s on the common library directory under Tomcat (for application servers like JBoss, you may skip this step).

And that´s all! Accessing the address http://localhost:8080/WebServices/provider?wsdl has shown me the WSDL that I quoted above, how about you?

You may check the code that I used here at the downloads section.

See ya!

How to dynamically select a certificate alias when invoking web services

08/09/2010 8 comments

Hi, boys and girls! First of all, I would like to say that, yes, this blog´s posts are gonna be all written in English, so they can affect more people around the world. That said, I also would like to say that, although the language has changed, the content is gonna be the same, so I will write and, where it fits, make a critical analysis on the solution.

So, what is the problem?

Web services that use certificates are specially tough to invoke. Along with the complexity that involves almost everything that concern to web services, it also involves the complexity that involves almost everything that concerns to secure communication over the web. So, putting these problems together may give a very strong headache to whoever that wishes to invoke secure web services in Java. Invoking services in a secure way is relatively easy when they do not use more than one certificate in the same VM. But, when it involves more than one certificate, it may be useful to give aliases to the certificates, and to have the ability to select between these certificates in runtime. Also, it may be useful even to change from keystore to keystore in runtime. So, the intent, here, is to provide a simple solution to this problem.

What am I gonna need?

First, you need to assemble a key store and a trust store. I´m not gonna show how to do it here; however, the reader may google it (using a query like this one) to find out how to do it. Second, you must (obviously) have the contract for the service to be invoked. Be warned that, probably, you won´t be able to access it directly, because it will be protected by HTTPS. So, get in touch with the responsibles for the service that you want to invoke. Third, and finally, remember to read carefully the analysis that I am providing at the end of the text (the penalty for not accomplishing this requirement may be an incompatibility between environments).

I´m aware of the requirements, let´s go!

The code consists of the following pieces:

  • A SSL Socket Factory Generator
  • An Alias Selector
  • A Service Invoker

The code for the SSL Socket Factory Generator is like the one below:

import java.io.FileInputStream;
import java.io.IOException;
import java.net.Socket;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.logging.Logger;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509KeyManager;


public class SSLSocketFactoryGenerator {
	
	
	private String alias = null;
	private String keyStore = null;
	private String trustStore = null;
	
	public SSLSocketFactoryGenerator (String alias, String keyStore, String trustStore) {
		if (alias == null)
			throw new IllegalArgumentException("The alias may not be null");
		this.alias = alias;
		this.keyStore = keyStore;
		this.trustStore = trustStore;

	}
		

	public SSLSocketFactory getSSLSocketFactory() throws IOException, GeneralSecurityException {
    
		KeyManager[] keyManagers = getKeyManagers();
		TrustManager[] trustManagers =getTrustManagers();
    
    
		//For each key manager, check if it is a X509KeyManager (because we will override its 		//functionality
		for (int i=0; i<keyManagers.length; i++) {
			if (keyManagers[i] instanceof X509KeyManager) {
				keyManagers[i]=new AliasSelectorKeyManager((X509KeyManager)keyManagers[i], alias);
			}
      		}
    

		SSLContext context=SSLContext.getInstance("SSL");
		context.init(keyManagers, trustManagers, null);

    
		SSLSocketFactory ssf=context.getSocketFactory();
    		return ssf;
  	}	
		
	
	public String getKeyStorePassword() {
		return "keyStorePassword";
	}
	
	public String getTrustStorePassword() {
		return "trustStorePassword";
	}


	public String getKeyStore() {
		return keyStore;
	}

	public String getTrustStore() {
		
		return trustStore;
	}


	private KeyManager[] getKeyManagers()
	throws IOException, GeneralSecurityException
	{
		
		//Init a key store with the given file.
		
		String alg=KeyManagerFactory.getDefaultAlgorithm();
		KeyManagerFactory kmFact=KeyManagerFactory.getInstance(alg);

		
		FileInputStream fis=new FileInputStream(getKeyStore());
		KeyStore ks=KeyStore.getInstance("jks");
		ks.load(fis, getKeyStorePassword().toCharArray());
		fis.close();

		//Init the key manager factory with the loaded key store
		kmFact.init(ks,  getKeyStorePassword().toCharArray());


		
		KeyManager[] kms=kmFact.getKeyManagers();
		return kms;
	}

	
	protected TrustManager[] getTrustManagers() throws IOException, GeneralSecurityException
	{
    
		String alg=TrustManagerFactory.getDefaultAlgorithm();
		TrustManagerFactory tmFact=TrustManagerFactory.getInstance(alg);
    
    
		FileInputStream fis=new FileInputStream(getTrustStore());
		KeyStore ks=KeyStore.getInstance("jks");
		ks.load(fis, getTrustStorePassword().toCharArray());
		fis.close();

    
		tmFact.init(ks);

    
		TrustManager[] tms=tmFact.getTrustManagers();
		return tms;
	}
}

So, the SSLSocketFactory will act as a provider for our secure sockets. Note that the class AliasSelectorKeyManager is our alias selector. It´s code is shown below:


import java.net.Socket;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;

import javax.net.ssl.X509KeyManager;

public class AliasSelectorKeyManager implements X509KeyManager{

	private X509KeyManager sourceKeyManager=null;
	private String alias;

	public AliasSelectorKeyManager(X509KeyManager keyManager, String alias)
	{
		this.sourceKeyManager=keyManager;	     
		this.alias = alias;

	}

	public String chooseClientAlias(String[] keyType, Principal[] issuers,
			Socket socket)
	{	
		boolean aliasFound=false;

		//Get all aliases from the key manager. If any matches with the managed alias,
		//then return it.
		//If the alias has not been found, return null (and let the API to handle it, 
		//causing the handshake to fail).

		for (int i=0; i<keyType.length && !aliasFound; i++) {
			String[] validAliases=sourceKeyManager.getClientAliases(keyType[i], issuers);
			if (validAliases!=null) {
				for (int j=0; j<validAliases.length && !aliasFound; j++) {
					if (validAliases[j].equals(alias)) aliasFound=true;
				}
			}
		}

		if (aliasFound) {
			return alias;
		}
		else return null;
	}


	public String chooseServerAlias(String keyType, Principal[] issuers,
			Socket socket)
	{
		return sourceKeyManager.chooseServerAlias(keyType, issuers, socket);
	}

	public X509Certificate[] getCertificateChain(String alias)
	{
		return sourceKeyManager.getCertificateChain(alias);
	}

	public String[] getClientAliases(String keyType, Principal[] issuers)
	{
		return sourceKeyManager.getClientAliases(keyType, issuers);
	}

	public PrivateKey getPrivateKey(String alias)
	{

		return sourceKeyManager.getPrivateKey(alias);
	}

	public String[] getServerAliases(String keyType, Principal[] issuers)
	{
		return sourceKeyManager.getServerAliases(keyType, issuers);
	}

}

So, at this point we have a custom SSLSocketFactory, that can generate a custom selection for aliases. The final step is to force our web services to use it. To do so, the trick is to generate a Dispatch, which is a JAX-WS interface responsible for calling web services. The Dispatch may accept custom parameters to do the invocations, and we may pass a SSLSocketFactory as parameter, by using a Sun interface called JAXWSProperties. The code is shown below:


public Dispatch<SOAPMessage> getDispatcher(String alias) throws IOException, GeneralSecurityException {
		String namespace = "http://alesaudate.com/Services/";
		String wsdlLocation = "http://alesaudate.com/Services/MyService?wsdl";
		String serviceName = "MyService";
		String portName = "MyServiceSoap";
		String soapActionUri = "http://alesaudate.com/Services/testAction";
		
		String keyStoreLocation = "C:\\chains\\myKeystore.jks";
		String trustStoreLocation = "C:\\chains\\myTrustStore.jks";
		

		//Load a dispatcher with the givend data.
		Dispatch<SOAPMessage> dispatcher = getDispatcher(wsdlLocation, namespace, serviceName, portName);
		
		//Create our custom SSLSocketFactory
		SSLSocketFactory socketFactory = new SSLSocketFactoryGenerator().generateSocketFactory(alias, keyStoreLocation, trustStoreLocation);

		//Be aware: don´t use the com.sun.xml.internal.ws.developer.JAXWSProperties interface instead !!!!
		dispatcher.getRequestContext().put (com.sun.xml.ws.developer.JAXWSProperties.SSL_SOCKET_FACTORY, socketFactory);
		
		dispatcher.getRequestContext().put(BindingProvider.SOAPACTION_URI_PROPERTY, soapActionUri);
		
		return dispatcher;
	}
	
	
	public Dispatch<SOAPMessage> getDispatcher (String wsdl, String namespace, String servicename, String portName){
	
		try {
			//create a representation of the service
			Service service = Service.create(new URL(wsdl), new QName(
					namespace, servicename));
			
			
			final Iterator<QName> ports = service.getPorts();
			
			QName methodToBeCalled = null;
			

			//Select the port to be called
			if (portName == null)
				methodToBeCalled = ports.next();
			
			else {
				while (methodToBeCalled == null || !methodToBeCalled.getLocalPart().equals(portName)) {
					methodToBeCalled = ports.next();
				}
			}
			
			//Create the dispatcher, given the data.
			Dispatch<SOAPMessage> dispatch = service.createDispatch(
					methodToBeCalled, SOAPMessage.class, Service.Mode.MESSAGE);
			

			return dispatch;
		} catch (Exception e) {
			throw new RuntimeException("Error calling web-service", e);
		}
		
	}


Here, I create the dispatch to invoke the service. All I have to do, now, is to create a SOAPMessage and use the invoke method, on the Dispatch interface, to invoke the web service in a secure way. If it interests the reader, you may improve the code to use some sort of JAXB piece to create a SOAPMessage from an object.

What should I pay attention to?

The reader must be aware that JAXWSProperties is part of a Sun library and, so, may not work with some application servers and/or Virtual Machine implementations. I tested it using Sun´s JDK 1.6.0_20, with a JBoss AS 5 (which already has the JAR that contains JAXWSProperties). Also, be aware that the solution presented here is not easy to use, and the reader may be interested in creating some sort of Object-to-SOAPMessage translator (maybe using Reflections?). Finally, be sure that speed is not critical, because this code is going to generate a new SSLSocketFactory (including a IO call) to every call, so it may be more interesting to use some kind of cache (of the SSLSocketFactory), in order not to create a new one at every call, but to reuse them.

Nice coding!