I thought that customizing web service on https server (packed with JRE) and its client should be easy. But the lack of documentation make it quiet hard. Here are results of my findings. The topics covered are: Two-way SSL communication, custom SSLContext for webservice running on Java SDK embedded https server, dummy TrustManager, client certificate authentication. I’ll cover both the server and client side.
Suppose, you have created 2 certifikate keystore files, let’s say server.jks
(containing server private key and client public certificate) andclient.jks
(symmetrically, the client private key and server public certificate).
The server side code follows, consider it as code for reference.
package
com.example.echo;
import
com.sun.net.httpserver.Authenticator;
import
com.sun.net.httpserver.HttpContext;
import
com.sun.net.httpserver.HttpExchange;
import
com.sun.net.httpserver.HttpServer;
import
com.sun.net.httpserver.HttpsConfigurator;
import
com.sun.net.httpserver.HttpsExchange;
import
com.sun.net.httpserver.HttpsParameters;
import
com.sun.net.httpserver.HttpsServer;
import
java.io.FileInputStream;
import
java.net.InetSocketAddress;
import
java.security.KeyStore;
import
java.security.cert.CertificateException;
import
java.security.cert.X509Certificate;
import
java.util.concurrent.ExecutorService;
import
java.util.concurrent.Executors;
import
javax.annotation.Resource;
import
javax.jws.WebMethod;
import
javax.jws.WebParam;
import
javax.jws.WebResult;
import
javax.jws.WebService;
import
javax.net.ssl.KeyManagerFactory;
import
javax.net.ssl.SSLContext;
import
javax.net.ssl.SSLParameters;
import
javax.net.ssl.SSLPeerUnverifiedException;
import
javax.net.ssl.TrustManager;
import
javax.net.ssl.X509TrustManager;
import
javax.xml.ws.Endpoint;
import
javax.xml.ws.WebServiceContext;
import
javax.xml.ws.handler.MessageContext;
public
class
SSLWebService {
public
static
void
main(String[] args)
throws
Exception {
new
SSLWebService().runHttpsService();
}
public
void
runHttpsService()
throws
Exception {
KeyStore ks = KeyStore.getInstance(
"JKS"
);
FileInputStream keyStoreIn =
new
FileInputStream(
"server.jks"
);
try
{
ks.load(keyStoreIn,
"passphrase"
.toCharArray());
}
finally
{
keyStoreIn.close();
}
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks,
"passphrase"
.toCharArray());
TrustManager[] trustManagers =
new
TrustManager[] {
new
DummyTrustManager()};
SSLContext sslCtx = SSLContext.getInstance(
"TLS"
);
sslCtx.init(kmf.getKeyManagers(), trustManagers,
null
);
HttpsConfigurator cfg =
new
HttpsConfigurator(sslCtx){
public
void
configure(HttpsParameters params) {
SSLParameters sslparams = getSSLContext().getDefaultSSLParameters();
// Modify the default params: Will require client certificates
sslparams.setNeedClientAuth(
true
);
params.setSSLParameters(sslparams);
}
};
ExecutorService httpThreadPool = Executors.newFixedThreadPool(
10
);
HttpsServer httpsS = HttpsServer.create(
new
InetSocketAddress(
8081
),
50
);
httpsS.setHttpsConfigurator(cfg);
httpsS.setExecutor(httpThreadPool);
httpsS.start();
HttpContext ctx = httpsS.createContext(
"/ws"
);
ctx.setAuthenticator(
new
Authenticator(){
@Override
public
Result authenticate(HttpExchange exch) {
try
{
if
(exch
instanceof
HttpsExchange) {
HttpsExchange httpsExch = (HttpsExchange)exch;
System.out.println(
"authen: "
+ httpsExch.getSSLSession().getPeerPrincipal().getName());
// DO YOUR AUTHENTICATION HERE
httpsExch.getSSLSession().putValue(
"MY_PARAM_PEER_NAME"
,
httpsExch.getSSLSession().getPeerPrincipal().getName());
return
new
Authenticator.Success(exch.getPrincipal());
}
}
catch
(SSLPeerUnverifiedException e) {
e.printStackTrace();
}
return
new
Authenticator.Failure(
403
);
}
});
Endpoint endpoint = Endpoint.create(
new
Echo());
endpoint.publish(ctx);
// publish also on HTTP server, so we can access
// wsdl through HTTP, just for our example.
HttpServer httpS = HttpServer.create(
new
InetSocketAddress(
8082
),
50
);
httpS.start();
Endpoint endpoint2 = Endpoint.create(
new
Echo());
endpoint2.publish(httpS.createContext(
"/ws"
));
}
@WebService
(
public
static
class
Echo {
@Resource
protected
WebServiceContext context;
@WebMethod
(operationName =
"echo"
)
@WebResult
(name =
"message"
)
public
String echo(
@WebParam
(name =
"msg"
) String msg) {
String user =
"unknown"
;
MessageContext msgCtx = context.getMessageContext();
Object httpExchange = msgCtx.get(
"com.sun.xml.ws.http.exchange"
);
if
(httpExchange
instanceof
HttpsExchange) {
HttpsExchange httpsExch = (HttpsExchange)httpExchange;
user = (String)httpsExch.getSSLSession().getValue(
"MY_PARAM_PEER_NAME"
);
}
return
"user '"
+ user +
"' said: "
+ msg;
}
}
public
static
class
DummyTrustManager
implements
X509TrustManager {
public
X509Certificate[] getAcceptedIssuers() {
return
new
X509Certificate[
0
]; }
public
void
checkClientTrusted(X509Certificate[] certs, String authType)
throws
CertificateException {
// DO YOUR VERIFICATION
}
public
void
checkServerTrusted(X509Certificate[] certs, String authType)
throws
CertificateException {
// DO YOUR VERIFICATION
}
}
}
|
Obtain wsdl and generate client classes, from commandline:
wget -O echo.wsdl http://localhost:8082/ws?wsdl
wsimport -Xnocompile http://localhost:8082/ws?wsdl
|
And finally write a client:
package
com.example.echo;
import
java.io.FileInputStream;
import
java.security.KeyStore;
import
java.security.cert.CertificateException;
import
java.security.cert.X509Certificate;
import
java.util.Map;
import
javax.net.ssl.HostnameVerifier;
import
javax.net.ssl.KeyManagerFactory;
import
javax.net.ssl.SSLContext;
import
javax.net.ssl.SSLSession;
import
javax.net.ssl.TrustManager;
import
javax.net.ssl.X509TrustManager;
import
javax.xml.namespace.QName;
import
javax.xml.ws.BindingProvider;
public
class
SSLWebClient {
public
void
callEcho(String wsUrl, String msg)
throws
Exception {
SSLContext sslCtx = SSLContext.getInstance(
"SSL"
);
TrustManager[] trustManagers =
new
TrustManager[] {
new
X509TrustManager() {
public
X509Certificate[] getAcceptedIssuers() {
return
new
X509Certificate[
0
];
}
public
void
checkClientTrusted(X509Certificate[] certs, String authType)
throws
CertificateException {
}
public
void
checkServerTrusted(X509Certificate[] certs, String authType)
throws
CertificateException {
}
}
};
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance(
"JKS"
);
FileInputStream keyStoreIn =
new
FileInputStream(
"client.jks"
);
try
{
ks.load(keyStoreIn,
"passphrase"
.toCharArray());
}
finally
{
keyStoreIn.close();
}
kmf.init(ks,
"passphrase"
.toCharArray());
sslCtx.init(kmf.getKeyManagers(), trustManagers,
null
);
// This is a key point: We must not initialize service with target URL, because then
// the WSDL would be downloaded from the HTTPS server, before we initialize our
// context properties. So we initialize the EchoService with previously downloaded WSDL
// and then use it to call the real service through HTTPS.
EchoService service =
new
EchoService(
getClass().getResource(
"echo.wsdl"
),
Echo port = service.getEchoPort();
Map<String, Object> ctxt = ((BindingProvider)port).getRequestContext();
ctxt.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, wsUrl);
// Sets dummy hostname verifier, so that server certificate CN doesn't
// have to match its hostname
HostnameVerifier hnv =
new
HostnameVerifier() {
@Override
public
boolean
verify(String hostname, SSLSession sslSession) {
return
true
;
}
};
ctxt.put(
"com.sun.xml.internal.ws.transport.https.client.hostname.verifier"
, hnv);
ctxt.put(
"com.sun.xml.ws.transport.https.client.hostname.verifier"
, hnv);
// Sets socket factory from our ssl context,
// which has dummy Trust manager. If used, server certificate doesn't need to be
// in our trust keystore nor in Java "cacerts" keystore.
ctxt.put(
"com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory"
, sslCtx.getSocketFactory());
ctxt.put(
"com.sun.xml.ws.transport.https.client.SSLSocketFactory"
, sslCtx.getSocketFactory());
String response = port.echo(msg);
System.out.println(
"Response: "
+ response);
}
public
static
void
main(String[] args)
throws
Exception {
}
}
|