Maybe you are wondering why a java entry in a databases blog? The answer is simple, at CERN, the Infrastucture and Middleware Services section (aka IMS) is part of the Database Services , and, among many other things, we take care of the Java application servers for our users.
And now get down to work! In this post I am going to show you how to generate a java client for a SOAP web service. This last one requires client certificate authentication.
For the generation we will use the maven cxf-codegen-plugin. The setup is simple, just declare the plugin in your pom.xml:
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>2.7.4</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<inherited>false</inherited>
<configuration>
<sourceRoot>${project.build.directory}/generated</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>${basedir}/src/main/resources/wsdl/HelloWorldWS.wsdl</wsdl>
<wsdlLocation>classpath:wsdl/HelloWorldWS.wsdl</wsdlLocation>
</wsdlOption>
</wsdlOptions>
</configuration>
<!-- IMPORTANT: invoke it with cxf-codegen:wsdl2java -->
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>
Couple of hints:
- At generation time the plugin will look for a wsdl folder, that's why we have <wsdl>${basedir}/src/main/resources/wsdl/HelloWorldWS.wsdl</wsdl>
- Avoid Absolute File Path When Using the Cxf-codegen-plugin. Thanks Kyle!
- You can find the service code in this nice tutorial: JAX-WS web service eclipse tutorial. Thanks Arpit!
And that's it! Just run the plugin and you are done. If you use the m2e-Maven Integration for Eclipse be careful of prefix your goal with the name of the plugin (cxf-codegen:wsdl2java):
In your console output you will have something like this:
Console output |
Apache Maven 3.0.4 (r1232337; 2012-01-17 09:44:56+0100) [DEBUG] Project: ch.cern.it.db.mw:helloworldclient:jar:0.0.1 [INFO] BUILD SUCCESS |
And in your project:
Now it is time to code a simple client, but first you need to include the generated code into your project. You can simply move it to your src/main/java folder:
Our client will be something like this:
Sample client |
public static void main(String[] args) throws MalformedURLException { URL wsdlLocation = new URL(args[1]); HelloWorldImplService helloWorldImplService = new HelloWorldImplService(wsdlLocation); HelloWorld helloWorld = helloWorldImplService.getHelloWorldImplPort(); SayHelloWorld parameters = new SayHelloWorld(); parameters.setArg0("Luis"); SayHelloWorldResponse helloWorldResponse = helloWorld.sayHelloWorld(parameters); System.out.println(helloWorldResponse.getReturn()); } |
And now the authentication. In theory it is very simple, at runtime we only need to add your client keystore with the privateKey to the JVM. Also you will need to add a keystore with the certificates of the trusted CAs:
JAVA OPTIONS |
-Djavax.net.ssl.trustStore=/your/path/cacerts.jks -Djavax.net.ssl.trustStorePassword=cacertsPassword -Djavax.net.ssl.keyStore=/your/path/client.jks -Djavax.net.ssl.keyStorePassword=clientPassword |
Me, for simplicity, I will added to the VM arguments of my Java run configuration in Eclipse:
And now the tricky part, be sure that your intermediate certificate is associated with the entry where you have your client certificate and its private key! This is, your privateKey in your client.jks should look like this:
Private key certificate chain |
Certificate chain length: 2 Certificate[1]: Owner: CN=Robot: Db Systems... Issuer: CN=CERN Grid Certification Authority... .../... Certificate[2]: Owner: CN=CERN Grid Certification Authority, DC=cern, DC=ch Issuer: CN=CERN Root Certification Authority 2... |
If you do not do this your client will never send the client certificate during the SSL handshake. Why? Because the client needs to determine if it has a keypair that matches one of the members of the list of trusted CA issued by the server. See this excellent article by Jos Dirksen in DZone: How to Analyze Java SSL Errors.
If you activate the debug traces in the JVM (-Djavax.net.debug=all), at some point you should see something like this (keytool -list -keystore client.jks -v -alias user_client_cern_ca2):
List of server's cert authorities |
*** CertificateRequest |
Above is part the list of the Cert authorities trusted by the server. And once the ServerHello is finish you should see the how the client certificate is issued:
Client certificate chain issued |
matching alias: user_client_cern_ca2 *** Certificate chain chain [0] = [ [ Version: V3 Subject: CN=Robot: Db Systems,... .../... ] chain [1] = [ [ Version: V3 Subject: CN=CERN Grid Certification Authority... .../... |
So in this example "CN=Robot: Db Systems..." is issue by "CN=CERN Grid Certification Authority..." that is issue by "CN=CERN Root Certification Authority 2".
Tha's all folks! Have a happy coding day!
Luis
Links:
- Database Services at CERN:http://information-technology.web.cern.ch/about/organisation/database-s…
- Maven home: http://maven.apache.org/
- Apache maven cxf: http://cxf.apache.org/docs/using-cxf-with-maven.html
- Avoid Absolute File Path When Using the Cxf-codegen-plugin: http://kylelieber.com/2012/03/avoid-absolute-file-path-when-using-the-c…
- Maven for eclipse https://www.eclipse.org/m2e/
- Why doesn't java send the client certificate during SSL handshake?: http://stackoverflow.com/questions/9299133/why-doesnt-java-send-the-cli…
- How to analyse Java SSL Errors: http://java.dzone.com/articles/how-analyze-java-ssl-errors
- JAX-WS web service eclipse tutorial: http://www.java2blog.com/2013/03/jaxws-web-service-eclipse-tutorial.html
Keep on writing, great job!