I have the following requirements:
- to build a multi-thread http server serving inbound web service requests via POST
- the request is not conventional SOAP but is still XML-based
- both client and server authentication (certificate based) is needed (riding not https)
After some googling I found that Java provides a convenient framework to deliver this capability (as in Meisch's article "Java Webservice using HTTPS part 2")
The major classes used is:
- com.sun.net.httpserver.HttpsServer (abstract class); and
- com.sun.net.httpserver.HttpHandler (interface)
The logic of the program is as follows:
- inside the main method, after creating a dummy java_https_server object, invoke the CreateHttpServer method to create a HttpsServer object
- inside CreateHttpServer method, open a Java Key Store containing the web server's own certificate and the Trust Store containing the trusted client certificate (the latter is needed because I use two-way SSL). An SSLContext object instance is linked to these two key stores. Finally, a context (which I find useless because I cannot still associate different handler with different context) is created for the HttpsServer object.
- the main method continue to set up a shutdown hook to graceful termination handling
- since the java_https_server implements the HttpHandler interface, a handle() method is defined This is the core running logic of the whole web service server. I have added many println statements to display the attributes of the http connection in my codes.
The source listing is as follows:
import java.io.FileInputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpsServer;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpsExchange;
import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsParameters;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.SSLContext;
import java.security.KeyStore;
import java.net.URI;
import java.net.InetSocketAddress;
public class java_https_server implements HttpHandler {
private static final int HTTP_OK_STATUS = 200;
// ----------class property --------------
private String context = "/";
private int port = 8000;
private String keystorePasswordString = "password";
private String keystoreFile = "/full_path/keystore.jks";
private String truststorePasswordString = "password";
private String truststoreFile = "/full_path/truststore.jks";
// --------- Constructor -------------------
public java_https_server () {
}
// --------------------------------------------
public HttpsServer CreateHttpServer(int port, String context) {
HttpsServer httpServer;
try {
httpServer = HttpsServer.create(new InetSocketAddress(port), 0);
SSLContext sslContext = SSLContext.getInstance("TLS");
// server keystore
char[] keystorePassword = keystorePasswordString.toCharArray();
KeyStore ks = KeyStore.getInstance("JKS");
ks.load (new FileInputStream(keystoreFile), keystorePassword);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init (ks, keystorePassword);
// server truststore
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
char[] truststorePassword = truststorePasswordString.toCharArray();
ks.load (new FileInputStream(truststoreFile), truststorePassword);
tmf.init (ks);
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
// create an anonymous inner class HttpsConfigurator to require client certificate
HttpsConfigurator configurator = new HttpsConfigurator(sslContext) {
public void configure (HttpsParameters params) {
SSLParameters sslParams = getSSLContext().getDefaultSSLParameters();
sslParams.setNeedClientAuth(true);
params.setSSLParameters(sslParams);
}
};
httpServer.setHttpsConfigurator(configurator);
//Create a new context for the given context and handler
httpServer.createContext(context, this);
//Create a default executor
httpServer.setExecutor(null);
}
catch (Exception e) {
e.printStackTrace();
return null;
}
return httpServer;
} // method CreateHttpServer
// --------------------------------------------
public void handle(HttpExchange t) throws IOException {
URI uri = t.getRequestURI();
String response;
System.out.println ("LocalAddress: " + t.getLocalAddress().toString());
System.out.println ("RemoteAddress: " + t.getRemoteAddress().toString());
System.out.println ("URL is: " + uri.toString());
System.out.println ("Method: " + t.getRequestMethod());
System.out.println ("Client Certficate: " +
((HttpsExchange)t).getSSLSession().getPeerCertificateChain()[0].getSubjectDN());
if (t.getRequestMethod().equals ("POST")) {
InputStream is = t.getRequestBody();
byte[] data = new byte[100000];
int length = is.read(data);
if (length == 100000)
System.out.println ("Warning: the input buffer is FULL!");
System.out.println ("Request Length: " + length);
data = java.util.Arrays.copyOf(data, length); // trim the array to the correct size
System.out.println ("Request Body:[" + new String(data) + "]");
is.close();
response = "Give your response here";
}
else {
response = "Error";
}
//Set the response header status and length
t.sendResponseHeaders(HTTP_OK_STATUS, response.getBytes().length);
//Write the response string
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
}
// --------------------------------------------
public static void main(String[] args) throws Exception {
java_https_server server = new java_https_server();
System.out.println("Use Ctrl-C (foreground) or \"kill -15 (background)\" to stop me");
final HttpsServer httpServer = server.CreateHttpServer(server.port, server.context);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.out.println("Stopping Server..");
httpServer.stop(0);
System.out.println("Server stopped");
} // run()
});
httpServer.start(); // Start the server
System.out.println("Server is started and listening on port "+ server.port);
} // method main
} // class java_https_server
|
Some example runtime output is as follows:
$ java -cp . java_https_server
Use Ctrl-C (foreground) or "kill -15 (background)" to stop me
Server is started and listening on port 8000
LocalAddress: /127.0.0.1:8000
RemoteAddress: localhost/127.0.0.1:52204
URL is: /app
Method: POST
Client Certficate: CN=ccm010, OU=xxx, O=xxx, L=xxx, ST=xxx, C=HK
Request Length: 385
Request Body:[<?xml version='1.0'?><Envelope xmlns='http://schemas.xmlsoap.org/soap/envelope/' xmlns:op='http://schemas.xyz.com/svc' xmlns:ems='http://schemas.xyz.com/ems'><Header>Header_Text</Header><Body><op:AddPbs><ems:ChannelAcctId><ems:ChannelId>06</ems:ChannelId><ems:AcctId>1234567</ems:AcctId></ems:ChannelAcctId></op:AddPbs></Body></Envelope>]
Stopping Server..
Server stopped
|
I will cover how to test using openssl with client certificate in the next post.
Using openssl to test two way SSL connectivity