tag:blogger.com,1999:blog-53741527282061387492024-02-07T15:50:25.174+13:00The Java Monkey...One code monkey's ideas on J2SE/EE software development - tales of problems and how they were overcome, with a healthy dose of pragmatism.The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.comBlogger24125tag:blogger.com,1999:blog-5374152728206138749.post-49015330098118749382009-08-24T07:00:00.005+12:002009-08-24T08:03:38.545+12:00GWT Basics - Reusable Bean Libraries<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgI-CpkdO6_CZp9cMh45Le_jIrRKtKL2iN8IURL_fXR_bLDa53defkovgLZzw7cWwqkRARZ3FHWbm2n4hpG1Wi54UTya_J5sb_aYV7fi0M-AVvNPQu3R5LUpo8ZxoR59fmfAY4LDJfT9FO2/s1600-h/960776013-00.gif"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgI-CpkdO6_CZp9cMh45Le_jIrRKtKL2iN8IURL_fXR_bLDa53defkovgLZzw7cWwqkRARZ3FHWbm2n4hpG1Wi54UTya_J5sb_aYV7fi0M-AVvNPQu3R5LUpo8ZxoR59fmfAY4LDJfT9FO2/s320/960776013-00.gif" border="0" alt=""id="BLOGGER_PHOTO_ID_5373237769756517682" /></a><br />Ok, so I've had to work a lot with GWT over the last 12 months. First up, I was really impressed at what GWT provides, especially the fact that it eliminates having to write browser code and forms. This quickly was overridden by the extremely poor compile times, the bulkiness of the GWT shell, and the fact that it allows developers to write truly terrible applications (due to its asynchronous nature) if you don't constantly monitor what they are doing.<br /><br />Designing a GWT application up front is important. It is essential to make strict use of the Model View Controller pattern and obviously the Observer pattern (due to the asynchronous nature of GWT). The projects I have been developing GWT client's for, all have SOAP backend services that provide their actual data access (using Apache Axis 2). The SOAP services are all code-first POJO based services. The easiest way to put this together is to let the backend service assemble the GWT beans and perform all the transport and marshalling work to get them into your browser via the GWT RPC server. This way, you don't need to do any conversion from SOAP in to GWT beans and you don't need data transfer objects, because the GWT beans go all the way through from your server to your browser. That is, this cuts out a truckload of marshalling code, as everything is talking in GWT, and the SOAP layer handles all the SOAP stuff.<br /><br />To do this, you need to create a separate jar/project for your GWT beans, that only contains the beans. This project is nothing but a pure, basic Java project that builds a jar file. It should not have the GWT framework or libraries applied to it. There are two minor things you need to do so that your GWT client project can work with these beans. <br /><br /><ol><li>Include the Java source of your beans in the jar file, so the GWT compiler can create the appropriate browser/RPC code.</li><li>Include a <span style="font-weight:bold;">.gwt.xml</span> in your jar so GWT knows it contains GWT resources that it needs to use.</li></ol><br /><span style="font-weight:bold;">1. </span>If you are using NetBeans, you can override one of the NetBeans build targets to make sure your Java files get built into the jar. The <span style="font-style:italic;">-pre-jar</span> target should be overridden in your NetBeans generated build.xml script as follows:<br /><br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><target name="-pre-jar"><br /> <copy todir="build/classes"><br /> <fileset dir="src"><br /> <include name="**/*"/><br /> </fileset><br /> </copy><br /></target></pre></div><br /><span style="font-weight:bold;">2. </span>The gwt.xml file mentioned above is purely a standard .gwt.xml file, however there is one very important gotcha. GWT expects by default that your beans will be in a sub package of the package named <span style="font-style:italic;"><span style="font-weight:bold;">client</span></span>. This client package is in the same parent package as the <span style="font-style:italic;">gwt.xml</span> file for your project. This is all well and good for most people, however I prefer to have more control over my package structure. I don't want to have my beans in a package called <span style="font-style:italic;">client</span>, when the beans are a re-usable library that servers and clients both use.<br /><br />To override this requirement, you simply add an extra line to your gwt.xml file with the name of the package that they can be found beneath. That is, if they are in a package called <span style="font-style:italic;">beans</span>, then use the following definition line:<br /><br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><source path="beans"/></pre></div><br />Note that you can have further packages beneath this folder, GWT just needs to know that it isn't the <span style="font-style:italic;">client</span> folder.<br /><br />If you fail to point this out to GWT, you will get the following exceptions during the GWT compile phase about it not being able to find the source code of your GWT beans:<br /><br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"> [java] [ERROR] Line 206: No source code is available for type com.mym2.m2w.common.movements.Movement; did you forget to inherit a required module?<br /> [java] [ERROR] Line 208: No source code is available for type com.mym2.m2w.common.movements.CashDeposit; did you forget to inherit a required module?<br /> [java] Computing all possible rebind results for 'com.mym2.apps.client.LoginForm'<br /> [java] Rebinding com.mym2.apps.client.LoginForm<br /> [java] Checking rule <generate-with class='com.google.gwt.user.rebind.ui.ImageBundleGenerator'/><br /> [java] [ERROR] Unable to find type 'com.mym2.apps.client.LoginForm'<br /> [java] [ERROR] Hint: Previous compiler errors may have made this type unavailable<br /> [java] [ERROR] Hint: Check the inheritance chain from your module; it may not be inheriting a required module or a module may not be adding its source path entries properly<br /> [java] [ERROR] Build failed<br /></pre></div><br />So thats my first GWT blog, nice and simple to begin with. I will most likely be putting up further blogs as theres a lot of little tricks and techniques that should be known to get the most out of GWT. I'm also very interested in the architectures of GWT applications, as large applications absolutely require a good design in GWT.The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com20tag:blogger.com,1999:blog-5374152728206138749.post-32934804129063408452009-06-19T13:35:00.008+12:002009-07-04T05:16:44.131+12:00Installing Java and Tomcat on Ubuntu Hardy Heron (8.04)<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgh87NsdO1RWx2hOO4h0RXoKeZA_fBlijU02IlUDVZ1J8sT1w0R4KzCuN7KSR8TOy0CHi7HesdLqUZMhOIIkMSU7G42R883FZomsbw06oAV7k76uSmPLAxGssFHzhlAfs_xHACk_9hFkD0Y/s1600-h/rosella.png"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgh87NsdO1RWx2hOO4h0RXoKeZA_fBlijU02IlUDVZ1J8sT1w0R4KzCuN7KSR8TOy0CHi7HesdLqUZMhOIIkMSU7G42R883FZomsbw06oAV7k76uSmPLAxGssFHzhlAfs_xHACk_9hFkD0Y/s320/rosella.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5348851358553696130" /></a>As part of my new job, I have been migrating our various development servers from a company in New Zealand to hosted servers in the States at <a href="http://www.slicehost.com"><b>Slice Host</b></a>. Slice Host is a great setup, as it allows experienced developers to host their own Linux machines, without having to pay extra for support that you could do yourself.<br /><br />Anyway, every time that I need to set up an Ubuntu machine, I always end up having to try and remember how I setup Java and Tomcat (among other stuff) on them. Hence, recording the steps and any gotchas down in a blog was worthwhile for me.<br /><br />Note that this blog is written from the point of view of a Java developer who uses Linux, not a system administrator! <br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Installing Sun Java 6 SDK</span><br /><br />To install the Sun Java 6 SDK, the Advanced Packaging Tool <span style="font-style:italic;">apt-get</span> is used. Note that you need super-user access before running these commands. Before attempting the install, it is useful to update your list of installable packages using the following command:<br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><br />apt-get update<br /></pre></div><br />Once the packages have been updated, execute the following command to install the Java 6 SDK:<br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><br />apt-get install sun-java6-sdk<br /></pre></div><br />This should start installing Java 6. You will be prompted within the installer to agree to a license - use the TAB key to select the the OK and Yes buttons.<br /><br /><span style="font-weight:bold;">Java Gotcha One</span> - Sometimes you will get the following errors, due to the Java 6 packages not being available from your package sources:<br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><br />Reading package lists... Done<br />Building dependency tree... Done<br />Package sun-java6-jdk is not available, but is referred to by another package.<br />This may mean that the package is missing, has been obsoleted, or is only available from another source<br />E: Package sun-java6-jdk has no installation candidate<br /></pre></div><br />To solve this, I had to add two more repositories to my <span style="font-style:italic;">/etc/apt/sources.list</span> file:<br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><br />deb http://archive.ubuntu.com/ubuntu hardy universe multiverse<br />deb-src http://archive.ubuntu.com/ubuntu hardy universe multiverse<br /><br />deb http://us.archive.ubuntu.com/ubuntu/ hardy-updates universe multiverse<br />deb-src http://us.archive.ubuntu.com/ubuntu/ hardy-updates universe multiverse<br /></pre></div><br />Following this, running the update and then install commands works fine.<br /><br />Sun Java SDK 6 should now be installed to your machine in the <span style="font-style:italic;">/usr/lib/jvm/java-6-sun</span> (or similar).<br /><br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Installing Apache Tomcat 6.0</span><br /><br />To install Tomcat 6.0, I sidestep using <span style="font-style:italic;">apt</span> in favour of directly downloading the tarball from an Apache download mirror and installing it manually. This way always seems to work a lot easier, and allows you to install it to custom locations if necessary.<br /><br />To find out the exact URL of the Tomcat tarball you want to get, you can navigate around an Apache download mirror in a web browser. For example, enter an Apache mirror URL into a web-browser, and navigate down to a bin folder (where the binary tarballs are kept), for example:<br /><br /><a href="http://apache.mirrors.hoobly.com/tomcat">http://apache.mirrors.hoobly.com/tomcat</a><br /><br />A list of Apache mirrors can be found at <a href="http://www.apache.org/dyn/closer.cgi">http://www.apache.org/dyn/closer.cgi</a>.<br /><br />Once you have chosen the version of Tomcat (e.g. 5.5.25, 6.0.20) you want to use, grab the exact URL of the tarball for Tomcat, such as <span style="font-style:italic;">http://apache.mirrors.hoobly.com/tomcat/tomcat-6/v6.0.20/bin/apache-tomcat-6.0.20.tar.gz</span>.<br /><br />To download this to your Ubuntu box, use the <span style="font-style:italic;">wget</span> command:<br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><br />wget http://apache.mirrors.hoobly.com/tomcat/tomcat-6/v6.0.20/bin/apache-tomcat-6.0.20.tar.gz<br /></pre></div><br />Now decide where you want the Tomcat binary to install to, and what user account you want to own it. It is a good idea to not run the Tomcat from your superuser account for security reasons. Once you have decided where your tomcat is going, switch to that folder and extract the tarball to it by using the following command:<br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><br />tar xzvf /path-to-tarball/apache-tomcat-6.0.20.tar.gz<br /></pre></div><br />This will create your Tomcat home area (and base if you intend to have base=home). If you want to, you can change directory into the <span style="font-style:italic;">bin</span> folder and simply run <span style="font-style:italic;">startup.sh</span> to get your Tomcat running.<br /><br />Seeing as this is going to be a server, you should also setup the Tomcat as a service, so that it starts up and shuts down as appropriate when the machine bounces.<br /><br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Tomcat as a Service</span><br /><br />The following simple Tomcat startup script can be used to control Tomcat as a service. Note that this script doesn't have non-superuser support in it yet, and only has start/stop/restart control cases. However, it is a good starting point, and is a lot better than having nothing:<br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><br />#!/bin/sh -e<br />#<br /># Startup script for Tomcat<br /><br /><br />CATALINA_HOME=/tomcat/apache-tomcat-6.0.20<br />CATALINA_BASE=/tomcat/apache-tomcat-6.0.20<br />JAVA_HOME=/usr/lib/jvm/java-6-sun<br />export CATALINA_HOME CATALINA_BASE JAVA_HOME<br /><br />start_tomcat="$CATALINA_HOME/bin/catalina.sh start"<br />stop_tomcat="$CATALINA_HOME/bin/catalina.sh stop"<br /><br />start() {<br /> echo -n "Starting tomcat: "<br /> cd $CATALINA_HOME<br /> ${start_tomcat}<br /> echo "done."<br />}<br />stop() {<br /> echo -n "Shutting down tomcat: "<br /> cd $CATALINA_HOME<br /> ${stop_tomcat}<br /> echo "done."<br />}<br /><br />case "$1" in<br /> start)<br /> start<br /> ;;<br /> stop)<br /> stop<br /> ;;<br /> restart)<br /> stop<br /> sleep 10<br /> start<br /> ;;<br /> *)<br /> echo "Usage: $0 {start|stop|restart}"<br />esac<br /><br />exit 0<br /></pre></div><br />Create a file in your <span style="font-style:italic;">/etc/init.d</span> folder, called <span style="font-style:italic;">tomcat</span> (or whatever you want it to be called). Run the following command on the file to make it executable:<br /><br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><br />chmod +x /etc/init.d/tomcat<br /></pre></div><br />Now you can control the Tomcat using this script and the <span style="font-style:italic;">start/stop/restart</span> parameters.<br /><br />To let the operating system know it is a service that needs automatic controlling, you run the following command from the <span style="font-style:italic;">/etc/init.d</span> folder (note that the following command assumes the script is named <span style="font-style:italic;">tomcat</span>):<br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><br />update-rc.d tomcat defaults<br /></pre></div><br />Once you've done this you have successfully installed Java 6 and Tomcat 6 and set it up as a service! I suggest looking at some of the following advanced topics if you're doing enterprise Java development:<br /><br /><ul><li>Running multiple tomcats using CATALINA_BASE and CATALINA_HOME settings.</li><br /><li>Securing your Tomcat to run over HTTPS using SSL.</li><br /><li><a href="http://thejavamonkey.blogspot.com/2008/07/using-apache-httpd-server-as-secure.html">Fronting your Tomcat(s) with a secure Apache proxy server</a>.</li><br /><li>Externally debugging your Tomcat using SSH tunneling.</li></ul><br /><br />If anyone wants a blog on any of these topics then let me know, particularly the tunneling one as its pretty cool and very useful!The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com12tag:blogger.com,1999:blog-5374152728206138749.post-72005933200040187882009-05-21T12:14:00.009+12:002009-06-19T17:45:03.164+12:00Axis 2 Clients - Socket Exception - Too many open files - Cleaning up idle connections.<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMr5drOpc_eEp2i6OTVogcvxy9su35rIe8iNPDDFHrjHBVIDClG7cY2I9ynbEiLAAwAU822O6MKw10TA4hZtolh9qps0CRmlfDEHr4sWsjhDVfn82mJ7nkFhrQf-ZBgUP6oBxs6tvIPXAl/s1600-h/doc-exc.PNG"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 328px; height: 154px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMr5drOpc_eEp2i6OTVogcvxy9su35rIe8iNPDDFHrjHBVIDClG7cY2I9ynbEiLAAwAU822O6MKw10TA4hZtolh9qps0CRmlfDEHr4sWsjhDVfn82mJ7nkFhrQf-ZBgUP6oBxs6tvIPXAl/s400/doc-exc.PNG" border="0" alt=""id="BLOGGER_PHOTO_ID_5338066368400996226" /></a>Its been half a year since my last post. In that time, I've separated from my wife, settled out my affairs with her, got a new job and worked overseas. Its pretty understandable, why I haven't devoted much time to my blog then!<br /><br />The new company I work for, happens to use Axis 2 as their SOAP service engine. The setup is more complicated than I've previously used. There are about eight satellite Axis 2 SOAP services running on Windows servers, all talking to a central Axis 2 SOAP service, fronted by a GWT client. The communications is two way between the satellite web services and the central service. That is each satellite service knows about the central service, and vice versa.<br /><br />That is, the central service acts a client to the satellite services and has client stubs for each of them. The GWT client, by its very nature, ends up causing multiple threads in the central SOAP service to be talking to the satellite SOAP services at the same time. There could be eight clients in the central service simultaneously talking to the satellite services, each with multiple threads in it.<br /><br />Now this is all fine, and has been working since well before I started my new job (i.e. its not my fault!!). However, the other day, several times in a row, the central service suddenly started hanging with the Tomcat servlet in it printing the following exception out over and over:<br /><br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><br />SEVERE: Socket accept failed<br />java.net.SocketException: Too many open files<br /> at java.net.PlainSocketImpl.socketAccept(Native Method)<br /> at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:384)<br /> at java.net.ServerSocket.implAccept(ServerSocket.java:453)<br /> at java.net.ServerSocket.accept(ServerSocket.java:421)<br /> at org.apache.tomcat.util.net.DefaultServerSocketFactory.acceptSocket(DefaultServerSocketFactory.java:61)<br /> at org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:310)<br /> at java.lang.Thread.run(Thread.java:619)<br />May 19, 2009 5:31:32 PM org.apache.tomcat.util.net.JIoEndpoint$Acceptor run<br /></pre></div><br />This error essentially started occurring out of the blue, and then to make matters worse after restarting the Tomcat it happened 2 more times over the course of the next 24 hours. For a 24 hour available system, this is not acceptable!<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Diagnosis</span><br /><br />Firstly, the <span style="font-style:italic;">open files</span> concept relates to both file system handles and sockets. When running a Tomcat application, any jars in your WAR file will remain open during the lifetime of the tomcat, as well as JRE libraries, servlet API libraries, and many socket connections. What is happening islikely to be that your application is opening files and not closing them or opening sockets and not cleaning them up afterwards.<br /><br />Ok, so its obvious what the issue is, however, we need to know how to diagnose this. Thankfully, on certain flavours of Unix/Linux there is the <span style="font-style:italic;">lsof</span> command, or <span style="font-style:italic;">list open files</span>.<br /><br />In my case, I'm using a Debian install, which has lsof installed under <span style="font-style:italic;">/usr/sbin</span>.<br /><br />lsof lists all open files and sockets by your process, all it needs to know is the process ID. Note that its probably best to have superuser privileges when executing this command. For example:<br /><br /><span style="font-weight:bold;">lsof -p 14121</span> <br /><br />This will list all open files and sockets owned by your process. For example:<br /><br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><br />java 1456 tomcat 23r REG 253,1 27699 104136845 /data/tomcat6/lib/el-api.jar<br />java 1456 tomcat 24r REG 253,1 742089 104136851 /data/tomcat6/lib/tomcat-coyote.jar<br />java 1456 tomcat 25r REG 253,1 511240 104136848 /data/tomcat6/lib/jasper.jar<br />java 1456 tomcat 26r REG 253,1 228175 104136843 /data/tomcat6/lib/catalina-tribes.jar<br />java 1456 tomcat 27r REG 253,1 1128229 104136844 /data/tomcat6/lib/catalina.jar<br />java 1456 tomcat 28r REG 253,1 1395270 104136847 /data/tomcat6/lib/jasper-jdt.jar<br />java 1456 tomcat 29r REG 253,1 42558 104136854 /data/tomcat6/lib/tomcat-i18n-fr.jar<br />java 1456 tomcat 30r REG 253,0 840171 7799295 /usr/java/jdk1.6.0_06/jre/lib/ext/localedata.jar<br />java 1456 tomcat 31u IPv6 219309528 TCP *:xprint-server (LISTEN)<br />java 1456 tomcat 32u sock 0,5 219309526 can't identify protocol<br />java 1456 tomcat 33w REG 253,1 775017 81362966 /data/tomcat-operator/logs/www_mym2_com_access_log.20090520.log<br />java 1456 tomcat 34r CHR 1,9 2796 /dev/urandom<br />java 1456 tomcat 35u IPv6 219317480 TCP mym2-04.mgroup.biz:37457->wsm2app01:http (CLOSE_WAIT)<br />java 1456 tomcat 36u IPv6 219310003 TCP *:8011 (LISTEN)<br />java 1456 tomcat 37u IPv6 219310007 TCP localhost.localdomain:8007 (LISTEN)<br />java 1456 tomcat 40u IPv6 219317497 TCP mym2-04.mgroup.biz:57140->wsm2app02:http (CLOSE_WAIT)<br />java 1456 tomcat 41u IPv6 219317481 TCP mym2-04.mgroup.biz:40130->wsm2app01:http (CLOSE_WAIT)<br />java 1456 tomcat 42u IPv6 219317512 TCP mym2-04.mgroup.biz:46401->wsm2app01:http (CLOSE_WAIT)<br />java 1456 tomcat 43u IPv6 219317496 TCP mym2-04.mgroup.biz:53113->wsm2app01:http (CLOSE_WAIT)<br />java 1456 tomcat 45u IPv6 219317513 TCP mym2-04.mgroup.biz:58318->wsm2app01:http (CLOSE_WAIT)<br />java 1456 tomcat 46u IPv6 219317505 TCP mym2-04.mgroup.biz:44570->wsm2app01:http (CLOSE_WAIT)<br />java 1456 tomcat 47u IPv6 219317504 TCP mym2-04.mgroup.biz:37790->wsm2app01:http (CLOSE_WAIT)<br />java 1456 tomcat 48u IPv6 219317484 TCP mym2-04.mgroup.biz:54947->wsm2app01:http (CLOSE_WAIT)<br /></pre></div><br />You can see in this output snippet, that there are jars and sockets currently open by the process. If you examine the ouput further, you would be able to tell if your program has not being closing files that it should or if it has opened files multiple times. <br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">How does this relate to Axis 2?</span><br /><br />In the case of my Axis 2 client, there were two issues. The first issue, was, that I was creating an Axis 2 <span style="font-style:italic;">ConfigurationContext</span> for every remote satellite server. This ends up causing your process to open multiple instances of .jar and .mar (Axis 2 modules) files to create each context. You should generally only ever need to create one context for the lifetime of your application. When piping the lsof output to grep, and grepping for <span style="font-style:italic;">jar</span> and <span style="font-style:italic;">mar</span> it becomes obvious if you're loading mars and jars too many times in Axis 2, as you will see the same ones appearing multiple times in the output.<br /><br />The second issue, and the more important one (in my case), is the fact that once socket connections were finished with, they were not being thrown away. I was getting hundreds of sockets in the CLOSE_WAIT state. The definition of CLOSE_WAIT is as follows:<br /><br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><br />CLOSE_WAIT: The socket connection has been closed by the remote peer, and the operating system is waiting for the local application to close its half of the connection.<br /></pre></div><br />When I listed the open files for my tomcat, there were around 800 socket connections with a CLOSE_WAIT state. Whats more none of them were actually closing, they were seemingly hanging there indefinitely.<br /><br />Note that in most Unix operating systems, the maximum number of file/sockets allowed open by a given process at any given time is 1024. Using the <span style="font-style:italic;">ulimit</span> command, you can view this limit (<span style="font-style:italic;">ulimit -a</span>) will print out the limits for your system. The one to pay attention to is <span style="font-style:italic;">open files</span>.<br /><br />It is possible to increase this limit, however the best solution is to fix your (Axis 2) application up to be using resources correctly.<br /><br />Ok, so once you've made sure that you're only creating one context, the next step is to set your Axis 2 client stubs to using a multi-threaded HTTP connection manager, and forcing them to clean up idle connections when possible.<br /><br />The way I do this is by having a Service Stub Connection Pool for each target service. Every time that my service wants to talk to a remote Axis 2 service, it borrows a service stub for the particular service it wants to talk to and then returns it after its finished with. <br /><br />Within my connection pool class, I then create a multi threaded HTTP connection manager and use that instead of the standard Axis 2 connection manager. Subsequently, every time that a service stub is returned to the pool, I clean up any idle connections on the HTTP client it is using. After running tests against this connection pool in my target environment I have found it has a much better file/socket usage profile than the previous codebase I had to inherit.<br /><br />Note that if you are only talking to one remote client, you only need one instance of ServiceStubConnectionPool, not one for each target service/URL. Additionally, if you are not multi-threading your calls to remote services then you will probably not even have this connection resource issue.<br /><br />The rough code for this Connection Pool is as follows below. Note you will need to change the constructor for the ServiceStub to match the class of your Service Stub. Alternatively you could make ServiceStubConnectionPool abstract so that it can be re-used, featuring an abstract method <span style="font-style:italic;">constructServiceStub</span> to do the construction.<br /><br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><br /><A NAME="1"></A><FONT ID="Package">package</FONT> com.thejavamonkey.axis2.client;<br /><A NAME="2"> </A><br /><A NAME="3"></A><FONT ID="Import">import</FONT> java.net.URL;<br /><A NAME="4"></A><FONT ID="Import">import</FONT> java.util.ArrayList;<br /><A NAME="5"></A><FONT ID="Import">import</FONT> java.util.HashMap;<br /><A NAME="6"></A><FONT ID="Import">import</FONT> java.util.List;<br /><A NAME="7"></A><FONT ID="Import">import</FONT> java.util.Map;<br /><A NAME="8"></A><FONT ID="Import">import</FONT> org.apache.axis2.AxisFault;<br /><A NAME="9"></A><FONT ID="Import">import</FONT> org.apache.axis2.Constants;<br /><A NAME="10"></A><FONT ID="Import">import</FONT> org.apache.axis2.client.Options;<br /><A NAME="11"></A><FONT ID="Import">import</FONT> org.apache.axis2.client.Stub;<br /><A NAME="12"></A><FONT ID="Import">import</FONT> org.apache.axis2.context.ConfigurationContext;<br /><A NAME="13"></A><FONT ID="Import">import</FONT> org.apache.axis2.context.ConfigurationContextFactory;<br /><A NAME="14"></A><FONT ID="Import">import</FONT> org.apache.axis2.transport.http.HTTPConstants;<br /><A NAME="15"></A><FONT ID="Import">import</FONT> org.apache.commons.httpclient.HostConfiguration;<br /><A NAME="16"></A><FONT ID="Import">import</FONT> org.apache.commons.httpclient.HttpClient;<br /><A NAME="17"></A><FONT ID="Import">import</FONT> org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;<br /><A NAME="18"></A><FONT ID="Import">import</FONT> org.apache.commons.httpclient.params.HttpConnectionManagerParams;<br /><A NAME="19"></A><FONT ID="Import">import</FONT> org.apache.log4j.Logger;<br /><A NAME="20"> </A><br /><A NAME="21"></A><FONT ID="FormalComment">/**<br /><A NAME="22"></A> * <p><code>Service Stub Connection Pool</code> is used to pool<br /><A NAME="23"></A> * Axis 2 client service stubs. It provides functionality for<br /><A NAME="24"></A> * setting the service stubs to run with a multi-threaded<br /><A NAME="25"></A> * connection manager and for closing idle socket connections.</p><br /><A NAME="26"></A> *<br /><A NAME="27"></A> * @author thejavamonkey<br /><A NAME="28"></A> */</FONT><br /><A NAME="29"></A><FONT ID="Public">public</FONT> <FONT ID="Class">class</FONT> ServiceStubConnectionPool {<br /><A NAME="30"> </A><br /><A NAME="31"></A> <FONT ID="FormalComment">/**<br /><A NAME="32"></A> * Creates a new instance of ServiceStubConnectionPool<br /><A NAME="33"></A> */</FONT><br /><A NAME="34"></A> <FONT ID="Private">private</FONT> ServiceStubConnectionPool() {<br /><A NAME="35"></A> <FONT ID="Super">super</FONT>();<br /><A NAME="36"></A> }<br /><A NAME="37"> </A><br /><A NAME="38"></A> <FONT ID="SingleLineComment">// The logger for this class.<br /><A NAME="39"></A></FONT> <FONT ID="Private">private</FONT> <FONT ID="Final">final</FONT> <FONT ID="Static">static</FONT> Logger logger = <br /><A NAME="40"></A> Logger.getLogger( <A HREF="../../../../com/thejavamonkey/axis2/client/ServiceStubConnectionPool.java.html">ServiceStubConnectionPool</A>.<FONT ID="Class">class</FONT>);<br /><A NAME="41"> </A><br /><A NAME="42"></A> <FONT ID="SingleLineComment">// The axis2 client configuration context - only one of these should be around <br /><A NAME="43"></A></FONT> <FONT ID="SingleLineComment">// for your whole application. This is staticly created on demand.<br /><A NAME="44"></A></FONT> <FONT ID="Private">private</FONT> <FONT ID="Static">static</FONT> ConfigurationContext context = <FONT ID="Null">null</FONT>;<br /><A NAME="45"> </A><br /><A NAME="46"></A> <FONT ID="SingleLineComment">// Http connection management - these two member variables are needed <br /><A NAME="47"></A></FONT> <FONT ID="SingleLineComment">// per remote service.<br /><A NAME="48"></A></FONT> <FONT ID="Private">private</FONT> MultiThreadedHttpConnectionManager httpConnectionManager = <FONT ID="Null">null</FONT>;<br /><A NAME="49"></A> <FONT ID="Private">private</FONT> HttpClient httpClient = <FONT ID="Null">null</FONT>;<br /><A NAME="50"> </A><br /><A NAME="51"></A> <FONT ID="SingleLineComment">// Setup the multi-threaded connection manager. This is only done once <br /><A NAME="52"></A></FONT> <FONT ID="SingleLineComment">// per target service.<br /><A NAME="53"></A></FONT> <FONT ID="Private">private</FONT> <FONT ID="Void">void</FONT> setupHttpConnectionManager( String host, <FONT ID="Int">int</FONT> port, String scheme) {<br /><A NAME="54"></A> <FONT ID="If">if</FONT> ( httpConnectionManager == <FONT ID="Null">null</FONT>) {<br /><A NAME="55"></A> logger.info( <FONT ID="StringLiteral">"setupHttpConnectionManager:"</FONT> + scheme + <FONT ID="StringLiteral">" "</FONT> + host + <FONT ID="StringLiteral">" "</FONT> + port);<br /><A NAME="56"></A> httpConnectionManager = <FONT ID="New">new</FONT> MultiThreadedHttpConnectionManager();<br /><A NAME="57"></A> HttpConnectionManagerParams params = <FONT ID="New">new</FONT> HttpConnectionManagerParams();<br /><A NAME="58"></A> HostConfiguration hostConfiguration = <FONT ID="New">new</FONT> HostConfiguration();<br /><A NAME="59"></A> hostConfiguration.setHost( host, port, scheme);<br /><A NAME="60"></A> params.setMaxTotalConnections( <FONT ID="IntegerLiteral">100</FONT>);<br /><A NAME="61"></A> params.setMaxConnectionsPerHost( hostConfiguration, <FONT ID="IntegerLiteral">25</FONT>);<br /><A NAME="62"></A> httpConnectionManager.setParams( params);<br /><A NAME="63"></A> httpClient = <FONT ID="New">new</FONT> HttpClient( httpConnectionManager);<br /><A NAME="64"></A> }<br /><A NAME="65"></A> }<br /><A NAME="66"> </A><br /><A NAME="67"></A> <FONT ID="SingleLineComment">// Configure the connection for the client stub to use.<br /><A NAME="68"></A></FONT> <FONT ID="Private">private</FONT> <FONT ID="Void">void</FONT> configureConnection( Stub client, String serviceURL) {<br /><A NAME="69"></A> logger.info( <FONT ID="StringLiteral">"configureConnection: "</FONT> + serviceURL);<br /><A NAME="70"></A> <FONT ID="If">if</FONT> ( httpConnectionManager == <FONT ID="Null">null</FONT>) {<br /><A NAME="71"></A> <FONT ID="SingleLineComment">// Parse the hostname, protocol and port for the target service.<br /><A NAME="72"></A></FONT> String host = <FONT ID="StringLiteral">""</FONT>;<br /><A NAME="73"></A> <FONT ID="Int">int</FONT> port = <FONT ID="IntegerLiteral">80</FONT>;<br /><A NAME="74"></A> <FONT ID="Int">int</FONT> portIndex = -<FONT ID="IntegerLiteral">1</FONT>;<br /><A NAME="75"></A> String scheme = serviceURL.substring( <FONT ID="IntegerLiteral">0</FONT>, serviceURL.indexOf( <FONT ID="StringLiteral">":"</FONT>));<br /><A NAME="76"></A> <FONT ID="If">if</FONT> ( scheme.equalsIgnoreCase( <FONT ID="StringLiteral">"https"</FONT>)) {<br /><A NAME="77"></A> port = <FONT ID="IntegerLiteral">443</FONT>;<br /><A NAME="78"></A> }<br /><A NAME="79"></A> <FONT ID="For">for</FONT> ( <FONT ID="Int">int</FONT> i=scheme.length()+<FONT ID="IntegerLiteral">3</FONT>; i<serviceURL.length(); i++) {<br /><A NAME="80"></A> <FONT ID="If">if</FONT> ( serviceURL.charAt( i) == <FONT ID="CharacerLiteral">':'</FONT>) {<br /><A NAME="81"></A> portIndex = i + <FONT ID="IntegerLiteral">1</FONT>;<br /><A NAME="82"></A> }<br /><A NAME="83"></A> <FONT ID="Else">else</FONT> <FONT ID="If">if</FONT> ( serviceURL.charAt( i) == <FONT ID="CharacerLiteral">'/'</FONT>) {<br /><A NAME="84"></A> <FONT ID="If">if</FONT> ( portIndex > -<FONT ID="IntegerLiteral">1</FONT>) {<br /><A NAME="85"></A> port = Integer.parseInt( serviceURL.substring( portIndex, i));<br /><A NAME="86"></A> host = serviceURL.substring( scheme.length()+<FONT ID="IntegerLiteral">3</FONT>, portIndex - <FONT ID="IntegerLiteral">1</FONT>);<br /><A NAME="87"></A> }<br /><A NAME="88"></A> <FONT ID="Else">else</FONT> {<br /><A NAME="89"></A> host = serviceURL.substring( scheme.length()+<FONT ID="IntegerLiteral">3</FONT>, i);<br /><A NAME="90"></A> }<br /><A NAME="91"></A> <FONT ID="Break">break</FONT>;<br /><A NAME="92"></A> }<br /><A NAME="93"></A> }<br /><A NAME="94"></A> logger.info(<FONT ID="StringLiteral">"Configuring connection manager for URL ["</FONT> +<br /><A NAME="95"></A> serviceURL + <FONT ID="StringLiteral">"]: scheme = ["</FONT> + scheme + <FONT ID="StringLiteral">"] host = ["</FONT> + host +<br /><A NAME="96"></A> <FONT ID="StringLiteral">"] port = ["</FONT> + port + <FONT ID="StringLiteral">"]"</FONT>);<br /><A NAME="97"></A> setupHttpConnectionManager( host, port, scheme);<br /><A NAME="98"></A> }<br /><A NAME="99"></A> Options options = client._getServiceClient().getOptions();<br /><A NAME="100"></A> options.setProperty( HTTPConstants.REUSE_HTTP_CLIENT, Constants.VALUE_TRUE);<br /><A NAME="101"></A> options.setProperty( HTTPConstants.CACHED_HTTP_CLIENT, httpClient);<br /><A NAME="102"></A> }<br /><A NAME="103"> </A><br /><A NAME="104"></A> <FONT ID="SingleLineComment">// Get the axis 2 context.<br /><A NAME="105"></A></FONT> <FONT ID="Private">private</FONT> <FONT ID="Static">static</FONT> <FONT ID="Synchronized">synchronized</FONT> ConfigurationContext getContext( URL axis2confURL, <br /><A NAME="106"></A> URL repositoryURL) <FONT ID="Throws">throws</FONT> AxisFault {<br /><A NAME="107"></A> <FONT ID="If">if</FONT> ( context == <FONT ID="Null">null</FONT>) {<br /><A NAME="108"></A> context = ConfigurationContextFactory.createConfigurationContextFromURIs( <br /><A NAME="109"></A> axis2confURL, repositoryURL);<br /><A NAME="110"></A> }<br /><A NAME="111"></A> <FONT ID="Return">return</FONT> context;<br /><A NAME="112"></A> }<br /><A NAME="113"> </A><br /><A NAME="114"></A> <FONT ID="SingleLineComment">// Map of ServiceStubConnectionPool instances. Keyed on url.<br /><A NAME="115"></A></FONT> <FONT ID="Private">private</FONT> <FONT ID="Final">final</FONT> <FONT ID="Static">static</FONT> Map<String, <A HREF="../../../../com/thejavamonkey/axis2/client/ServiceStubConnectionPool.java.html">ServiceStubConnectionPool</A>> factories =<br /><A NAME="116"></A> <FONT ID="New">new</FONT> HashMap<String, <A HREF="../../../../com/thejavamonkey/axis2/client/ServiceStubConnectionPool.java.html">ServiceStubConnectionPool</A>>();<br /><A NAME="117"> </A><br /><A NAME="118"></A> <FONT ID="SingleLineComment">// Get a factory by url. Create one if it doesn't exist. These are required per<br /><A NAME="119"></A></FONT> <FONT ID="SingleLineComment">// remote URL.<br /><A NAME="120"></A></FONT> <FONT ID="Public">public</FONT> <FONT ID="Static">static</FONT> <FONT ID="Synchronized">synchronized</FONT> <FONT ID="Final">final</FONT> <A HREF="../../../../com/thejavamonkey/axis2/client/ServiceStubConnectionPool.java.html">ServiceStubConnectionPool</A> getInstance( String url ) {<br /><A NAME="121"></A> <A HREF="../../../../com/thejavamonkey/axis2/client/ServiceStubConnectionPool.java.html">ServiceStubConnectionPool</A> connPool = factories.get( url);<br /><A NAME="122"></A> <FONT ID="If">if</FONT> ( connPool == <FONT ID="Null">null</FONT> ) {<br /><A NAME="123"></A> connPool = <FONT ID="New">new</FONT> <A HREF="../../../../com/thejavamonkey/axis2/client/ServiceStubConnectionPool.java.html">ServiceStubConnectionPool</A>();<br /><A NAME="124"></A> factories.put( url, ServiceStubConnectionPool);<br /><A NAME="125"></A> }<br /><A NAME="126"></A> <FONT ID="Return">return</FONT> connPool;<br /><A NAME="127"></A> }<br /><A NAME="128"> </A><br /><A NAME="129"></A> <FONT ID="SingleLineComment">// The client stub pool - one of these is required per connection pool.<br /><A NAME="130"></A></FONT> <FONT ID="Private">private</FONT> <FONT ID="Final">final</FONT> List<Stub> servicePool = <FONT ID="New">new</FONT> ArrayList();<br /><A NAME="131"> </A><br /><A NAME="132"></A> <FONT ID="FormalComment">/**<br /><A NAME="133"></A> * Borrow a service stub from the pool. This creates stubs as<br /><A NAME="134"></A> * they are required. The stub MUST be returned to the pool after it has<br /><A NAME="135"></A> * been used (in a finally block if possible!)<br /><A NAME="136"></A> * @param axis2confURL the url location to the Axis 2 client configuration file<br /><A NAME="137"></A> * @param repositoryURL the url location to the Axis 2 repository (modules,conf etc)<br /><A NAME="138"></A> * @param serviceURL the http URL of the user authentication service<br /><A NAME="139"></A> * @return the stub<br /><A NAME="140"></A> */</FONT><br /><A NAME="141"></A> <FONT ID="Public">public</FONT> <FONT ID="Synchronized">synchronized</FONT> Stub borrowService(<br /><A NAME="142"></A> String serviceURL, URL axis2confURL, URL repositoryURL,<br /><A NAME="143"></A> String username, String password) <FONT ID="Throws">throws</FONT> AxisFault {<br /><A NAME="144"></A> logger.debug( <FONT ID="StringLiteral">"Method call: borrowService [repositoryURL = "</FONT> + repositoryURL + <br /><A NAME="145"></A> <FONT ID="StringLiteral">", axis2confURL = "</FONT> + axis2confURL + <FONT ID="StringLiteral">"]"</FONT>);<br /><A NAME="146"></A> MonitorService10Stub result = <FONT ID="Null">null</FONT>;<br /><A NAME="147"></A> <FONT ID="If">if</FONT> ( servicePool.size() > <FONT ID="IntegerLiteral">0</FONT>) {<br /><A NAME="148"></A> result = servicePool.remove( <FONT ID="IntegerLiteral">0</FONT>);<br /><A NAME="149"></A> }<br /><A NAME="150"></A> <FONT ID="Else">else</FONT> {<br /><A NAME="151"></A> <FONT ID="SingleLineComment">// ******** NOTE: change the following line to use your own ServiceStub class<br /><A NAME="152"></A></FONT> result = <FONT ID="New">new</FONT> MonitorService10Stub( getContext( axis2confURL, repositoryURL), <br /><A NAME="153"></A> serviceURL);<br /><A NAME="154"></A> <FONT ID="SingleLineComment">// Configure connection management to support a multiple threaded, reusable<br /><A NAME="155"></A></FONT> <FONT ID="SingleLineComment">// http connection client.<br /><A NAME="156"></A></FONT> configureConnection( result, serviceURL);<br /><A NAME="157"></A> }<br /><A NAME="158"></A> <FONT ID="SingleLineComment">// Configure your outflow security (rampart/WS-Security) here!<br /><A NAME="159"></A></FONT> <FONT ID="SingleLineComment">//configureOutflowSecurity( username, password, result);<br /><A NAME="160"></A></FONT><br /><A NAME="161"></A> logger.debug( <FONT ID="StringLiteral">"Method exit: borrowService ["</FONT> + result + <FONT ID="StringLiteral">"]"</FONT>);<br /><A NAME="162"></A> <FONT ID="Return">return</FONT> result;<br /><A NAME="163"></A> }<br /><A NAME="164"> </A><br /><A NAME="165"></A> <FONT ID="FormalComment">/**<br /><A NAME="166"></A> * Return a stub to the pool. Call this after you have made your client call.<br /><A NAME="167"></A> * @param service the service stub<br /><A NAME="168"></A> */</FONT><br /><A NAME="169"></A> <FONT ID="Public">public</FONT> <FONT ID="Synchronized">synchronized</FONT> <FONT ID="Void">void</FONT> returnService( Stub service) {<br /><A NAME="170"></A> logger.debug( <FONT ID="StringLiteral">"Method call: returnService [service = "</FONT> +<br /><A NAME="171"></A> service + <FONT ID="StringLiteral">"]"</FONT>);<br /><A NAME="172"></A> <FONT ID="SingleLineComment">// Clean up idle connections.<br /><A NAME="173"></A></FONT> httpConnectionManager.closeIdleConnections( <FONT ID="IntegerLiteral">20000</FONT>);<br /><A NAME="174"></A> servicePool.add( service);<br /><A NAME="175"></A> logger.debug( <FONT ID="StringLiteral">"Method exit: returnService"</FONT>);<br /><A NAME="176"></A> }<br /><A NAME="177"></A>}<br /><A NAME="178"> </A><br /></pre></div>The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com9tag:blogger.com,1999:blog-5374152728206138749.post-84222750765224964422008-10-25T10:17:00.018+13:002008-10-26T18:50:24.266+13:00Processing large XML documents with x-query<br/><p><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgba3mInS1Tq6wOLw2LasZ_xw5jxQP3zcRIMgUPpB3CZXcscsneFFdLofp-Zi8TxLXwJHYynuZJQZUWrWCULwCznkJWVeME1NjcaA98WwOmV65AbIIiYbsKGixJxeGQlCyzH3ndSR1NmhV_/s1600-h/gk1.gif"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgba3mInS1Tq6wOLw2LasZ_xw5jxQP3zcRIMgUPpB3CZXcscsneFFdLofp-Zi8TxLXwJHYynuZJQZUWrWCULwCznkJWVeME1NjcaA98WwOmV65AbIIiYbsKGixJxeGQlCyzH3ndSR1NmhV_/s320/gk1.gif" border="0" alt=""id="BLOGGER_PHOTO_ID_5260833630406185842" /></a><br />If you are doing enterprise Java development I can guarantee that you will at some point have to deal with XML. XML has become ubiquitous with enterprise software as a medium for storing, processing and communicating information.<br /><br />Depending on the parameters of your system, you will need to make an up-front choice of how you are going to process the XML. The three major options are:</p><br /><br /><ol><li>Read the the document in as a <a href="http://en.wikipedia.org/wiki/Document_Object_Model" onmouseover="monkeyTip('Document Object Model::The Document Object Model (DOM) is a platform- and language-independent standard object model for representing HTML or XML and related formats.<br><br>Because the DOM supports navigation in any direction (e.g., parent and previous sibling) and allows for arbitrary modifications,<br>an implementation must at least buffer the document that has been read so far (or some parsed form of it).<br>Hence the DOM is likely to be best suited for applications where the document must be accessed repeatedly or out of sequence order.<br>');" onmouseout="UnTip()">Document Object Model (DOM)</a> and process it in memory in its entirety.</li><li>Implement <a href="http://en.wikipedia.org/wiki/Simple_API_for_XML" onmouseover="monkeyTip('SAX Event Handling::SAX (Simple API for XML) is a serial access parser API for XML.<br><br>A parser which implements SAX (ie, a SAX Parser) functions as a stream parser, with an event-driven API.<br>The user codes a number of callback methods that will be called when events occur during parsing.<br>The SAX events include:<br><br><ul><li>XML Text nodes</li><li>XML Element nodes</li><li>XML Processing Instructions</li><li>XML Comments</li></ul><br>Events are fired when each of these XML features are encountered, and again when the end of them is encountered.<br>XML attributes are provided as part of the data passed to element events.<br><br>SAX parsing is unidirectional; previously parsed data cannot be re-read without starting the parsing operation again<br>');" onmouseout="UnTip()">SAX Event Handling (SAX)</a> to stream the document in, grabbing the data you need out of it.</li><li>Use <a href="http://en.wikipedia.org/wiki/Xquery" onmouseover="monkeyTip('XQuery::XQuery is a query language (with some programming language features) that is designed to query collections of XML data. It is semantically similar to SQL.<br><br>XQuery 1.0 was developed by the XML Query working group of the W3C. The work was closely coordinated with the development of XSLT 2.0 by the XSL Working Group:<br>the two groups shared responsibility for XPath 2.0, which is a subset of XQuery 1.0. XQuery 1.0 became a W3C Recommendation on January 23, 2007.<br><br>"The mission of the XML Query project is to provide flexible query facilities to extract data from real and virtual documents on the World Wide Web,<br>therefore finally providing the needed interaction between the Web world and the database world. Ultimately, collections of XML files will be accessed like databases".<br>');" onmouseout="UnTip()">XQuery</a> to query, transform and extract the data you need from the document.</li></ol><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">The DOM</span><br /><br />If you are processing relatively small documents at a low frequency, the DOM approach wins hands down - because its simply a matter of dropping in <span style="font-style:italic;">dom4j.jar</span> and adding the following code:<br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><br /> SAXReader reader = new SAXReader();<br /> Document document = reader.read( MyClass.class.getClassLoader().<br /> getResourceAsStream( "document.xml"));<br /></pre></div>However, if you have a high frequency of documents, and/or they are large in size, then using the DOM is problematic. When writing performant Java code, it is important to ensure that the code is not I/O bound and that you are not needlessly creating objects. In Java object creation is fairly expensive.<br /><br />The problem with the DOM is that it creates <span style="font-style:italic;">a lot</span> of objects to represent even a simple XML document. <br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Safe SAX?</span><br /><br />If the DOM doesn't suit, then the next approach is to use SAX event handling to process the documents. This requires you to write event handlers that get called when start, close and text nodes <span style="font-style:italic;">events</span> are found in the document. Your event handler can then build up a simple object model in memory to represent the document. This has the advantage that it keeps the object creation to a minimum, and features you don't care about in the document can be ignored. The downside to this is that complex XML documents require a lot of code to handle them. As soon as a SAX parser becomes complicated, it becomes incredibly difficult to understand what is going on, as it can span hundreds of lines of code.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Massive!</span><br /><br />While SAX handling of documents is fine, sometimes you will be faced with truly massive XML documents to process, and in some cases you will not even know the exact schema - e.g. 500 megabytes in size. Situations like this call for a different set of tools to handle. Often when required to process big documents you are only interested in certain elements buried deep in the document structure. Writing a SAX handler to find these in a non-trivial or even unknown schema format is incredibly complicated and long-winded. In these cases, XQuery is the hands down winner for both complexity (or lack thereof) and performance.<br /><br />XQuery is essentially SQL for XML. You write a query, and run it against a document. Anything in the document that matches your query can be returned to work on in your code. This has the advantage of not requiring lots of objects to be created as well as being simple to configure/use. Many of the more advanced features of SQL are also available such as joins and string functions. The XQuery syntax uses <a href="http://en.wikipedia.org/wiki/XPath_2.0">XPath</a> as base for its queries.<br /><br />There are many articles on the net about XQuery syntax, so I will not re-write that here. The initial difficulty in using XQuery for those new to it, is actually selecting a (free) implementation to use, and getting it running in your code. <br /><br />The rest of this article is a focus on getting set up quickly with an XQuery implementation in your Java project, so you don't get stuck trying to learn how to use the XQuery Java API.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Saxon XQuery</span><br /><br />The implementation of XQuery that we will be using is known as <a href="http://saxon.sourceforge.net/">Saxon</a>. Saxon has a commercial flavour and an open-source version. The open-source version differs in that it doesn't provide schema aware processing. This shouldn't trouble you - and if it is necessary you can simply get your work to buy the commercial version.<br /><br />Once you've downloaded the latest release of the open-source variant (Saxon-B), you need to add the jar file(s) to your project. In my Maven 2 POM file, I have something similar to the following:<br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><br /> <dependency><br /> <groupId>net.sf.saxon</groupId><br /> <artifactId>saxon</artifactId><br /> <version>8.7</version><br /> </dependency><br /></pre></div>There is a lot of boiler plate code required to run an XQuery in Saxon. For that reason, it is very useful to encapsulate this in a class. To achieve this, I've created two classes to do most of this work for you.<br /><br />Saxon needs to know about all the possible namespaces that will appear in the XML document that is being queried in order to understand the document. If it doesn't know them it will throw an exception during the document processing. For these purposes, I have created a simple Namespace bean that contains the namespace prefix and URI:<br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><br /><A NAME="1"></A><FONT ID="Package">package</FONT> javamonkey.app.xquery;<br /><A NAME="2"> </A><br /><A NAME="3"></A><FONT ID="FormalComment">/**<br /><A NAME="4"></A> * Describes an XML namespace. A namespace consists of a prefix and an URI.<br /><A NAME="5"></A> * @author Ants<br /><A NAME="6"></A> */</FONT><br /><A NAME="7"></A><FONT ID="Public">public</FONT> <FONT ID="Class">class</FONT> Namespace {<br /><A NAME="2"> </A><br /><A NAME="9"></A> <FONT ID="Private">private</FONT> <FONT ID="Final">final</FONT> String uri;<br /><A NAME="10"></A> <FONT ID="Private">private</FONT> <FONT ID="Final">final</FONT> String prefix;<br /><A NAME="2"> </A><br /><A NAME="12"></A> <FONT ID="Public">public</FONT> Namespace( String uri, String prefix) {<br /><A NAME="13"></A> <FONT ID="This">this</FONT>.uri = uri;<br /><A NAME="14"></A> <FONT ID="This">this</FONT>.prefix = prefix;<br /><A NAME="15"></A> }<br /><A NAME="2"> </A><br /><A NAME="17"></A> <FONT ID="Public">public</FONT> String getPrefix() {<br /><A NAME="18"></A> <FONT ID="Return">return</FONT> prefix;<br /><A NAME="19"></A> }<br /><A NAME="2"> </A><br /><A NAME="21"></A> <FONT ID="Public">public</FONT> String getURI() {<br /><A NAME="22"></A> <FONT ID="Return">return</FONT> uri;<br /><A NAME="23"></A> }<br /><A NAME="2"> </A><br /><A NAME="25"></A>}<br /><A NAME="26"></A><br /></pre></div>The class does that all the work is called <span style="font-style:italic;">XQueryRunner</span>. This has one method on it, <span style="font-style:italic;">queryForXmlDocument</span>. This takes a list of namespaces, an XML document dressed up in an Input Source (see below for example) and the x-query itself. It returns a string that contains an XML document. Check it out:<br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><br /><A NAME="1"></A><FONT ID="Package">package</FONT> javamonkey.app.xquery;<br /><A NAME="2"> </A><br /><A NAME="3"></A><FONT ID="Import">import</FONT> java.io.IOException;<br /><A NAME="4"></A><FONT ID="Import">import</FONT> java.io.StringWriter;<br /><A NAME="5"></A><FONT ID="Import">import</FONT> java.util.List;<br /><A NAME="6"></A><FONT ID="Import">import</FONT> java.util.Properties;<br /><A NAME="7"></A><FONT ID="Import">import</FONT> javax.xml.transform.OutputKeys;<br /><A NAME="8"></A><FONT ID="Import">import</FONT> javax.xml.transform.sax.SAXSource;<br /><A NAME="9"></A><FONT ID="Import">import</FONT> javax.xml.transform.stream.StreamResult;<br /><A NAME="10"></A><FONT ID="Import">import</FONT> net.sf.saxon.Configuration;<br /><A NAME="11"></A><FONT ID="Import">import</FONT> net.sf.saxon.om.DocumentInfo;<br /><A NAME="12"></A><FONT ID="Import">import</FONT> net.sf.saxon.query.DynamicQueryContext;<br /><A NAME="13"></A><FONT ID="Import">import</FONT> net.sf.saxon.query.StaticQueryContext;<br /><A NAME="14"></A><FONT ID="Import">import</FONT> net.sf.saxon.query.XQueryExpression;<br /><A NAME="15"></A><FONT ID="Import">import</FONT> net.sf.saxon.trans.XPathException;<br /><A NAME="16"></A><FONT ID="Import">import</FONT> org.xml.sax.InputSource;<br /><A NAME="17"></A><FONT ID="Import">import</FONT> org.apache.log4j.Logger;<br /><A NAME="2"> </A><br /><A NAME="19"></A><FONT ID="FormalComment">/**<br /><A NAME="20"></A> * X Query Runner provides an API to compile and run x-queries against an<br /><A NAME="21"></A> * XML document.<br /><A NAME="22"></A> *<br /><A NAME="23"></A> * @author Ants<br /><A NAME="24"></A> */</FONT><br /><A NAME="25"></A><FONT ID="Public">public</FONT> <FONT ID="Class">class</FONT> XQueryRunner {<br /><A NAME="2"> </A><br /><A NAME="27"></A> <FONT ID="SingleLineComment">// The logger for this class.<br /><A NAME="28"></A></FONT> <FONT ID="Private">private</FONT> <FONT ID="Static">static</FONT> <FONT ID="Final">final</FONT> Logger logger = Logger.getLogger( <A HREF="../../../javamonkey/app/xquery/XQueryRunner.java.html">XQueryRunner</A>.<FONT ID="Class">class</FONT>);<br /><A NAME="2"> </A><br /><A NAME="30"></A> <FONT ID="FormalComment">/**<br /><A NAME="31"></A> * Run a query against the given xml document.<br /><A NAME="32"></A> * @param namespaces defines the namespaces that are expected in the document<br /><A NAME="33"></A> * that is being queried<br /><A NAME="34"></A> * @param xmlDocument the document to query<br /><A NAME="35"></A> * @param queryString the x-query<br /><A NAME="36"></A> * @return the result of the x-query<br /><A NAME="37"></A> * @throws net.sf.saxon.trans.XPathException on any XPath errors in the query<br /><A NAME="38"></A> * compilation or execution<br /><A NAME="39"></A> * @throws java.io.IOException on any errors processing the input document<br /><A NAME="40"></A> */</FONT><br /><A NAME="41"></A> <FONT ID="Public">public</FONT> String queryForXmlDocument( List<<A HREF="../../../javamonkey/app/xquery/Namespace.java.html">Namespace</A>> namespaces,<br /><A NAME="42"></A> InputSource xmlDocument, String queryString)<br /><A NAME="43"></A> <FONT ID="Throws">throws</FONT> XPathException, IOException {<br /><A NAME="44"></A> logger.debug( <FONT ID="StringLiteral">"entering XQueryRunner.queryForXmlDocument() query = ["</FONT> +<br /><A NAME="45"></A> queryString + <FONT ID="StringLiteral">"]"</FONT>);<br /><A NAME="46"></A> StringWriter output = <FONT ID="New">new</FONT> StringWriter();<br /><A NAME="2"> </A><br /><A NAME="48"></A> XQueryExpression expression = <FONT ID="Null">null</FONT>;<br /><A NAME="49"></A> Configuration configuration = <FONT ID="New">new</FONT> Configuration();<br /><A NAME="50"></A> StaticQueryContext SQC = <FONT ID="New">new</FONT> StaticQueryContext( configuration);<br /><A NAME="51"></A> DynamicQueryContext DQC = <FONT ID="New">new</FONT> DynamicQueryContext( configuration);<br /><A NAME="2"> </A><br /><A NAME="53"></A> <FONT ID="Try">try</FONT> {<br /><A NAME="54"></A> <FONT ID="Try">try</FONT> {<br /><A NAME="55"></A> <FONT ID="SingleLineComment">// Compile the query.<br /><A NAME="56"></A></FONT> <FONT ID="For">for</FONT> ( <A HREF="../../../javamonkey/app/xquery/Namespace.java.html">Namespace</A> namespace : namespaces) {<br /><A NAME="57"></A> SQC.declareNamespace( namespace.getURI(), namespace.getPrefix());<br /><A NAME="58"></A> }<br /><A NAME="59"></A> expression = SQC.compileQuery( queryString);<br /><A NAME="60"></A> SQC = expression.getStaticContext().getUserQueryContext();<br /><A NAME="2"> </A><br /><A NAME="62"></A> SAXSource documentSource = <FONT ID="New">new</FONT> SAXSource( xmlDocument);<br /><A NAME="63"></A> DocumentInfo docInfo = SQC.buildDocument( documentSource);<br /><A NAME="64"></A> DQC.setContextItem( docInfo);<br /><A NAME="65"></A> <FONT ID="SingleLineComment">// Choose to output XML from the XQuery results (instead of plain text).<br /><A NAME="66"></A></FONT> Properties props = <FONT ID="New">new</FONT> Properties();<br /><A NAME="67"></A> props.setProperty( OutputKeys.METHOD, <FONT ID="StringLiteral">"xml"</FONT>);<br /><A NAME="68"></A> props.setProperty( OutputKeys.INDENT, <FONT ID="StringLiteral">"yes"</FONT>);<br /><A NAME="69"></A> <FONT ID="SingleLineComment">// Run the query.<br /><A NAME="70"></A></FONT> expression.run( DQC, <FONT ID="New">new</FONT> StreamResult( output), props);<br /><A NAME="71"></A> output.flush();<br /><A NAME="72"></A> String outputString = output.getBuffer().toString();<br /><A NAME="73"></A> <FONT ID="SingleLineComment">//outputString = outputString.substring( outputString.indexOf( ">") + 1);<br /><A NAME="74"></A></FONT> logger.debug( <FONT ID="StringLiteral">"XQueryRunner.queryForXmlDocument() result = "</FONT> +<br /><A NAME="75"></A> outputString);<br /><A NAME="76"></A> <FONT ID="Return">return</FONT> outputString;<br /><A NAME="77"></A> }<br /><A NAME="78"></A> <FONT ID="Finally">finally</FONT> {<br /><A NAME="79"></A> output.close();<br /><A NAME="80"></A> }<br /><A NAME="81"></A> }<br /><A NAME="82"></A> <FONT ID="Catch">catch</FONT>( XPathException e) {<br /><A NAME="83"></A> logger.error( <FONT ID="StringLiteral">"XPath error in query: "</FONT> + queryString + <FONT ID="StringLiteral">" - "</FONT> +<br /><A NAME="84"></A> e.getMessage(), e);<br /><A NAME="85"></A> <FONT ID="Throw">throw</FONT> e;<br /><A NAME="86"></A> }<br /><A NAME="87"></A> <FONT ID="Catch">catch</FONT> ( IOException e) {<br /><A NAME="88"></A> logger.error( <FONT ID="StringLiteral">"Error reading XML document from input source "</FONT> +<br /><A NAME="89"></A> e.getMessage(), e);<br /><A NAME="90"></A> <FONT ID="Throw">throw</FONT> e;<br /><A NAME="91"></A> }<br /><A NAME="92"></A> }<br /><A NAME="93"></A>}<br /></pre></div><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Lets get to it then...</span><br />Ok, so a quick example of calling it is probably a good idea also. This grabs an XML file and wraps it in an Input Source, then runs a query over it. Here goes:<br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><br /> <FONT ID="Import">import</FONT> org.xml.sax.InputSource;<br /> <FONT ID="Import">import</FONT> java.io.FileReader;<br /><br /> InputSource inputSource = <FONT ID="New">new</FONT> InputSource( new FileReader( <FONT ID="StringLiteral">"./document.xml"</FONT>));<br /> String query = <FONT ID="StringLiteral">"for $element in /rdf:RDF/rdf:desc[@xlink:arcrole="</FONT> +<br /> <FONT ID="StringLiteral">"\"http://www.me.com/my#Monkey\"] return data($element/@rdf:desc)"</FONT>;<br /> <FONT ID="SingleLineComment">// Define the namespaces.</FONT><br /> List<Namespace> namespaces = <FONT ID="New">new</FONT> ArrayList();<br /> Namespace namespace = <FONT ID="New">new</FONT> Namespace( <FONT ID="StringLiteral">"rdf"</FONT>, <br /> <FONT ID="StringLiteral">"http://www.w3.org/1999/02/22-rdf-syntax-ns"</FONT>);<br /> namespaces.add( namespace);<br /> namespace = <FONT ID="New">new</FONT> Namespace( <FONT ID="StringLiteral">"dcterms"</FONT>, <br /> <FONT ID="StringLiteral">"http://dublincore.org/2008/01/14/dcterms.rdf"</FONT>);<br /> namespaces.add( namespace);<br /> namespace = <FONT ID="New">new</FONT> Namespace( <FONT ID="StringLiteral">"xlink"</FONT>, <br /> <FONT ID="StringLiteral">"http://www.w3.org/1999/xlink"</FONT>);<br /> namespaces.add( namespace);<br /> String result = <FONT ID="New">new</FONT> XQueryRunner().queryForXmlDocument( namespaces, inputSource,<br /> queryString);<br /></pre></div>The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com9tag:blogger.com,1999:blog-5374152728206138749.post-23963930540729881452008-09-13T19:39:00.034+12:002008-10-26T18:27:46.638+13:00Axis 2 web services using Kerberos authentication!!!<br/><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjU5U71WJgjzxtHKsKkr77VwH0gLnBrkKqmDwXukLz_4QI3wDgjLUQrrwtLktF-Nqw35NPGrq2uAhQvYasntPvl0FjW1DVud0-cbks5PqfKlqBUU6bx1j7QicEGt3aLAHTE2b9-SXgfNKFh/s1600-h/sq3.gif"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjU5U71WJgjzxtHKsKkr77VwH0gLnBrkKqmDwXukLz_4QI3wDgjLUQrrwtLktF-Nqw35NPGrq2uAhQvYasntPvl0FjW1DVud0-cbks5PqfKlqBUU6bx1j7QicEGt3aLAHTE2b9-SXgfNKFh/s320/sq3.gif" border="0" alt=""id="BLOGGER_PHOTO_ID_5253523602885578898" /></a>I have mentioned in many of my previous blogs about having to implement Kerberos authentication in Axis 2 for a project at work. The project in question was an interoperable web service that had to also work out of the box with .Net WSE3 Kerberos enabled web services.<br /><br />This blog, is a work in progress build of my results, that works for most cases - a lot of people have asked me about this stuff, so I thought I'd release the binaries for it with some instructions so people can try it out. <br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">In the beginning...</span><br /><br />Axis 2 uses the <a href="http://ws.apache.org/axis2/modules/rampart/1_3/security-module.html">Rampart</a> security module to provide its <a href="http://en.wikipedia.org/wiki/WS-Security" onmouseover="monkeyTip('WS-Security::Web Services Security (WS-Security) is a communications protocol providing a means for applying security to Web services.<br><br>On April 19 2004 the WS-Security 1.0 standard was released by Oasis-Open. On February 17 2006 they released version 1.1.<br>Originally developed by IBM, Microsoft, and VeriSign, the protocol is now officially called WSS and developed via committee in Oasis-Open.<br><br>The protocol contains specifications on how integrity and confidentiality can be enforced on Web services messaging.<br>The WSS protocol includes details on the use of SAML and Kerberos, and certificate formats such as X.509.<br><br>WS-Security describes how to attach signatures and encryption headers to SOAP messages. In addition, it describes how to attach security tokens, <br>including binary security tokens such as X.509 certificates and Kerberos tickets, to messages. WS-Security incorporates security features in the<br>header of a SOAP message, working in the application layer. Thus it ensures end-to-end security.<br>');" onmouseout="UnTip()">WS-Security</a> features. Rampart in turn uses <a href="http://ws.apache.org/wss4j/">WSS4J</a> which actually implements these features.<br /><br />The bulk of the code changes I have made are to the WSS4J 1.5.3 codebase, with a few minor ones to Rampart 1.3, so that the Axis 2 configuration files can access the Kerberos functionality I've added.<br /><br />Please note that to get Kerberos working with Axis 2 you absolutely must have a working knowledge of setting up Kerberos service principals, configuring KDCs and how to troubleshoot. If you still are in the dark on some of these topics, try my previous blogs where I have covered these topics in depth:<br /><br /><ul><li><a href="http://thejavamonkey.blogspot.com/2008/07/using-apache-directory-server-as-kdc.html">Using Apache Directory Server as a KDC.</a></li><li><a href="http://thejavamonkey.blogspot.com/2008/04/clientserver-hello-world-in-kerberos.html">Client/Server Hello World in Java and Kerberos.</a></li><li><a href="http://thejavamonkey.blogspot.com/2008/03/active-directory-and-kerberos-service.html">Active Directory Kerberos service principal names.</a></li></ul><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Lets get started...</span><br /><br />The requirements are:<br /><br /><ul><li>Java 1.5</li><li>Axis 2 1.3</li><li>Rampart 1.3</li><li>A KDC - preferably Active Directory or Apache Directory 1.5.1</li><li>Client user configured in the KDC.</li><li>Service principal configured in the KDC.</li><li>Web service client and associated web service.</li></ul><br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Client Side Connections</span><br /><br />The client-side outflow security configurations are based around a new security action, <span style="font-weight:bold;">KerberosTokenSigned</span>. My initial implementation requires the SOAP messages to be digitally signed, using the Kerberos session key. That is, the shared session key that the KDC supplies both the client and server, is used to digitally sign various parts of the message. The client and server are the only entities besides the KDC who know what the session key is - therefore the act of signing requests and responses provide an indisputable guarantee that the messages are from either the client or server.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">JAAS configuration</span><br /><br />As JAAS is used to provide the Kerberos services in Java, we have to use a JAAS properties file to configure both our client and server. Note that I'm working on implementing a JAAS extension so that all this stuff can be put into the Rampart configuration in the future, as it IS annoying having so many properties files to configure something. In fact, even having one properties file other than Spring is annoying to say the least.<br /><br />The following or a similar JAAS properties file needs to be accessible from both your client and server side code:<br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><br />ClientConfiguration {<br /> com.sun.security.auth.module.Krb5LoginModule required<br /> debug=true<br /> useTicketCache=false;<br />};<br /><br/><br />ServiceConfiguration {<br /> com.sun.security.auth.module.Krb5LoginModule required<br /> useKeyTab=false<br /> storeKey=true<br /> debug=true<br /> useTicketCache=false<br /> principal="testserver/testserver.example.com";<br />};<br /></pre></div>Note that the service principal in your config file should be set to your service principal and domain so that it works with your KDC. If you are an experienced Kerberos user, you can toy around with the key-tab and ticket-cache settings. I prefer to always talk to the KDC for authentication, instead of relying on caching - especially in a real network where user's passwords are changing every couple of months.<br /><br />You can obviously keep the client and service configs in separate files on separate servers. When testing stuff on my laptop I usually keep them in the same file for expedience, as the example file above illustrates.<br /><br />Setting <span style="font-style:italic;">debug</span> to true causes the JAAS libraries to print copious amounts of debug information when you are authenticating. This is <span style="font-weight:bold;">EXTREMELY</span> useful in getting things to work initially.<br /><br />In your code, you will also need to set some system properties when your client starts up. The code should be similar to the following:<br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><br /><FONT ID="System">System</FONT>.setProperty( <FONT ID="StringLiteral">"sun.security.krb5.debug"</FONT>, <FONT ID="StringLiteral">"true"</FONT>);<br /><FONT ID="System">System</FONT>.setProperty( <FONT ID="StringLiteral">"java.security.krb5.realm"</FONT>, <FONT ID="StringLiteral">"EXAMPLE.COM"</FONT>);<br /><FONT ID="System">System</FONT>.setProperty( <FONT ID="StringLiteral">"java.security.krb5.kdc"</FONT>, <FONT ID="StringLiteral">"localhost"</FONT>);<br /><FONT ID="System">System</FONT>.setProperty( <FONT ID="StringLiteral">"java.security.auth.login.config"</FONT>, <FONT ID="StringLiteral">"C:/jaas.conf"</FONT>);<br /><FONT ID="System">System</FONT>.setProperty( <FONT ID="StringLiteral">"javax.security.auth.useSubjectCredsOnly"</FONT>, useCreds);</pre></div>Obviously, you will need to put your realm, your KDC hostname and the path to your JAAS configuration file into these properties.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Rampart Client Configuration</span><br /><br />If you are using the old-style <span style="font-style:italic;">axis2.xml</span> config file to configure your rampart security, configure your <span style="font-style:italic;">OutFlowSecurity</span> as follows:<br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes"><br /><parameter name="OutflowSecurity"><br /> <action><br /> <!-- Timestamp header, message signature, and kerberos security token --><br /> <items>Timestamp <span style="color:blue">KerberosTokenSigned</span></items><br /> <br /> <!-- The kerberos client principal credentials --><br /> <!-- Apache Directory account --><br /> <user>bloggsj</user><br /> <!-- Your callback handler goes here --><br /> <passwordCallbackClass>my.client.CredentialsHandler</passwordCallbackClass><br /> <passwordType>PasswordText</passwordType><br /><br /> <!-- The parts of the message to sign --><br /> <signatureParts><br /> {Element}{http://docs.oasis-open.org/wss/2004/01/<br /> oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp;<br /> {Element}{http://schemas.xmlsoap.org/soap/envelope/}Body;<br /> {Element}{http://schemas.xmlsoap.org/ws/2004/08/addressing}To;<br /> {Element}{http://schemas.xmlsoap.org/ws/2004/08/addressing}Action;<br /> {Element}{http://schemas.xmlsoap.org/ws/2004/08/addressing}MessageID;<br /> {Element}{http://schemas.xmlsoap.org/ws/2004/08/addressing}ReplyTo<br /> </signatureParts><br /> <!-- Defines how the key used for signing is identified in the message --><br /> <signatureKeyIdentifier><br /> DirectReference<br /> </signatureKeyIdentifier><br /><br /> <!-- Identifies the kerberos service principal the session is with --><br /> <span style="color:blue"><KerberosServicePrinicpal><br /> <!-- Apache Directory account --><br /> testserver/testserver.example.com<br /> </KerberosServicePrincipal></span><br /> </action><br /></parameter><br /></pre></div>The only notes are that you MUST use <span style="font-style:italic;"><span style="font-weight:bold;">DirectReference</span></span> as your key identifier (for interoperability reasons) and your client and service principals must work with your chosen KDC/domain controller. Also, you will need to point it to your own password/credentials callback handler, as is standard with Rampart configurations. (i.e. don't use the <span style="font-style:italic;">my.client.CredentialsHandler</span> handler above because it doesn't exist!).<br /><br />You can choose to sign whatever parts of the message you want to - the key is that you sign at least one part, preferably the body of the message.<br /><br />When the response comes back, it will also be signed by the web service. Therefore, an <span style="font-style:italic;">InflowSecurity</span> handler must also be setup to verify the signatures. The following is the matching <span style="font-style:italic;">InflowSecurity</span> handler that achieves this:<br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes"><br /><parameter name="InflowSecurity"><br /> <action><br /> <items>Timestamp <span style="color:blue">KerberosTokenSigned</span></items><br /> <!-- Verify the signatures from the server --><br /> <enableSignatureConfirmation>true</enableSignatureConfirmation><br /> <!-- Your callback handler goes here --><br /> <passwordCallbackClass>my.client.CredentialsHandler</passwordCallbackClass><br /> </action><br /></parameter><br /></pre></div>This is much simpler, due to the fact that inflows only need to verify signatures rather than create them. Additionally, the kerberos principals and session details are already known from the request that was sent out.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Code Please?</span><br /><br />Ok, so most people don't like configuring Axis 2 clients from config files - I know because I especially hate it myself. When logging into an application, having the username in a config is just plain old annoying! Due to the need to have loads of different users with Kerberos accounts using my services, which themselves contain axis 2 clients for other services, I always have to configure the security settings via code. To configure the client-side using code in <span style="font-style:italic;">Axis 2/Rampart 1.3</span>, use the following (which is analogus to the previous two config files):<br /><br /><span style="font-weight:bold;">Note:</span> <span style="font-style:italic;">serviceClient</span> is your standard Axis 2 service client or RPC service client.<br /><br /><br /><div id="codebody" style="display:block"><pre id="codebody-show" class="Classes"><br /><FONT ID="SingleLineComment">// Use this method to configure the inflow and outflow security for this client.</FONT><br /><FONT ID="Private">private</FONT> void configureSecurity( String username, ServiceClient serviceClient,<br /> String servicePrincipal) {<br /> Options options = serviceClient.getOptions();<br /> options.setProperty( WSSHandlerConstants.OUTFLOW_SECURITY,<br /> buildOutflowConfiguration( username, servicePrincipal));<br /> options.setProperty( WSSHandlerConstants.INFLOW_SECURITY,<br /> buildInflowConfiguration( username, servicePrincipal));<br />}<br /><br/><br /><FONT ID="SingleLineComment">// Build the outflow security configuration parameter.</FONT><br /><FONT ID="Private">private</FONT> Parameter buildOutflowConfiguration( String username, String servicePrincipal) {<br /> OutflowConfiguration ofc = <FONT ID="New">new</FONT> OutflowConfiguration();<br /> ofc.setUser( username);<br /> ofc.setPasswordType( <FONT ID="StringLiteral">"PasswordText"</FONT>);<br /> ofc.setPasswordCallbackClass( <FONT ID="StringLiteral">"my.client.CredentialsHandler"</FONT>);<br /> ofc.setActionItems( <FONT ID="StringLiteral">"Timestamp KerberosTokenSigned"</FONT>);<br /> ofc.setEnableSignatureConfirmation( true);<br /> ofc.setSignatureParts(<br /> <FONT ID="StringLiteral">"{Element}{http://docs.oasis-open.org/wss/2004/01"</FONT> +<br /> <FONT ID="StringLiteral">"/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp;"</FONT> +<br /> <FONT ID="StringLiteral">"{Element}{http://schemas.xmlsoap.org/soap/envelope/}Body;"</FONT> +<br /> <FONT ID="StringLiteral">"{Element}{http://schemas.xmlsoap.org/ws/2004/08/addressing}To;"</FONT> +<br /> <FONT ID="StringLiteral">"{Element}{http://schemas.xmlsoap.org/ws/2004/08/addressing}Action;"</FONT> +<br /> <FONT ID="StringLiteral">"{Element}{http://schemas.xmlsoap.org/ws/2004/08/addressing}MessageID;"</FONT> +<br /> <FONT ID="StringLiteral">"{Element}{http://schemas.xmlsoap.org/ws/2004/08/addressing}ReplyTo"</FONT>);<br /> <B>ofc.setKerberosServicePrincipal( servicePrincipal);</B><br /> ofc.setSignatureKeyIdentifier( <FONT ID="StringLiteral">"DirectReference"</FONT>);<br /> <FONT ID="Return">return</FONT> ofc.getProperty();<br />}<br /><br/><br /><FONT ID="SingleLineComment">// Build the inflow security configuration parameter.</FONT><br /><FONT ID="Private">private</FONT> Parameter buildInflowConfiguration( String username, String servicePrincipal) {<br /> InflowConfiguration config = <FONT ID="New">new</FONT> InflowConfiguration();<br /> config.setPasswordCallbackClass( <FONT ID="StringLiteral">"my.client.CredentialsHandler"</FONT>);<br /> config.setActionItems( <FONT ID="StringLiteral">"Timestamp KerberosTokenSigned"</FONT>);<br /> config.setEnableSignatureConfirmation( true);<br /> <FONT ID="Return">return</FONT> config.getProperty();<br />}<br /></pre></div>Again, you will need to configure this to point to your own password/credentials callback handler, instead of the <span style="font-style:italic;">my.client.CredentialsHandler</span> class in my example.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Turning to the Server Side of the Force</span><br /><br />To configure the server, it is an extremely similar process. A <span style="font-style:italic;">jaas.conf</span> file is needed as per the client. The server side Kerberos security is configured via the <span style="font-style:italic;">services.xml</span> file for the web service. Here is an example of the inflow/outflow security settings required:<br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes"><br /><parameter name="InflowSecurity"><br /> <action><br /> <items>Timestamp <span style="color:blue">KerberosTokenSigned</span></items><br /> <user>testserver/testserver.example.com</user><br /> <span style="color:blue"><kerberosPropFile>C:/server-kerberos.properties</kerberosPropFile></span><br /> <passwordCallbackClass>wss4j.service.PWCBHandler</passwordCallbackClass><br /> <signatureKeyIdentifier>DirectReference</signatureKeyIdentifier><br /> <signatureParts>{Element}{http://docs.oasis-open.org/wss/2004/01/<br /> oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp;<br /> {Element}{http://schemas.xmlsoap.org/soap/envelope/}Body;<br /> {Element}{http://schemas.xmlsoap.org/ws/2004/08/addressing}To;<br /> {Element}{http://schemas.xmlsoap.org/ws/2004/08/addressing}Action;<br /> {Element}{http://schemas.xmlsoap.org/ws/2004/08/addressing}MessageID;<br /> {Element}{http://schemas.xmlsoap.org/ws/2004/08/addressing}ReplyTo<br /> </signatureParts><br /> </action><br /></parameter><br /><br /><parameter name="OutflowSecurity"><br /> <action><br /> <items>Timestamp <span style="color:blue">KerberosTokenSigned</span></items><br /> <user>testserver/testserver.example.com</user><br /> <passwordCallbackClass>samples.sample04.PWCBHandler</passwordCallbackClass><br /> <span style="color:blue"><kerberosPropFile>C:/server-kerberos.properties</kerberosPropFile></span><br /> <signatureKeyIdentifier><span style="color:blue">Thumbprint</span></signatureKeyIdentifier><br /> <signatureParts>{Element}{http://docs.oasis-open.org/wss/2004/01/<br /> oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp;<br /> {Element}{http://schemas.xmlsoap.org/soap/envelope/}Body;<br /> {Element}{http://schemas.xmlsoap.org/ws/2004/08/addressing}RelatesTo;<br /> {Element}{http://schemas.xmlsoap.org/ws/2004/08/addressing}MessageID;<br /> {Element}{http://schemas.xmlsoap.org/ws/2004/08/addressing}To;<br /> {Element}{http://schemas.xmlsoap.org/ws/2004/08/addressing}Action<br /> </signatureParts><br /> </action><br /></parameter><br /></pre></div>The important parts above are that the signature key reference on the input is <span style="font-style:italic;">DirectReference</span> and the output signature key reference is <span style="font-style:italic;">Thumbprint</span>. More specifically, the WS-Security spec requires the thumbprint to be the SHA-1 hash of the shared session key.<br /><br />The <span style="font-style:italic;">kerberosPropFile</span> element is a file that contains the Java Kerberos system properties for the server side. The best place for these were either in <span style="font-style:italic;">services.xml</span> or external. I kept them external as it made the <span style="font-style:italic;">services.xml</span> overly complicated. An example of such a file is:<br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes"><br />sun.security.krb5.debug=true<br />java.security.krb5.realm=EXAMPLE.COM<br />java.security.krb5.kdc=localhost<br />java.security.auth.login.config=C:/jaas.conf<br />javax.security.auth.useSubjectCredsOnly=true</pre></div>Note that this file in turn points to the <span style="font-style:italic;">JAAS</span> config file mentioned above. This indirection is kind've nasty, but it works at the moment, and is forced upon us by how JAAS works. <br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Thats all folks!</span><br /><br />Ok, so thats how to configure Axis 2 1.3/Rampart 1.3 to use Kerberos authentication, so it can play nicely with other Kerberos enabled web services. The final step is to download the <span style="color:white"><span style="font-weight:bold;">rampart-core</span></span>, <span style="color:white"><span style="font-weight:bold;">wss4j</span></span> and <span style="color:white"><span style="font-weight:bold;">kerberos-fix</span></span> jar files below and replace the matching jars in your client and service with them. <br /><br />Once you run it, you will either get a whole heap of weird errors due to not setting your Kerberos configs up correctly, or not configuring your KDC correctly, or you will see a whole load of extra stuff put into your SOAP messages such as <span style="font-style:italic;">BinarySecurityTokens</span>. Here is a snippet of a SOAP request that has Kerberos added to it (click to view full size):<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpjNgDgh4qXupPz3iZk_huLEXtJdhQEJ-KqDdX74QQePf2CPKV9-Avk1u8F0WXaBB-Bg0xYyvEwQ2K2qxPt4dyM3sinCYbdxnuSSymha7IeQ78gi-e2IEYBdSwBj552NuqMi_Byot6VNeJ/s1600-h/SOAP-kerb.PNG"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpjNgDgh4qXupPz3iZk_huLEXtJdhQEJ-KqDdX74QQePf2CPKV9-Avk1u8F0WXaBB-Bg0xYyvEwQ2K2qxPt4dyM3sinCYbdxnuSSymha7IeQ78gi-e2IEYBdSwBj552NuqMi_Byot6VNeJ/s320/SOAP-kerb.PNG" border="0" alt=""id="BLOGGER_PHOTO_ID_5253107145865237170" /></a><br /><br /><table class="codelink"><tbody><tr><td><a href="http://antsbull.googlepages.com/wss4j-1.5.3.jar"><img class="codeimage" src="http://antsbull.googlepages.com/disks.gif" /> Download the wss4j-1.5.3.jar file here.</a></td></tr></tbody></table><br /><br /><table class="codelink"><tbody><tr><td><a href="http://antsbull.googlepages.com/rampart-core-1.3.jar"><img class="codeimage" src="http://antsbull.googlepages.com/disks.gif" /> Download the rampart-core-1.3.jar file here.</a></td></tr></tbody></table><br /><br /><table class="codelink"><tbody><tr><td><a href="http://antsbull.googlepages.com/kerberos-fix.jar"><img class="codeimage" src="http://antsbull.googlepages.com/disks.gif" /> Download the kerberos-fix.jar file here.</a></td></tr></tbody></table><br /><br />Note that the <span style="color:white"><span style="font-weight:bold;">kerberos-fix</span></span> jar contains a tweaked version of the Java Kerberos libraries so that the web service can pull the Kerberos session key out of the GSS Context. In the next release of this stuff, I'll be using my own Kerberos ticket decoding algorithm to achieve this (<a href="http://thejavamonkey.blogspot.com/2008/05/how-to-decrypt-kerberos-gss-ap-req.html">see my previous blog for more info</a>).<br /><br />And finally, I am happy to help people through their errors/problems - as the intent of this release is to get it tested and get feedback on it, with the aim of publishing the code back to Apache for integration back into Axis 2/wss4j. Enjoy!<br /><br />PS: I also have an Axis 2 client and web service that can be used for this Kerberos stuff if anyone wants. Let me know and I'll put up a blog about it.The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com32tag:blogger.com,1999:blog-5374152728206138749.post-42313710991197413372008-09-06T12:56:00.006+12:002008-09-06T13:45:11.544+12:00Apache Synapse - How to run without wrapper.exe!I have been using the excellent <a href="http://synapse.apache.org/">Apache Synapse</a> lightweight Enterprise Service Bus over the last year or so, including on a live, production project. I recently started on a new project that requires Synapse, and used it as an opportunity to switch to the new production release, version 1.2.<br /><br />To my dismay, I discovered that to run the basic server on a Windows PC, you are now forced to use the <a href="http://wrapper.tanukisoftware.org">Java Service Wrapper</a> program, which runs Java programs as Windows services. There are two major problems with this requirement however:<br /><br /><ol><li>Wrapper.exe has some nasty bugs, and wouldn't work on any PC I tried it on.</li><li>You shouldn't be forced to run Synapse as a Windows service when all you want is a simple instance to test your configuration files and mediators against during development.</li></ol><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Dr Watson!</span><br /><br />The actual error we got on every Windows XP machine we tried Synapse 1.2 on is:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_39zRFwoX9RQeIz6v_dFZ50-rgGw962M52FfSu4DukMR4DlhPXQirP4pga0Y1QXNQ-ccP2UkjxdUWDYu066TeazSFqjklCSTYeM0G_0b47k-TIbQsQBLpIfovlOkqZS09wz4vFSC5amvG/s1600-h/synapse-wrapper.PNG"><img style="cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_39zRFwoX9RQeIz6v_dFZ50-rgGw962M52FfSu4DukMR4DlhPXQirP4pga0Y1QXNQ-ccP2UkjxdUWDYu066TeazSFqjklCSTYeM0G_0b47k-TIbQsQBLpIfovlOkqZS09wz4vFSC5amvG/s400/synapse-wrapper.PNG" border="0" alt=""id="BLOGGER_PHOTO_ID_5242711733384077410" /></a><br /><br /><span style="font-weight:bold;">wrapper-windows-x86-32.exe has encountered a problem and needs to close.</span><br /><br />Previously, Synapse was run using a <span style="font-style:italic;">.bat</span> file. I grabbed this from a development snapshot of version 1.0 I still had lying around and modified it slightly so that it would run with the command-line parameter format of the newer version of Synapse (I had to look at the source code to work this format out).<br /><br />To run Synapse 1.2 on Windows using this .bat file (see below), you need to define the following environment variables:<br /><br /><ul><li><span style="font-weight:bold;">SYNAPSE_HOME</span></li><li><span style="font-weight:bold;">SYNAPSE_XML</span></li></ul><br />For example:<br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes"><br />set SYNAPSE_HOME=C:\Java\synapse-1.2<br />set SYNAPSE_XML=C:\Java\synapse-1.2\synapse-config.xml<br /></pre></div>And, then simply put the <span style="font-style:italic;">run-synapse.bat</span> file below into the bin folder of your Synapse install, and you're free from the wrapper-hell. Note that this batch file has port 8080 set as the port to run Synapse on.<br /><br /><table class="codelink"><tbody><tr><td><a href="http://antsbull.googlepages.com/run-synapse.bat"><img class="codeimage" src="http://antsbull.googlepages.com/disks.gif" /> Download the run-synapse.bat file here.</a></td></tr></tbody></table>The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com3tag:blogger.com,1999:blog-5374152728206138749.post-84233794589301364572008-08-17T12:01:00.007+12:002008-09-06T13:37:20.727+12:00Architecture 101 - Java Developer Certification - The ServerThis is the first in a series of articles around the Java Developer Certification course. The plan is to do a new blog each month around this topic, so stay tuned!<br /><br />At work, they have been pushing me to get the Java Developer Certification for a few years now. I've actually booked my project and almost completed it, but it has taken me several years to do so. It goes something like:<br /><br /><ol><li>Get fired up and work on it for a few hours.</li><li>Wait 6-9 months.</li><li>Go back to step 1 and repeat.</li></ol><br />There is something that is extremely boring and unfulfilling about being at work 40+ hours a week, working on my own side projects for 20 hours a week and then on top of that having to write a University style room booking client/server application in a technology I absolutely despise! (there are many limitations to RMI that I do not like).<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMR0cdhNnFO7gG1fTRHLR6ZedCG7yB1WT8LBPzkb3CfeR2hAnB9EubLbP4tNkjknSg5pODcbNgFWU0BFkQ_XyhrLAw3WIABWho6aBi716jmYlrY2hXBgS0rl_kNcE21QtF0WEaHS1lEHXU/s1600-h/sq1.gif"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMR0cdhNnFO7gG1fTRHLR6ZedCG7yB1WT8LBPzkb3CfeR2hAnB9EubLbP4tNkjknSg5pODcbNgFWU0BFkQ_XyhrLAw3WIABWho6aBi716jmYlrY2hXBgS0rl_kNcE21QtF0WEaHS1lEHXU/s320/sq1.gif" border="0" alt=""id="BLOGGER_PHOTO_ID_5242715490696036386" /></a>Anyway, in a move of desperation I have decided to discuss architectures related to the developer certification, which will force me to get cracking (hopefully).<br /><br />So, over the next few weeks I've planned a series of blogs about the various parts of the certification project. Note that I'm not going to post any of my code and the purpose of these blogs is for purely a reasoning/discussion view point. Hopefully this may help some other developers to come up with a good plan for their developer certification project. <br /><br />Personally, I'm hoping that it forces me to finish off the remaining bits in my project.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Project X</span><br /><br />The developer certification is all about showing you are a capable developer. That is, can you write clean, maintainable code, and more importantly, can you take some requirements for a system and come up with a clean, pragmatic design, then implement that. The key point about the design for this project is that it has to be simple - so that a junior programmer could jump in and understand it and maintain it if necessary. An important requirement for such a system is to keep the number of classes low - that is, make your design efficient - which isn't a bad thing. A problem I have with developers who get a bit of experience is that they start going down a path that I call "Design Pattern Overload" - with the end result often becoming a disaster, where everytime you have to do something with the code, you have to spend 10 minutes trawling through the classes trying to remember exactly how they work together. This is more commonly known as <a href="#" onmouseover="monkeyTip('The Simpleton Pattern::The Simpleton Pattern is an extremely complex pattern used for the most trivial of tasks.<br>The Simpleton is an accurate indicator of the skill level of its creator.');" onmouseout="UnTip()">The Simpleton Pattern</a>.<br /><br />You have your project assignment - so where to start? The first choice that needs to be made is RMI or sockets? This should be a no-brainer - unless you have to write socket communication layers as part of your job, you should choose RMI. All the other more important stuff in the project should have your attention rather than worrying about also implementing a socket layer with its own messaging protocol.<br /><br />The next step, as it should be in any software project, is to map out the architecture of your project. In todays blog, I'm going to focus on the networked/stand-alone client design.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Are you In or Out?</span><br /><br />The biggest design point for the developer certification is the requirement to run the server networked, or internally in a fat-client mode. This part should be simple, but from reading sites like Java Ranch, it seems that people have a lot of trouble with it.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">The Adapter Pattern</span><br /><br />The <a href="http://en.wikipedia.org/wiki/Adaptor_pattern" onmouseover="monkeyTipImage('http://antsbull.googlepages.com/adaptor-pattern.png',546,348);" onmouseout="UnTip()">adapter design pattern translates the interface of a class to another interface</a>, so that it can be used by other objects which are incompatible with the original interface. That is, the adapter takes the adaptee and allows another object to use it.<br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes"><b>Requirement:</b> The program must be able to work in a non-networked mode.<br>In this mode, the database and GUI must run in the same VM and must perform no networking,<br>must not use loopback networking, and must not involve the serialization of any objects<br> when communicating between the GUI and database elements.</pre></div><span style="font-style:italic;">So what does the adapter pattern have to do with an application that has to be both RMI/non-networked? <br /></span><br />The GUI needs to access the business layer. It has to do so in both non-networked form and over an RMI connection, with transparency. That is, the GUI layer should not need to worry about whether or not the business layer it is talking to is remote or local. To achieve this, the GUI layer needs to talk to a service interface. <br /><br />There will be two implementations of this interface, each which will adapt the business model layer for use with the client. The client is expecting an interface to the business model - the local, non-networked adaptor is simply a wrapper for the business model, whereas the remote adaptor is a remote, RMI object. As far as the client knows, it is simply talking to an implementation of an interface, and does not need to worry about whether that instance is local or running on a remote server. This completely decouples the user interface from the networking code which is a good thing. It means in the future the system could be expanded to have a third adapter which implements everything as a SOAP web service.<br /><br />The following class diagram illustrates the local and remote adapters.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://antsbull.googlepages.com/nw-adaptor.png"><img style="cursor:pointer; cursor:hand;" src="http://antsbull.googlepages.com/nw-adaptor.png" border="0" alt="" /></a><br /><br />A key point in keeping your design simple is that your service interface will need to throw <span style="font-style:italic;">Exception</span> so that it covers the need for remote methods to declare that they throw <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/rmi/RemoteException.html" onmouseover="monkeyTip('Remote Exception::A Remote Exception is the common superclass for a number of communication-related exceptions that may occur during the <br /> execution of a remote method call. Each method of a remote interface, an interface that extends java.rmi.Remote, must<br /> list RemoteException in its throws clause.<br />');" onmouseout="UnTip()">Remote Exception</a><br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Lets Get Cracking!</span><br /><br />Ok, so thats the first part of your project underway - a nice, basic architecture to work with. My next topic in this series is going to cover the persistence layer code (including the notorious row locking topic!). Have fun.The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com12tag:blogger.com,1999:blog-5374152728206138749.post-33750421618309042642008-08-16T13:08:00.023+12:002008-09-06T13:39:31.570+12:00Modding Axis 2 - Durable Subscriptions for Asynchronous Messaging<br/><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjROZoNlXr2z-lenwLVVal-KcPFO0OHMANXzCCy6RfVRVk-ZScYFiBATn4LVq9Eof-IlTN0ezewKAiY2SnxbTJDDhyphenhyphen1oSJU0KVEi0M81RCNfsQbemlumQ-LVgSJfyjGNv0WjM3O4GY2csrv/s1600-h/kq6-beach,gif.gif"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjROZoNlXr2z-lenwLVVal-KcPFO0OHMANXzCCy6RfVRVk-ZScYFiBATn4LVq9Eof-IlTN0ezewKAiY2SnxbTJDDhyphenhyphen1oSJU0KVEi0M81RCNfsQbemlumQ-LVgSJfyjGNv0WjM3O4GY2csrv/s400/kq6-beach,gif.gif" border="0" alt=""id="BLOGGER_PHOTO_ID_5234933389440371106" /></a><br />Axis 2 is an enterprise solution for an enterprise problem - <a href="http://en.wikipedia.org/wiki/Service-oriented_architecture" onmouseover="monkeyTip('Service Oriented Architecture::Service-oriented architecture (SOA) is a methodology for systems development and integration where functionality is grouped around business<br />processes and packaged as interoperable services. SOA also describes IT infrastructure which allows different applications to exchange data <br />with one another as they participate in business processes. The aim is a loose coupling of services with operating systems, programming<br />languages and other technologies which underlie applications.<br />');" onmouseout="UnTip()">Service Oriented Architectures</a>. When running Axis 2 over the standard HTTP/S transports it is excellent. However, when running it over JMS, it has a major shortcoming (as of Axis 2 1.3).<br /><br />The key feature of a system based around messaging is that messages must be 100% guaranteed to reach their destination. In the SOA world, this is handled via the <span style="font-style:italic;"><a href="http://www.enterpriseintegrationpatterns.com/DurableSubscription.html" onmouseover="monkeyTip('The Durable Subscriber Pattern::A durable subscription saves messages for an inactive subscriber and delivers these saved messages when the subscriber reconnects.<br /><br />In this way, a subscriber will not lose any messages even though it is disconnected. A durable subscription has no effect<br />on the behavior\n of the subscriber or the messaging system while the subscriber is active (e.g., connected). <br /><br />A connected subscriber acts the same whether its subscription is durable or non-durable. The difference is in how the messaging <br />system behaves when the subscriber is disconnected.<br /><br />');" onmouseout="UnTip()">Durable Subscriber</a></span> enterprise integration pattern. A durable subscription instructs the messaging server to save messages, that are published while a subscriber is inactive. When the subscriber reconnects to the messaging server, the subscriber receives all the messages that it missed while it was inactive. This is a standard JMS feature which is one of the key patterns in enterprise integration systems.<br /><br />Axis 2 1.3, does not support this, so I had to do some custom modifications to the Axis 2 transport layer so that I could make use of durable subscriptions. This blog is about how to configure your Axis 2 to support durable subscriptions, and contains a downloadable jar that contains the modified JMS transport layer for Axis 2 1.3.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Don't Shoot the Messenger!</span><br /><br /><span style="color:white;font-weight:bold;">axis2.xml</span><br /><br />I have modified Axis 2 so that the configuration fits in with the standard JMS transport configuration. The following example <span style="font-style:italic;">JMS transport receiver</span> highlights the two new transport receiver configuration parameters. <span style="font-style:italic;">axis2.xml</span> is generally located in the <span style="font-style:italic;">WEB-INF/conf</span> folder of your Axis 2 server.<br /><br /><ol><li><b>UseDurableSubscriptions</b> - this speaks for itself.</li><li><b>DurableSubscriptionClientIdentifier</b> - this is a unique identifier that the messaging server uses to identify your specific application server instance (for example, your dev tomcat).</li></ol><br />Note that the example transport receiver below is configured to work with Web Sphere MQ, so the JNDI parameters should be changed depending on the messaging platform you are using.<br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes"><br /><transportReceiver name="jms" class="org.apache.axis2.transport.jms.JMSListener"><br /> <parameter name="defaultCF"><br /> <parameter name="java.naming.factory.initial"><br /> com.ibm.mq.jms.context.WMQInitialContextFactory<br /> </parameter><br /> <parameter name="java.naming.provider.url"><br /> MQSERVER:1421/MY.CHANNEL.SVRCONN</parameter><br /> <parameter name="transport.jms.ConnectionFactoryJNDIName"><br /> topicCF<br /> </parameter><div style="color:blue;font-weight:bold"><br /> <parameter name="transport.jms.UseDurableSubscriptions"><br /> yes<br /> </parameter><br /> <parameter name="transport.jms.DurableSubscriptionClientIdentifier"><br /> myservice.prod.tomcat<br /> </parameter></div><br /> </parameter><br /></transportReceiver><br /></pre></div><br /><span style="color:white;font-weight:bold;">services.xml</span><br /><br />The final part of your configuration, is to configure your individual web services to be durable. My implementation is such, that you can have multiple web services and can define per web service, whether it is durable, or runs over HTTP or JMS (or both) or any combination.<br /><br />The only parameter required for your service definition is, <span style="font-weight:bold;">DurableSubscriptionName</span> - this parameter identifies your specific web service uniquely to the messaging server so that when your web service connects to it, it knows what messages you have missed. As soon as you deploy your web service, you will see it start to pull all the messages off MQ that it missed!<br /><br />If your JMS-enabled web service does not require to be durable, then simply leave this parameter out and it will resort to being a plain-old Axis 2 non-durable JMS service. Any of your web services running over standard message queues should be configured this non-durable way, as durability is only for publish-subscribe.<br /><br />Note that the following <span style="font-style:italic;">services.xml</span> excerpt is for a web service that is available over both HTTP and JMS and is subscribed to the topic <span style="font-style:italic;">MQ.BOOKING.TOPIC</span>.<br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes"><br /><transports><br /> <transport>jms</transport><br /> <transport>http</transport><br /></transports><br /><br /><parameter name="transport.jms.ConnectionFactory" locked="true"><br /> defaultCF<br /></parameter><br /><parameter name="transport.jms.Destination" locked="true"><br /> MQ.BOOKING.TOPIC<br /></parameter><div style="color:blue;font-weight:bold"><br /><parameter name="transport.jms.DurableSubscriptionName" locked="true"><br /> BOOKING.SERVICE.PROD<br /></parameter></div><br /></pre></div><br />Note that I have not modified Axis 2 to have the ability to de-register a web service as a durable subscriber. Also note that durable subscriptions only work for publish-subscribe/asynchronous messaging - it does not work for point-point message queues and never will!<br /><br />To anyone who is interested, I am also working on a major update to the JMS transport for Axis 2 so that it can heal its connections to messaging servers without having to restart the Axis 2 server.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Distribute Me</span><br /><br />The jar file attached to this blog (see below) contains the JMS transport classes I fixed up for Axis 2 1.3 only. These either need to be put into the Axis 2 kernel jar so they take the place of the existing ones, or expanded into the WEB-INF/classes folder of your Axis 2, so they override the versions of these classes in the Axis 2 kernel jar file.<br /><br /><table class="codelink"><tbody><tr><td><a href="http://antsbull.googlepages.com/axis-2-durable-jms-1.3.jar"><img class="codeimage" src="http://antsbull.googlepages.com/disks.gif" /> Download the jar file here.</a></td></tr></tbody></table>The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com6tag:blogger.com,1999:blog-5374152728206138749.post-27417433616933533982008-08-16T12:41:00.007+12:002008-08-16T14:17:03.076+12:00Modding Axis 2 - JMS Transport/Web Sphere MQ resource leaking<br/><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAU4GzCou8eox0ur29v9kTViLet209Es078t1QZWucCZOh7GYYMt_6joi9sOUgamO0SD2WZoUe3DrlomTdQEQ8-zVf_gWrXaeBkc6mdCtHZ7RpuiDLDvHObTTs8AuIP1HpOahBB1hOrvH7/s1600-h/qg4-erana.gif"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAU4GzCou8eox0ur29v9kTViLet209Es078t1QZWucCZOh7GYYMt_6joi9sOUgamO0SD2WZoUe3DrlomTdQEQ8-zVf_gWrXaeBkc6mdCtHZ7RpuiDLDvHObTTs8AuIP1HpOahBB1hOrvH7/s400/qg4-erana.gif" border="0" alt=""id="BLOGGER_PHOTO_ID_5234933819145794994" /></a><br />I have spent a lot of time in the last year deploying Axis 2 web services that run over a JMS transport layer provided by <a href="http://en.wikipedia.org/wiki/MQSeries">Web Sphere MQ 6.0</a>. These services are all web services that consume message topics - that is, they use the <a href="http://en.wikipedia.org/wiki/Publish_subscribe">publish-subscribe messaging paradigm</a>, or <span style="font-style:italic;">asynchronous messaging</span>, for short.<br /><br />In addition to consumers, I also have to publish to message topics from web and RMI services, Swing applications, web apps and background services. Axis 2 supports this out of the box and everything works pretty well.<br /><br />Earlier this year, the extremely important project (a core system for a big client's business) I was working on rolled through acceptance testing and into production with no alarm. Then after a few days of running in production our Web Sphere MQ server stopped accepting connections! The .NET and Java software talking to it was completely locked out, and MQ needed a reboot to sort it out. Before bouncing MQ, I took a look at the connections list and saw that there were 200 connections from the same Axis 2 client, all started at various times over the preceding 3 days.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Guilty as charged, your honour!</span><br /><br />Anyway, to cut a long story short I debugged Axis 2 (1.3)'s JMS transport layer and found that when running over Web Sphere MQ it was not releasing connections to the MQ server when <span style="font-style:italic;">sending</span> messages. I ended up patching the JMS transport for Axis 2 1.3 and forcing it to clean up the JNDI contexts correctly after sending messages. Note that this may only be specific to Web Sphere MQ, and not for other JMS providers - as I have never run this stuff over anything else.<br /><br />The jar file attached to this blog (see below) contains the two JMS transport classes I fixed up for Axis 2 1.3 only. These either need to be put into the Axis 2 kernel jar so they take the place of the existing ones, or expanded into the <span style="font-style:italic;">classes</span> folder of your application that is sending Axis 2 messages over JMS, so they override the versions of these classes in the Axis 2 kernel jar file.<br /><br /><table class="codelink"><tbody><tr><td><a href="http://antsbull.googlepages.com/axis-2-client-jms-fix-1.3.jar"><img class="codeimage" src="http://antsbull.googlepages.com/disks.gif" /> Download the jar file here.</a></td></tr></tbody></table>The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com6tag:blogger.com,1999:blog-5374152728206138749.post-715116382185723422008-08-10T23:27:00.012+12:002008-08-16T13:06:03.528+12:00Using Tomcat / JKS Certificates in Apache HTTP Server<br/><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOYIQ0-qGijKLXDnfHXAxWFkmgyQE0hyphenhyphensTmLWJHwIoTNKKD587dhSA0AU2DmclvqifTFXr3s8LAK-q-K2QlNUIsW2197Z-uKSaaGMpWYQRlwh2OrPW4CD33fPPLHTOGNu2iMxq6HrmfCBH/s1600-h/kq5-2.png"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOYIQ0-qGijKLXDnfHXAxWFkmgyQE0hyphenhyphensTmLWJHwIoTNKKD587dhSA0AU2DmclvqifTFXr3s8LAK-q-K2QlNUIsW2197Z-uKSaaGMpWYQRlwh2OrPW4CD33fPPLHTOGNu2iMxq6HrmfCBH/s400/kq5-2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5233469281230948770" /></a>In a <a href="http://thejavamonkey.blogspot.com/2008/07/using-apache-httpd-server-as-secure.html">previous blog</a>, I detailed how to use Apache as a secure proxy balancer for multiple Tomcats using URL context re-direction. I was setting up a new machine in Australia recently where there was one secure Tomcat (port 443) that had to be split into three Tomcats seamlessly. <br /><br />This meant re-using the security certificate from the Tomcat, which as it turned out was a tricky thing to do. The issue is that the JKS (Java Key Store) that Tomcat uses, likes certificates in the DER format (see <a href="http://thejavamonkey.blogspot.com/2008/05/how-to-decrypt-kerberos-gss-ap-req.html">my previous blog on Kerberos packet decoding</a> for more about '<span style="font-weight:bold;">the DER</span>'). Conversely, Apache likes its certificates in the <a href="http://en.wikipedia.org/wiki/X.509#Certificate_filename_extensions">PEM format</a>. The PEM format is basically a base-64 encoded version of the raw certificate bytes.<br /><br />Additionally, you need to extract both the certificate AND the private key - Apache requires both for securing it. The certificate can be extracted with the JDK <span style="font-style:italic;">keytool.exe</span> program - however the private key can't be.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Certifiably Mad</span><br /><br />To extract your certificate from the Tomcat/JKS keystore, use the keytool.exe program that comes with your JDK distribution. Firstly, you need to know what the certificate is aliased under in the keystore. Note that you will need to know the password of your keystore in order to export from it. The default password is 'changeit'. Run the following command to list the certificates and their aliases (replace <span style="font-style:italic;">my-keystore.jks</span> with your keystore path):<br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes">keytool.exe -list -keystore my-keystore.jks<br><br>Keystore type: JKS<br><br />Keystore provider: SUN<br><br><br />Your keystore contains 4 entries<br><br><br />isskey, 12/05/2008, trustedCertEntry,<br><br />Certificate fingerprint (MD5): 7A:43:3A:50:02:F9:EC:09:1D:9F:E7:25:80:4F:4C:33<br><br />sceq, 10/06/2008, trustedCertEntry,<br><br />Certificate fingerprint (MD5): 7A:43:3A:50:02:F9:EC:09:1D:9F:E7:25:80:4F:4C:33<br><br />tomcat, 11/06/2008, trustedCertEntry,<br><br />Certificate fingerprint (MD5): 54:95:24:14:D8:73:77:9F:BF:65:D1:C8:A5:ED:B6:0D<br><br />mykey, 4/03/2002, PrivateKeyEntry,<br><br />Certificate fingerprint (MD5): 54:95:24:14:D8:73:77:9F:BF:65:D1:C8:A5:ED:B6:0D<br><br /></pre></div>You can see the list of certificates preceded by their aliases.<br /><br />To export the certificate, use the following command:<br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes">keytool.exe -export -alias tomcat -keystore my-keystore.jks > certificate.der</pre></div>This gives you a DER encoded certificate. Now, use <span style="font-style:italic;">OpenSSL.exe</span>, which was included with your Apache install, to convert this certificate to the PEM format as follows:<br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes">C:\Program Files\Apache Software Foundation\Apache2.2\bin>openssl x509 <br /> -in certificate.der -inform DER -out server.crt -outform PEM</pre></div>You can now copy this into your Apache installation, and point the <span style="font-style:italic;">conf/extra/httpd-ssl.conf</span> file to it.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">The Key is Private</span><br /><br />The tricky part, is extracting the private key from the JKS keystore. The keytool doesn't provide a means of doing this, so we have to roll up our sleeves and write some code to do it. Anyone who read my Kerberos ticket decoding articles will find this pretty simple and is commando-like (in and out with the minimum hassle): <span style="font-style:italic;">Open the keystore, grab the key, base 64 encode it and write it to disk.</span><br /><br />For these purposes, I have put together both an archive containing just the binary, and an archived Net Beans project containing all the source code (some people may need to re-use the code programmatically):<br /><br /><table id="codeblock"><tbody><tr><td><img src="http://antsbull.googlepages.com/expand.gif" class="codeexpander" onclick="showHideElement('codebodyjks1')" title="display / hide the file PrivateKeyExporter.java" /></td><td><h2>PrivateKeyExporter.java</h2></td></tr></tbody></table><br /><div id="codebody"><pre id="codebodyjks1" class="Classes"><A NAME="1"></A><FONT ID="Package">package</FONT> javamonkey.app.privatekey;<br /><A NAME="2"> </A><br /><A NAME="3"></A><FONT ID="Import">import</FONT> java.io.File;<br /><A NAME="4"></A><FONT ID="Import">import</FONT> java.io.FileInputStream;<br /><A NAME="5"></A><FONT ID="Import">import</FONT> java.io.FileNotFoundException;<br /><A NAME="6"></A><FONT ID="Import">import</FONT> java.io.FileOutputStream;<br /><A NAME="7"></A><FONT ID="Import">import</FONT> java.io.FileWriter;<br /><A NAME="8"></A><FONT ID="Import">import</FONT> java.security.Key;<br /><A NAME="9"></A><FONT ID="Import">import</FONT> java.security.KeyStore;<br /><A NAME="10"></A><FONT ID="Import">import</FONT> java.security.KeyStoreException;<br /><A NAME="11"></A><FONT ID="Import">import</FONT> sun.misc.BASE64Encoder;<br /><A NAME="12"> </A><br /><A NAME="13"></A><FONT ID="FormalComment">/**<br /><A NAME="14"></A> * <p>Private Key Exporter is a simple utility app that enables the exporting of<br /><A NAME="15"></A> * private keys from JKS keystores. The JDK keytool.exe utility does not present<br /><A NAME="16"></A> * this ability, which makes it difficult to load certificate/keys from JKS into<br /><A NAME="17"></A> * Apache HTTPD server.</p><br /><A NAME="18"></A> * <p>Note that this dumps in a base 64 encoded format compatible with OpenSSL.<br /><A NAME="19"></A> * </p><br /><A NAME="20"></A> *<br /><A NAME="21"></A> * @author Ants<br /><A NAME="22"></A> */</FONT><br /><A NAME="23"></A><FONT ID="Public">public</FONT> <FONT ID="Class">class</FONT> PrivateKeyExporter {<br /><A NAME="24"> </A><br /><A NAME="25"></A> <FONT ID="Public">public</FONT> PrivateKeyExporter( String keystorePath, String certAlias,<br /><A NAME="26"></A> String keystorePassword, String outputFile, <FONT ID="Boolean">boolean</FONT> outputToConsole) {<br /><A NAME="27"></A> <FONT ID="Super">super</FONT>();<br /><A NAME="28"></A> <FONT ID="This">this</FONT>.keystorePath = keystorePath;<br /><A NAME="29"></A> <FONT ID="This">this</FONT>.certAlias = certAlias;<br /><A NAME="30"></A> <FONT ID="This">this</FONT>.keystorePassword = keystorePassword;<br /><A NAME="31"></A> <FONT ID="This">this</FONT>.outputFile = outputFile;<br /><A NAME="32"></A> <FONT ID="This">this</FONT>.outputToConsole = outputToConsole;<br /><A NAME="33"></A> }<br /><A NAME="34"> </A><br /><A NAME="35"></A> <FONT ID="Private">private</FONT> <FONT ID="Final">final</FONT> String keystorePath;<br /><A NAME="36"></A> <FONT ID="Private">private</FONT> <FONT ID="Final">final</FONT> String certAlias;<br /><A NAME="37"></A> <FONT ID="Private">private</FONT> <FONT ID="Final">final</FONT> String keystorePassword;<br /><A NAME="38"></A> <FONT ID="Private">private</FONT> <FONT ID="Final">final</FONT> String outputFile;<br /><A NAME="39"></A> <FONT ID="Private">private</FONT> <FONT ID="Final">final</FONT> <FONT ID="Boolean">boolean</FONT> outputToConsole;<br /><A NAME="40"> </A><br /><A NAME="41"></A> <FONT ID="FormalComment">/**<br /><A NAME="42"></A> * Export a private key either to a file or to the console or both.<br /><A NAME="43"></A> * @throws java.security.KeyStoreException on any errors accessing the<br /><A NAME="44"></A> * keystore such as it not being a JKS keystore<br /><A NAME="45"></A> * @throws java.io.FileNotFoundException if the keystore doesn't exist<br /><A NAME="46"></A> * @throws java.lang.Exception on any other errors during the export<br /><A NAME="47"></A> */</FONT><br /><A NAME="48"></A> <FONT ID="Public">public</FONT> <FONT ID="Void">void</FONT> exportPrivateKey()<br /><A NAME="49"></A> <FONT ID="Throws">throws</FONT> KeyStoreException, FileNotFoundException, Exception {<br /><A NAME="50"></A> File keystoreFile = <FONT ID="New">new</FONT> File( keystorePath);<br /><A NAME="51"></A> <FONT ID="SingleLineComment">// The source keystore is a Java Key Store (JKS).<br /><A NAME="52"></A></FONT> KeyStore keystore = KeyStore.getInstance( <FONT ID="StringLiteral">"jks"</FONT>);<br /><A NAME="53"></A> keystore.load( <FONT ID="New">new</FONT> FileInputStream( keystoreFile),<br /><A NAME="54"></A> keystorePassword.toCharArray());<br /><A NAME="55"></A> <FONT ID="SingleLineComment">// Get the key.<br /><A NAME="56"></A></FONT> Key key = keystore.getKey( certAlias, keystorePassword.toCharArray());<br /><A NAME="57"></A> <FONT ID="SingleLineComment">// Convert to the PEM format.<br /><A NAME="58"></A></FONT> String encoded = <FONT ID="New">new</FONT> BASE64Encoder().encode( key.getEncoded());<br /><A NAME="59"></A> StringBuffer output = <FONT ID="New">new</FONT> StringBuffer();<br /><A NAME="60"></A> output.append( <FONT ID="StringLiteral">"-----BEGIN PRIVATE KEY-----\n"</FONT>);<br /><A NAME="61"></A> output.append( encoded);<br /><A NAME="62"></A> output.append( <FONT ID="StringLiteral">"\n-----END PRIVATE KEY-----\n"</FONT>);<br /><A NAME="63"></A> <FONT ID="If">if</FONT> ( outputToConsole) {<br /><A NAME="64"></A> <FONT ID="SingleLineComment">// Write to the console.<br /><A NAME="65"></A></FONT> System.out.println( output.toString());<br /><A NAME="66"></A> }<br /><A NAME="67"></A> <FONT ID="If">if</FONT> ( outputFile != <FONT ID="Null">null</FONT>) {<br /><A NAME="68"></A> <FONT ID="SingleLineComment">// Write to disk.<br /><A NAME="69"></A></FONT> FileWriter writer = <FONT ID="New">new</FONT> FileWriter( outputFile);<br /><A NAME="70"></A> writer.write( output.toString());<br /><A NAME="71"></A> writer.close();<br /><A NAME="72"></A> }<br /><A NAME="73"></A> }<br /><A NAME="74"> </A><br /><A NAME="75"></A> <FONT ID="Public">public</FONT> <FONT ID="Static">static</FONT> <FONT ID="Void">void</FONT> main( String[] args) {<br /><A NAME="76"></A> <FONT ID="If">if</FONT> ( args.length < <FONT ID="IntegerLiteral">3</FONT> || args.length > <FONT ID="IntegerLiteral">4</FONT>) {<br /><A NAME="77"></A> printCmdParameters();<br /><A NAME="78"></A> System.exit( <FONT ID="IntegerLiteral">1</FONT>);<br /><A NAME="79"></A> }<br /><A NAME="80"></A> <FONT ID="Try">try</FONT> {<br /><A NAME="81"></A> <A HREF="../../../javamonkey/app/privatekey/PrivateKeyExporter.java.html">PrivateKeyExporter</A> exporter =<br /><A NAME="82"></A> <FONT ID="New">new</FONT> <A HREF="../../../javamonkey/app/privatekey/PrivateKeyExporter.java.html">PrivateKeyExporter</A>( args[<FONT ID="IntegerLiteral">0</FONT>], args[<FONT ID="IntegerLiteral">1</FONT>], args[<FONT ID="IntegerLiteral">2</FONT>], args[<FONT ID="IntegerLiteral">3</FONT>], <FONT ID="True">true</FONT>);<br /><A NAME="83"></A> exporter.exportPrivateKey();<br /><A NAME="84"></A> }<br /><A NAME="85"></A> <FONT ID="Catch">catch</FONT> ( FileNotFoundException e) {<br /><A NAME="86"></A> System.out.println( <FONT ID="StringLiteral">"\nKeystore file ["</FONT> + args[<FONT ID="IntegerLiteral">0</FONT>] +<br /><A NAME="87"></A> <FONT ID="StringLiteral">" does not exist.\n"</FONT>);<br /><A NAME="88"></A> }<br /><A NAME="89"></A> <FONT ID="Catch">catch</FONT> ( KeyStoreException e) {<br /><A NAME="90"></A> System.out.println( <FONT ID="StringLiteral">"\nAn error occurred while accessing the keystore. "</FONT> +<br /><A NAME="91"></A> <FONT ID="StringLiteral">"Is this a valid JKS keystore?\n\n"</FONT> + e.getMessage() + <FONT ID="StringLiteral">"\n"</FONT>);<br /><A NAME="92"></A> }<br /><A NAME="93"></A> <FONT ID="Catch">catch</FONT> ( Exception e) {<br /><A NAME="94"></A> System.out.println( <FONT ID="StringLiteral">"\nAn unexpected error occurred while accessing "</FONT> +<br /><A NAME="95"></A> <FONT ID="StringLiteral">"the keystore. "</FONT> + e.getMessage() + <FONT ID="StringLiteral">"\n"</FONT>);<br /><A NAME="96"></A> e.printStackTrace();<br /><A NAME="97"></A> }<br /><A NAME="98"></A> }<br /><A NAME="99"> </A><br /><A NAME="100"></A> <FONT ID="Private">private</FONT> <FONT ID="Static">static</FONT> <FONT ID="Void">void</FONT> printCmdParameters() {<br /><A NAME="101"></A> System.out.println( <FONT ID="StringLiteral">"\n\nUsage: pkey-export.bat [keystore-file] "</FONT> +<br /><A NAME="102"></A> <FONT ID="StringLiteral">"[cert-alias] [password] [out-file]\n\nNote that [out-file] is "</FONT> +<br /><A NAME="103"></A> <FONT ID="StringLiteral">"optional.\n\n"</FONT>);<br /><A NAME="104"></A> }<br /><A NAME="105"></A>}<br /><A NAME="106"> </A></pre></div>A batch file, <span style="font-style:italic;">pkey-export.bat</span>, which when run takes a keystore, and grabs the private key for a given certificate alias, and can display the results in a PEM format to the console and/or an outputfile. Run the file with no parameters for more detailed parameter instructions.<br /><br />Once you have your key file, you need to point the <span style="font-style:italic;">conf/extra/httpd-ssl.conf</span> file to it in your Apache installation. Finally, all you need to do is to restart your Apache, and it will now be using your Tomcat security certificate!<br /><br /><table class="codelink"><tbody><tr><td><a href="http://antsbull.googlepages.com/export-private-key-nbeans.zip"><img class="codeimage" src="http://antsbull.googlepages.com/disks.gif" /> Download the NetBeans project and source/binaries here.</a></td></tr></tbody></table><br /><table class="codelink"><tbody><tr><td><a href="http://antsbull.googlepages.com/export-pkey.zip"><img class="codeimage" src="http://antsbull.googlepages.com/disks.gif" /> Download the binary-only archive here.</a></td></tr></tbody></table>The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com11tag:blogger.com,1999:blog-5374152728206138749.post-33892127048368301142008-07-28T06:42:00.011+12:002008-08-16T14:20:20.547+12:00Spring and JDBC Connection Pools - Verifying the Connection on Borrow<br/><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2c_XOsdRpQ5T3Lt3h8495YaklLay37fMPU2myCh06Ic-iS14_E881-zkvpgLBH3M_jbyhPdpMGG2kUzVr6mX80QykjMZaKhK-7YnEsGRklrWvybr0jX7RSycIYGHc2iu_fpg6kWmt5RE1/s1600-h/connection.png"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2c_XOsdRpQ5T3Lt3h8495YaklLay37fMPU2myCh06Ic-iS14_E881-zkvpgLBH3M_jbyhPdpMGG2kUzVr6mX80QykjMZaKhK-7YnEsGRklrWvybr0jX7RSycIYGHc2iu_fpg6kWmt5RE1/s400/connection.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5227773775401287266" /></a><br />I have been using the excellent Spring framework for a couple of years now. At work we pretty much exclusively use it on all projects, after I convinced my boss of the merits of it with regards to speeding up our development process.<br /><br />For the most, it has been a flawless experience. The only initial hurdle we had was trying to get transactions going - we initially tried to use programmatic transaction management as this is what developers were used to using with our existing frameworks. A quick rethink and bringing in standard architectures across our projects where transactionally annotated facades are used solved this.<br /><br />A client of ours has a pretty complicated production (and development) environment. There is something in there that decides to break network (database) connections regularly. This started playing havoc with our connection pools - Spring or otherwise. After running our servers for a day or so we would frequently start getting choice exceptions such as <span style="font-style:italic;">Socket pipe error</span> and <span style="font-style:italic;">Broken Connection</span> from the JDBC driver when attempting to execute SQL against the database.<br /><br />Some quick searching around in google was done and I found that it was possible to tell your connection pool to test the connection against the database everytime it is borrowed from the pool. If the connection is broken, it is dumped and a new one is initialised. This is all possible with only 3 lines of Spring XML (ah the beauty of Spring!).<br /><br />The only requirement for this is that there is either a table in your database with very few rows, or a function in the database server that you can call from SQL that is not computationally expensive. The reason for this is that we need a test SQL statement to actually fire at the database to check that the connection is working. Its best for this query to be as quick as possible, otherwise you end up ruining your application performance. Having a table in the database that only has a few rows is useful as you can simply do a <span style="font-style:italic;"><span style="font-weight:bold;">count(*)</span></span> on that table. If I'm using PostgreSQL, then I use the statement <span style="font-style:italic;">'SELECT NOW()'</span> which selects the current time from the database server. Most database servers probably have similar functions that can be called through SQL queries.<br /><br />The extra fields added to your data-source definition are <span style="font-style:italic;">validationQuery</span>, <span style="font-style:italic;">testOnBorrow</span> and <span style="font-style:italic;">testOnReturn</span>. Validation Query is where you put your innocuous, little test-query.<br /><br />The Spring context configuration is as follows:<br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes"><br /><bean id="propertyConfigurer"<br /> class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><br /> <property name="locations"><br /> <list><br /> <value>nz/co/toyota/webservices/jdbc.properties</value><br /> </list><br /> </property><br /></bean><br /><br/><br /><!-- Apache Commons DBCP DataSource that refers to a database. --><br /><!-- JDBC properties loaded via jdbc.properties. --><br /><br/><br /><bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" method="close"><br /> <property name="driverClassName" value="${jdbc.driverClassName}"><br /> <property name="url" value="${jdbc.url}"><br /> <property name="username" value="${jdbc.username}"><br /> <property name="password" value="${jdbc.password}"><br /> <b style="color:#11F"><property name="validationQuery" value="select now()"><br /> <property name="testOnBorrow" value="true"><br /> <property name="testOnReturn" value="false"></b><br /></property></div></pre>As you can probably tell, the connection can be tested on return if necessary also. I've never needed to do this, however.....The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com5tag:blogger.com,1999:blog-5374152728206138749.post-57263119021246986602008-07-27T13:39:00.018+12:002008-10-25T10:17:08.729+13:00Digital Signatures in Java - Public Key Cryptography and Software Licenses<br/><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaF0c1t2HhVsVbSIkK0LHEoAGSRSMEKqGtvC4bj-kt9jkCt3FMvUcDxFfOKD5Z57wCNsDXRhhgQqZmKA-gTqLHUax5kwVbJScM8HjUoGc6qgeApVkD_s3KMbONlKrHL7iiVZxxtK7wJ_p8/s1600-h/security.gif"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaF0c1t2HhVsVbSIkK0LHEoAGSRSMEKqGtvC4bj-kt9jkCt3FMvUcDxFfOKD5Z57wCNsDXRhhgQqZmKA-gTqLHUax5kwVbJScM8HjUoGc6qgeApVkD_s3KMbONlKrHL7iiVZxxtK7wJ_p8/s400/security.gif" border="0" alt=""id="BLOGGER_PHOTO_ID_5227623437112162466" /></a><br />Since I started this blog, it seems to have become focused around Java security topics. This is because my blogs are pretty much related to what I'm currently working on, and getting my thoughts and processes down in writing is extremely valuable. It helps me to not only understand what I'm doing better, it also gives me a written record and explanation of the work. I can guarantee, that unless I work on these things frequently, in a few months I've generally forgotten the details of how I actually did it. And then the next time I need to do the same stuff, I have to re-learn it! I can also guarantee that any developer out there who is required to use a lot of different Java technologies in their job will face the same scenario regularly =)<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Software Licensing - A Blast from the (monkey's) Past?</span><br /><br />A couple of years back, I had to implement a licensing system for some online software. The licensing system consisted of a license serial number and a digital signature of that license. When a user licensed the software for use, they did so by entering the serial number and its signature into the software, which would then verify the license and unlock the software with the set of features that were in the license.<br /><br />The license number itself was encrypted with a <a href="http://en.wikipedia.org/wiki/Symmetric_cryptography"><span style="font-weight:bold;">symmetric key</span></a>. A symmetric key is one that can be used to both encrypt and decrypt the same set of data. Symmetric keys are also known as shared keys, secret keys and as private key encryption. So the license number was encrypted - the first question that most people would ask is <span style="font-style:italic;">"Why do we need a digital signature when the license itself is encrypted?"</span>. The signature is used to verify that the identity that signed the license is in fact the identity that created the license. In much the same way that signing a VISA payment slip is a way of verifying a credit card account, this signature verifies that the license originated from the place it was meant to (i.e. the licensing system).<br /><br />The problem with just falling back on an encrypted license number, is that someone can disassemble your software and find out what the private encryption key is that is used to encrypt/decrypt the license number. From there, they can simply generate their own license numbers and use your software for free.<br /><br />The digital signature itself is generated and verified with an asymmetric key pair. Asymmetric key cryptography is more commonly known as <a href="http://en.wikipedia.org/wiki/Public-key_cryptography"><span style="font-weight:bold;">public key cryptography</span></a>. An identity, in this case, our licensing system, has a private key which only they know. Any recipient systems only know the public key that is paired with that private key. A message signed with a sender's private key can be verified by anyone who has access to the sender's public key, thereby proving that the sender signed it and that the message has not been tampered with.<br /><br />When combined together, the encrypted license and the signature of that license, provide an extremely secure way of licensing your software. This can also be used for signing client-server communications in your software. SSL secured communications with web applications also use public-key cryptography for securing the traffic to/from the web-browser and the web-server.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">License to Kill</span><br /><br />The license number for the system I was licensing was comprised from a whole bunch of the licensing properties. These were all calculated programmatically in Java code:<br /><br /><ul><li>External IP address of the system that was being licensed.</li><li>License expiry timestamp.</li><li>Number of users that the license was valid for.</li><li>The features in the system that the license would unlock.</li></ul><br /><br />Note that I am not covering how to generate these values, or how to encrypt/decrypt the license number here with symmetric keys, that can be saved for a later blog!<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Don't Bore Us, Get to the Code!</span><br /><br />The first step to this secure licensing nirvana, is to generate the public-private key pair for your licensing system. The following class, <span style="font-style:italic;">KeyGenerator</span>, generates a public and a private key-pair. The attached Net Beans project has a batch file that will run KeyGenerator, <span style="font-style:italic;">key-gen.bat</span> from the command-line. The only parameter it takes is the length of the key in bits, which is optional. If this parameter is not passed in, a 1024-bit key is used, which is extremely secure. <br /><br />Each bit in the key length doubles the time required to crack the key. A 1024-bit key could take millions of years to crack with current computing power. The downside to this is that if you are using the key to secure client-server traffic, a complex key will slow down the signature generation and verification. A one-off licensing step for some software is an ideal case for a 1024-bit key.<br /><br />The current time is used as a seed to the secure random algorithm which is used to generate the key bytes. Note that the algorithm used is <span style="font-style:italic;"><span style="font-weight:bold;">SHA1PRNG</span></span>, which is a cryptographically strong, <span style="font-style:italic;"><span style="font-weight:bold;">Pseudo-Random Number Generator</span></span> (PRNG) based on the <a href="http://en.wikipedia.org/wiki/Sha1">SHA-1</a> message digest algorithm. A message digest is a fixed-length representation of the data sequence, in this case the sequence being the key bytes.<br /><br />KeyGenerator runs and spits out the files <span style="font-style:italic;">private-key.bin</span> and <span style="font-style:italic;">public-key.bin</span>.<br /><br /><table id="codeblock"><tbody><tr><td><img src="http://antsbull.googlepages.com/expand.gif" class="codeexpander" onclick="showHideElement('codebodydsa3')" title="display / hide the file KeyGenerator.java" /></td><td><h2>KeyGenerator.java</h2></td></tr></tbody></table><br /><div id="codebody"><pre id="codebodydsa3" class="Classes"><A NAME="1"> </A><FONT ID="Package">package</FONT> javamonkey.app.dsa;<br /><A NAME="2"> </A><br /><A NAME="3"></A><FONT ID="Import">import</FONT> java.io.FileOutputStream;<br /><A NAME="4"></A><FONT ID="Import">import</FONT> java.security.GeneralSecurityException;<br /><A NAME="5"></A><FONT ID="Import">import</FONT> java.security.KeyPair;<br /><A NAME="6"></A><FONT ID="Import">import</FONT> java.security.KeyPairGenerator;<br /><A NAME="7"></A><FONT ID="Import">import</FONT> java.security.NoSuchAlgorithmException;<br /><A NAME="8"></A><FONT ID="Import">import</FONT> java.security.NoSuchProviderException;<br /><A NAME="9"></A><FONT ID="Import">import</FONT> java.security.SecureRandom;<br /><A NAME="10"> </A><br /><A NAME="11"></A><FONT ID="FormalComment">/**<br /><A NAME="12"></A> * <p>This class generates an asymmetric key-pair (public key cryptography). <br /><A NAME="13"></A> * The current time is used as a seed to the secure random algorithm which is <br /><A NAME="14"></A> * used to generate the key bytes. Note that the algorithm used is <br /><A NAME="15"></A> * SHA1PRNG, which is a cryptographically strong, Pseudo-Random <br /><A NAME="16"></A> * Number Generator (PRNG) based on the SHA-1 message digest algorithm.</p><br /><A NAME="17"></A> * <p><br /><A NAME="18"></A> * The key length in bits can be specified as a parameter. The main method<br /><A NAME="19"></A> * generates a key pair and writes the pair to disk under the files <br /><A NAME="20"></A> * <code>private-key.bin</code> and <code>public-key.bin</code> to the current<br /><A NAME="21"></A> * working directory.</p><br /><A NAME="22"></A> * @author Ants<br /><A NAME="23"></A> */</FONT><br /><A NAME="24"></A><FONT ID="Public">public</FONT> <FONT ID="Class">class</FONT> KeyGenerator {<br /><A NAME="25"> </A><br /><A NAME="26"></A> <FONT ID="FormalComment">/**<br /><A NAME="27"></A> * Run the key generator and store the key pair in the files private-key.bin <br /><A NAME="28"></A> * and public-key.bin<br /><A NAME="29"></A> * <br /><A NAME="30"></A> * @param args the command line arguments<br /><A NAME="31"></A> */</FONT><br /><A NAME="32"></A> <FONT ID="Public">public</FONT> <FONT ID="Static">static</FONT> <FONT ID="Void">void</FONT> main(String[] args) {<br /><A NAME="33"></A> <FONT ID="Int">int</FONT> keyLength = <FONT ID="IntegerLiteral">1024</FONT>;<br /><A NAME="34"></A> <FONT ID="If">if</FONT> ( args.length > <FONT ID="IntegerLiteral">0</FONT>) {<br /><A NAME="35"></A> <FONT ID="Try">try</FONT> {<br /><A NAME="36"></A> keyLength = Integer.parseInt( args[<FONT ID="IntegerLiteral">0</FONT>]);<br /><A NAME="37"></A> }<br /><A NAME="38"></A> <FONT ID="Catch">catch</FONT> ( NumberFormatException e) {<br /><A NAME="39"></A> printCmdParameters();<br /><A NAME="40"></A> System.exit( <FONT ID="IntegerLiteral">1</FONT>);<br /><A NAME="41"></A> }<br /><A NAME="42"></A> }<br /><A NAME="43"></A> <FONT ID="Try">try</FONT> {<br /><A NAME="44"></A> KeyPair keyPair = <FONT ID="New">new</FONT> <A HREF="../../../javamonkey/app/dsa/KeyGenerator.java.html">KeyGenerator</A>().generateKeyPair( keyLength);<br /><A NAME="45"></A> System.out.println( <FONT ID="StringLiteral">"\nKey pair with key-length of "</FONT> + keyLength +<br /><A NAME="46"></A> <FONT ID="StringLiteral">" successfully generated.\n"</FONT>);<br /><A NAME="47"></A> <FONT ID="SingleLineComment">// Write the private key.<br /><A NAME="48"></A></FONT> FileOutputStream output = <FONT ID="New">new</FONT> FileOutputStream( <FONT ID="StringLiteral">"private-key.bin"</FONT>);<br /><A NAME="49"></A> output.write( keyPair.getPrivate().getEncoded());<br /><A NAME="50"></A> output.close();<br /><A NAME="51"></A> <FONT ID="SingleLineComment">// Write the public key.<br /><A NAME="52"></A></FONT> output = <FONT ID="New">new</FONT> FileOutputStream( <FONT ID="StringLiteral">"public-key.bin"</FONT>);<br /><A NAME="53"></A> output.write( keyPair.getPublic().getEncoded());<br /><A NAME="54"></A> output.close();<br /><A NAME="55"></A> }<br /><A NAME="56"></A> <FONT ID="Catch">catch</FONT> ( Exception e) {<br /><A NAME="57"></A> e.printStackTrace();<br /><A NAME="58"></A> System.exit( <FONT ID="IntegerLiteral">1</FONT>);<br /><A NAME="59"></A> }<br /><A NAME="60"></A> }<br /><A NAME="61"> </A> <br /><A NAME="62"></A> <FONT ID="Private">private</FONT> <FONT ID="Static">static</FONT> <FONT ID="Void">void</FONT> printCmdParameters() {<br /><A NAME="63"></A> System.out.println( <FONT ID="StringLiteral">"\n\nUsage: key-gen.bat [key-length]"</FONT> +<br /><A NAME="64"></A> <FONT ID="StringLiteral">"\n\nkey-length defaults to 1024\n\n"</FONT>);<br /><A NAME="65"></A> }<br /><A NAME="66"> </A><br /><A NAME="67"></A> <FONT ID="Public">public</FONT> KeyGenerator() {<br /><A NAME="68"></A> <FONT ID="Super">super</FONT>();<br /><A NAME="69"></A> }<br /><A NAME="70"> </A> <br /><A NAME="71"></A> <FONT ID="FormalComment">/**<br /><A NAME="72"></A> * Generate the keypair using the given key length in bits.<br /><A NAME="73"></A> * @param keyBitSize the number of bits for the key length. e.g. 512,1024<br /><A NAME="74"></A> * @return the key pair<br /><A NAME="75"></A> * @throws java.security.GeneralSecurityException on any errors during the<br /><A NAME="76"></A> * generation<br /><A NAME="77"></A> */</FONT><br /><A NAME="78"></A> <FONT ID="Public">public</FONT> KeyPair generateKeyPair( <FONT ID="Int">int</FONT> keyBitSize) <br /><A NAME="79"></A> <FONT ID="Throws">throws</FONT> GeneralSecurityException {<br /><A NAME="80"></A> <FONT ID="SingleLineComment">// Use a digital signature algorithm generator.<br /><A NAME="81"></A></FONT> KeyPairGenerator generator = KeyPairGenerator.getInstance( <FONT ID="StringLiteral">"DSA"</FONT>);<br /><A NAME="82"></A> <FONT ID="SingleLineComment">// Random algorithm is SHA-1 with pseudo-random number generator.<br /><A NAME="83"></A></FONT> SecureRandom randomAlg = SecureRandom.getInstance( <FONT ID="StringLiteral">"SHA1PRNG"</FONT>, <FONT ID="StringLiteral">"SUN"</FONT>);<br /><A NAME="84"></A> randomAlg.setSeed( System.currentTimeMillis());<br /><A NAME="85"></A> generator.initialize( keyBitSize, randomAlg);<br /><A NAME="86"></A> <FONT ID="Return">return</FONT> generator.generateKeyPair();<br /><A NAME="87"></A> }<br /><A NAME="88"> </A><br /><A NAME="89"></A>}<br /><A NAME="90"></A></pre></div><span style="font-weight: bold; color: rgb(255, 204, 102);">A Signature is required, Sir.</span><br /><br />Step two is to implement the signature algorithm. This takes the raw (unencrypted) license string, and generates the digital signature of it, using your system's private key. <br /><br />The code is very simple - load the key from disk, create a private key instance from the raw bytes and then sign the input string (license number) with the private key instance. <br /><br />The resulting bytes are then <a href="http://en.wikipedia.org/wiki/Base64"><span style="font-style:italic;">Base-64 encoded</span></a>. The base-64 encoding, which is a MIME content transfer encoding, allows the signature to be displayed using Alpha-Numeric characters - instead of raw binary data. The resulting string can easily be emailed out, printed out or displayed in a web browser, so a user can enter it into your system. <br /><br />The following class, SignatureGenerator, generates the digital signature of the input string and returns the Base-64 encoded, digital signature. In the attached Net Beans project, sig-gen.bat takes an input string and the <span style="font-style:italic;">private-key.bin</span> stored private key and prints out the signature to the console and writes the encoded signature to disk, to the file signature.base64.<br /><br />If you are wondering what a base-64 signature looks like:<br /><br /><span style="color:white;font-weight:bold;">MCwCFCsAXvhNi8kbal/ZomD7jI06/KmlAhR9ZCKN+kC9jAKdDPJ38aaJiVpxXg==</span><br /><br /><br /><table id="codeblock"><tbody><tr><td><img src="http://antsbull.googlepages.com/expand.gif" class="codeexpander" onclick="showHideElement('codebodydsa2')" title="display / hide the file SignatureGenerator.java" /></td><td><h2>SignatureGenerator.java</h2></td></tr></tbody></table><br /><div id="codebody"><pre id="codebodydsa2" class="Classes"><A NAME="1"></A><FONT ID="Package">package</FONT> javamonkey.app.dsa;<br /><A NAME="2"> </A><br /><A NAME="3"></A><FONT ID="Import">import</FONT> java.io.FileInputStream;<br /><A NAME="4"></A><FONT ID="Import">import</FONT> java.io.FileOutputStream;<br /><A NAME="5"></A><FONT ID="Import">import</FONT> java.io.IOException;<br /><A NAME="6"></A><FONT ID="Import">import</FONT> java.security.GeneralSecurityException;<br /><A NAME="7"></A><FONT ID="Import">import</FONT> java.security.InvalidKeyException;<br /><A NAME="8"></A><FONT ID="Import">import</FONT> java.security.KeyFactory;<br /><A NAME="9"></A><FONT ID="Import">import</FONT> java.security.NoSuchAlgorithmException;<br /><A NAME="10"></A><FONT ID="Import">import</FONT> java.security.PrivateKey;<br /><A NAME="11"></A><FONT ID="Import">import</FONT> java.security.Signature;<br /><A NAME="12"></A><FONT ID="Import">import</FONT> java.security.SignatureException;<br /><A NAME="13"></A><FONT ID="Import">import</FONT> java.security.spec.PKCS8EncodedKeySpec;<br /><A NAME="14"></A><FONT ID="Import">import</FONT> sun.misc.BASE64Encoder;<br /><A NAME="15"> </A><br /><A NAME="16"></A><FONT ID="FormalComment">/**<br /><A NAME="17"></A> * Signature Generator generates a signature for an input string. The signature<br /><A NAME="18"></A> * generation requires the private-key part of an asymmetric key pair. <br /><A NAME="19"></A> * <br /><A NAME="20"></A> * @author Ants<br /><A NAME="21"></A> */</FONT><br /><A NAME="22"></A><FONT ID="Public">public</FONT> <FONT ID="Class">class</FONT> SignatureGenerator {<br /><A NAME="23"> </A> <br /><A NAME="24"></A> <FONT ID="FormalComment">/**<br /><A NAME="25"></A> * Create a signature generator, using the specified private key <br /><A NAME="26"></A> * @param privateKeyBytes the private key to use in the signature<br /><A NAME="27"></A> * generation<br /><A NAME="28"></A> * @throws GeneralSecurityException on any errors initialising the private<br /><A NAME="29"></A> * key instance<br /><A NAME="30"></A> */</FONT><br /><A NAME="31"></A> <FONT ID="Public">public</FONT> SignatureGenerator( <FONT ID="Byte">byte</FONT>[] privateKeyBytes) <br /><A NAME="32"></A> <FONT ID="Throws">throws</FONT> GeneralSecurityException { <br /><A NAME="33"></A> <FONT ID="Super">super</FONT>();<br /><A NAME="34"></A> initPrivateKey( privateKeyBytes);<br /><A NAME="35"></A> }<br /><A NAME="36"> </A><br /><A NAME="37"></A> <FONT ID="FormalComment">/**<br /><A NAME="38"></A> * Create a signature generator, using the private key in the given file<br /><A NAME="39"></A> * @param filePath the path to a file containing the private key to use in<br /><A NAME="40"></A> * the signature generation<br /><A NAME="41"></A> * @throws GeneralSecurityException on any errors initialising the private<br /><A NAME="42"></A> * key instance<br /><A NAME="43"></A> * @throws IOException on any errors while reading the private key from<br /><A NAME="44"></A> * disk<br /><A NAME="45"></A> */</FONT><br /><A NAME="46"></A> <FONT ID="Public">public</FONT> SignatureGenerator( String filePath) <br /><A NAME="47"></A> <FONT ID="Throws">throws</FONT> IOException, GeneralSecurityException {<br /><A NAME="48"></A> FileInputStream input = <FONT ID="New">new</FONT> FileInputStream( filePath);<br /><A NAME="49"></A> <FONT ID="Byte">byte</FONT>[] keyBytes = <FONT ID="New">new</FONT> <FONT ID="Byte">byte</FONT>[input.available()];<br /><A NAME="50"></A> input.read( keyBytes);<br /><A NAME="51"></A> input.close();<br /><A NAME="52"></A> initPrivateKey( keyBytes);<br /><A NAME="53"></A> }<br /><A NAME="54"> </A> <br /><A NAME="55"></A> <FONT ID="SingleLineComment">// Create the private key instance, using the key bytes passed in.<br /><A NAME="56"></A></FONT> <FONT ID="Private">private</FONT> <FONT ID="Void">void</FONT> initPrivateKey( <FONT ID="Byte">byte</FONT>[] privateKeyBytes) <br /><A NAME="57"></A> <FONT ID="Throws">throws</FONT> GeneralSecurityException {<br /><A NAME="58"></A> PKCS8EncodedKeySpec keySpec = <FONT ID="New">new</FONT> PKCS8EncodedKeySpec( privateKeyBytes);<br /><A NAME="59"></A> KeyFactory keyFactory = KeyFactory.getInstance( <FONT ID="StringLiteral">"DSA"</FONT>);<br /><A NAME="60"></A> <FONT ID="This">this</FONT>.privateKey = keyFactory.generatePrivate( keySpec);<br /><A NAME="61"></A> }<br /><A NAME="62"> </A> <br /><A NAME="63"></A> <FONT ID="SingleLineComment">// The private key used to sign input data.<br /><A NAME="64"></A></FONT> <FONT ID="Private">private</FONT> PrivateKey privateKey;<br /><A NAME="65"> </A> <br /><A NAME="66"></A> <FONT ID="FormalComment">/**<br /><A NAME="67"></A> * Sign the given message using the SHA-1 with DSA algorithm.<br /><A NAME="68"></A> * @param message the message to sign<br /><A NAME="69"></A> * @return the signature bytes - note that these should be base 64 encoded<br /><A NAME="70"></A> * if these are required to be viewed by users<br /><A NAME="71"></A> * @throws InvalidKeyException if the private key provided is invalid<br /><A NAME="72"></A> * @throws NoSuchAlgorithmException if the signature algorithm is not<br /><A NAME="73"></A> * available<br /><A NAME="74"></A> * @throws SignatureException on any errors during the signature generation<br /><A NAME="75"></A> */</FONT><br /><A NAME="76"></A> <FONT ID="Public">public</FONT> <FONT ID="Byte">byte</FONT>[] signMessage( String message) <br /><A NAME="77"></A> <FONT ID="Throws">throws</FONT> NoSuchAlgorithmException, SignatureException, <br /><A NAME="78"></A> InvalidKeyException {<br /><A NAME="79"></A> Signature sig = Signature.getInstance( <FONT ID="StringLiteral">"SHA1withDSA"</FONT>);<br /><A NAME="80"></A> sig.initSign( privateKey);<br /><A NAME="81"></A> sig.update( message.getBytes());<br /><A NAME="82"></A> <FONT ID="Return">return</FONT> sig.sign();<br /><A NAME="83"></A> }<br /><A NAME="84"> </A> <br /><A NAME="85"></A> <FONT ID="Private">private</FONT> <FONT ID="Static">static</FONT> <FONT ID="Void">void</FONT> printCmdParameters() {<br /><A NAME="86"></A> System.out.println( <FONT ID="StringLiteral">"\n\nUsage: sig-gen.bat [message]"</FONT> +<br /><A NAME="87"></A> <FONT ID="StringLiteral">"\n\nThis requires private-key.bin. The message is "</FONT> +<br /><A NAME="88"></A> <FONT ID="StringLiteral">"base-64 encoded and written to the file signature.base64\n\n"</FONT>);<br /><A NAME="89"></A> }<br /><A NAME="90"> </A><br /><A NAME="91"></A> <FONT ID="Public">public</FONT> <FONT ID="Static">static</FONT> <FONT ID="Void">void</FONT> main( String[] args) {<br /><A NAME="92"></A> <FONT ID="If">if</FONT> ( args.length != <FONT ID="IntegerLiteral">1</FONT>) {<br /><A NAME="93"></A> printCmdParameters();<br /><A NAME="94"></A> System.exit( <FONT ID="IntegerLiteral">1</FONT>);<br /><A NAME="95"></A> }<br /><A NAME="96"></A> <FONT ID="Try">try</FONT> {<br /><A NAME="97"></A> <A HREF="../../../javamonkey/app/dsa/SignatureGenerator.java.html">SignatureGenerator</A> sigGen = <FONT ID="New">new</FONT> <A HREF="../../../javamonkey/app/dsa/SignatureGenerator.java.html">SignatureGenerator</A>( <FONT ID="StringLiteral">"private-key.bin"</FONT>);<br /><A NAME="98"></A> <FONT ID="Byte">byte</FONT>[] message = sigGen.signMessage( args[<FONT ID="IntegerLiteral">0</FONT>]);<br /><A NAME="99"></A> BASE64Encoder encoder = <FONT ID="New">new</FONT> BASE64Encoder();<br /><A NAME="100"></A> String signature = encoder.encode( message);<br /><A NAME="101"></A> System.out.println(<FONT ID="StringLiteral">"\nSignature = ["</FONT> + signature + <FONT ID="StringLiteral">"]\n\n"</FONT>);<br /><A NAME="102"></A> FileOutputStream output = <FONT ID="New">new</FONT> FileOutputStream( <FONT ID="StringLiteral">"signature.base64"</FONT>);<br /><A NAME="103"></A> output.write( signature.getBytes());<br /><A NAME="104"></A> output.close();<br /><A NAME="105"></A> }<br /><A NAME="106"></A> <FONT ID="Catch">catch</FONT> ( Exception e) {<br /><A NAME="107"></A> e.printStackTrace();<br /><A NAME="108"></A> System.exit( <FONT ID="IntegerLiteral">1</FONT>);<br /><A NAME="109"></A> }<br /><A NAME="110"></A> }<br /><A NAME="111"></A><br /><A NAME="112"></A>}<br /><A NAME="113"></A></pre></div><span style="font-weight: bold; color: rgb(255, 204, 102);">Unlocking the Revolution</span><br /><br />The third and final step, is where, after the license has been decrypted and its properties stored, the signature is then verified using the public key from step one. The following class, <span style="font-style:italic;">SignatureVerifier</span>, verifies the digital signature that it is passed. <br /><br />In the attached Net Beans project, sig-verify.bat takes the <span style="font-style:italic;">public-key.bin</span> stored public key, takes the signature.base64 stored signature and verifies it.<br /><br /><table id="codeblock"><tbody><tr><td><img src="http://antsbull.googlepages.com/expand.gif" class="codeexpander" onclick="showHideElement('codebodydsa1')" title="display / hide the file SignatureVerifier.java" /></td><td><h2>SignatureVerifier.java</h2></td></tr></tbody></table><br /><div id="codebody"><pre id="codebodydsa1" class="Classes"><A NAME="1"></A><FONT ID="Package">package</FONT> javamonkey.app.dsa;<br /><A NAME="2"> </A><br /><A NAME="3"></A><FONT ID="Import">import</FONT> java.io.FileInputStream;<br /><A NAME="4"></A><FONT ID="Import">import</FONT> java.io.FileOutputStream;<br /><A NAME="5"></A><FONT ID="Import">import</FONT> java.io.IOException;<br /><A NAME="6"></A><FONT ID="Import">import</FONT> java.security.GeneralSecurityException;<br /><A NAME="7"></A><FONT ID="Import">import</FONT> java.security.KeyFactory;<br /><A NAME="8"></A><FONT ID="Import">import</FONT> java.security.PrivateKey;<br /><A NAME="9"></A><FONT ID="Import">import</FONT> java.security.PublicKey;<br /><A NAME="10"></A><FONT ID="Import">import</FONT> java.security.Signature;<br /><A NAME="11"></A><FONT ID="Import">import</FONT> java.security.spec.PKCS8EncodedKeySpec;<br /><A NAME="12"></A><FONT ID="Import">import</FONT> java.security.spec.X509EncodedKeySpec;<br /><A NAME="13"></A><FONT ID="Import">import</FONT> sun.misc.BASE64Decoder;<br /><A NAME="14"> </A><br /><A NAME="15"></A><FONT ID="FormalComment">/**<br /><A NAME="16"></A> * Signature Verifier verifies a digital signature. The signature<br /><A NAME="17"></A> * verification requires the public-key part of an asymmetric key pair. <br /><A NAME="18"></A> * <br /><A NAME="19"></A> * @author Ants<br /><A NAME="20"></A> */</FONT><br /><A NAME="21"></A><FONT ID="Public">public</FONT> <FONT ID="Class">class</FONT> SignatureVerifier {<br /><A NAME="22"> </A><br /><A NAME="23"></A> <FONT ID="FormalComment">/**<br /><A NAME="24"></A> * Create a signature generator, using the specified public key <br /><A NAME="25"></A> * @param publicKeyBytes the public key to use in the signature<br /><A NAME="26"></A> * generation<br /><A NAME="27"></A> * @throws GeneralSecurityException on any errors initialising the public<br /><A NAME="28"></A> * key instance<br /><A NAME="29"></A> */</FONT><br /><A NAME="30"></A> <FONT ID="Public">public</FONT> SignatureVerifier( <FONT ID="Byte">byte</FONT>[] publicKeyBytes) <br /><A NAME="31"></A> <FONT ID="Throws">throws</FONT> GeneralSecurityException { <br /><A NAME="32"></A> <FONT ID="Super">super</FONT>();<br /><A NAME="33"></A> initPublicKey( publicKeyBytes);<br /><A NAME="34"></A> }<br /><A NAME="35"> </A><br /><A NAME="36"></A> <FONT ID="FormalComment">/**<br /><A NAME="37"></A> * Create a signature verifier, using the public key in the given file<br /><A NAME="38"></A> * @param filePath the path to a file containing the public key to use in<br /><A NAME="39"></A> * the signature generation<br /><A NAME="40"></A> * @throws GeneralSecurityException on any errors initialising the public<br /><A NAME="41"></A> * key instance<br /><A NAME="42"></A> * @throws IOException on any errors while reading the public key from<br /><A NAME="43"></A> * disk<br /><A NAME="44"></A> */</FONT><br /><A NAME="45"></A> <FONT ID="Public">public</FONT> SignatureVerifier( String filePath) <br /><A NAME="46"></A> <FONT ID="Throws">throws</FONT> IOException, GeneralSecurityException {<br /><A NAME="47"></A> FileInputStream input = <FONT ID="New">new</FONT> FileInputStream( filePath);<br /><A NAME="48"></A> <FONT ID="Byte">byte</FONT>[] keyBytes = <FONT ID="New">new</FONT> <FONT ID="Byte">byte</FONT>[input.available()];<br /><A NAME="49"></A> input.read( keyBytes);<br /><A NAME="50"></A> input.close();<br /><A NAME="51"></A> initPublicKey( keyBytes);<br /><A NAME="52"></A> }<br /><A NAME="53"> </A> <br /><A NAME="54"></A> <FONT ID="SingleLineComment">// Create the public key instance, using the key bytes passed in.<br /><A NAME="55"></A></FONT> <FONT ID="Private">private</FONT> <FONT ID="Void">void</FONT> initPublicKey( <FONT ID="Byte">byte</FONT>[] publicKeyBytes) <br /><A NAME="56"></A> <FONT ID="Throws">throws</FONT> GeneralSecurityException {<br /><A NAME="57"></A> X509EncodedKeySpec keySpec = <FONT ID="New">new</FONT> X509EncodedKeySpec( publicKeyBytes);<br /><A NAME="58"></A> KeyFactory keyFactory = KeyFactory.getInstance( <FONT ID="StringLiteral">"DSA"</FONT>);<br /><A NAME="59"></A> <FONT ID="This">this</FONT>.publicKey = keyFactory.generatePublic( keySpec);<br /><A NAME="60"></A> }<br /><A NAME="61"> </A> <br /><A NAME="62"></A> <FONT ID="SingleLineComment">// The public key used to verify the digital signature.<br /><A NAME="63"></A></FONT> <FONT ID="Private">private</FONT> PublicKey publicKey;<br /><A NAME="64"> </A> <br /><A NAME="65"></A> <FONT ID="FormalComment">/**<br /><A NAME="66"></A> * Verify the signature for a message.<br /><A NAME="67"></A> * @param signatureBytes the digital signature to verify<br /><A NAME="68"></A> * @param message the message that the signature is for<br /><A NAME="69"></A> * @return true if the verification succeeds, otherwise false<br /><A NAME="70"></A> * @throws java.security.GeneralSecurityException on any security API errors<br /><A NAME="71"></A> * during the verification<br /><A NAME="72"></A> */</FONT><br /><A NAME="73"></A> <FONT ID="Public">public</FONT> <FONT ID="Boolean">boolean</FONT> verifyDigitalSignature( <FONT ID="Byte">byte</FONT>[] signatureBytes, <br /><A NAME="74"></A> String message) <FONT ID="Throws">throws</FONT> GeneralSecurityException {<br /><A NAME="75"></A> Signature sig = Signature.getInstance( <FONT ID="StringLiteral">"SHA1withDSA"</FONT>);<br /><A NAME="76"></A> sig.initVerify( publicKey);<br /><A NAME="77"></A> sig.update( message.getBytes());<br /><A NAME="78"></A> <FONT ID="Return">return</FONT> sig.verify( signatureBytes);<br /><A NAME="79"></A> }<br /><A NAME="80"> </A> <br /><A NAME="81"></A> <FONT ID="Private">private</FONT> <FONT ID="Static">static</FONT> <FONT ID="Void">void</FONT> printCmdParameters() {<br /><A NAME="82"></A> System.out.println( <FONT ID="StringLiteral">"\n\nUsage: sig-verify.bat [message]"</FONT> +<br /><A NAME="83"></A> <FONT ID="StringLiteral">"\n\nThis requires the files public-key.bin and "</FONT> +<br /><A NAME="84"></A> <FONT ID="StringLiteral">"signature.base64.\n\n"</FONT>);<br /><A NAME="85"></A> }<br /><A NAME="86"> </A><br /><A NAME="87"></A> <FONT ID="Public">public</FONT> <FONT ID="Static">static</FONT> <FONT ID="Void">void</FONT> main( String[] args) {<br /><A NAME="88"></A> <FONT ID="If">if</FONT> ( args.length != <FONT ID="IntegerLiteral">1</FONT>) {<br /><A NAME="89"></A> printCmdParameters();<br /><A NAME="90"></A> System.exit( <FONT ID="IntegerLiteral">1</FONT>);<br /><A NAME="91"></A> }<br /><A NAME="92"></A> <FONT ID="Try">try</FONT> {<br /><A NAME="93"></A> <FONT ID="SingleLineComment">// Read the signature in and decode it.<br /><A NAME="94"></A></FONT> FileInputStream input = <FONT ID="New">new</FONT> FileInputStream( <FONT ID="StringLiteral">"signature.base64"</FONT>);<br /><A NAME="95"></A> BASE64Decoder decoder = <FONT ID="New">new</FONT> BASE64Decoder();<br /><A NAME="96"></A> <FONT ID="Byte">byte</FONT>[] sigBytes = decoder.decodeBuffer( input);<br /><A NAME="97"></A> input.close();<br /><A NAME="98"> </A><br /><A NAME="99"></A> <FONT ID="SingleLineComment">// Verify the digital signature.<br /><A NAME="100"></A></FONT> <A HREF="../../../javamonkey/app/dsa/SignatureVerifier.java.html">SignatureVerifier</A> sigVerify = <FONT ID="New">new</FONT> <A HREF="../../../javamonkey/app/dsa/SignatureVerifier.java.html">SignatureVerifier</A>( <FONT ID="StringLiteral">"public-key.bin"</FONT>);<br /><A NAME="101"></A> <FONT ID="If">if</FONT> ( sigVerify.verifyDigitalSignature( sigBytes, args[<FONT ID="IntegerLiteral">0</FONT>])) {<br /><A NAME="102"></A> System.out.println( <FONT ID="StringLiteral">"\nSignature verification successful!\n"</FONT>);<br /><A NAME="103"></A> }<br /><A NAME="104"></A> <FONT ID="Else">else</FONT> {<br /><A NAME="105"></A> System.out.println( <FONT ID="StringLiteral">"\nSignature verification failed!\n"</FONT>);<br /><A NAME="106"></A> System.exit( <FONT ID="IntegerLiteral">1</FONT>);<br /><A NAME="107"></A> }<br /><A NAME="108"></A> }<br /><A NAME="109"></A> <FONT ID="Catch">catch</FONT> ( Exception e) {<br /><A NAME="110"></A> e.printStackTrace();<br /><A NAME="111"></A> System.exit( <FONT ID="IntegerLiteral">1</FONT>);<br /><A NAME="112"></A> }<br /><A NAME="113"></A> }<br /><A NAME="114"> </A> <br /><A NAME="115"></A>}<br /><A NAME="116"></A></pre></div>The attached Net Beans project contains a batch file, sig-verify.bat that takes the message string as a parameter and uses public-key.bin and signature.base64 to verify the signature.<br /><br /><table class="codelink"><tbody><tr><td><a href="http://antsbull.googlepages.com/digital-signing.zip"><img class="codeimage" src="http://antsbull.googlepages.com/disks.gif" /> Download the NetBeans project and source/binaries here.</a></td></tr></tbody></table>The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com35tag:blogger.com,1999:blog-5374152728206138749.post-89567358686876180172008-07-22T22:53:00.012+12:002009-05-21T21:44:37.832+12:00Using Apache HTTPD server as a secure Tomcat proxy server<br/><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9cUo9P4N6zS6Yrg4GhYpglrU5kI3S4N3czn1udc5RYFZijd8a5ZPuZqYGrrskWfClV50H38SZCHWBVsTodY8JJrAWwE7NUUgCiiy15YvSr9AUVqYt7K7ubbDobx59VOEUQEIN1PZ_WwTY/s1600-h/sword-fight.png"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9cUo9P4N6zS6Yrg4GhYpglrU5kI3S4N3czn1udc5RYFZijd8a5ZPuZqYGrrskWfClV50H38SZCHWBVsTodY8JJrAWwE7NUUgCiiy15YvSr9AUVqYt7K7ubbDobx59VOEUQEIN1PZ_WwTY/s400/sword-fight.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5225790303669814290" /></a><br />For the last 8 years or so, I have almost exclusively developed applications that run on Tomcat. Whether it be JSP applications, servlet based SOAP/XML services, Struts, Faces, or Axis 2 web services - Tomcat has always been involved. As such, many, many times over that period, I have also been asked to setup servers in the work environment, or hosted servers for clients, or install complete systems onto developer machines and laptops. <br /><br />Tomcat is easy to setup. However, Tomcat on its own does often not provide everything that is needed for a production environment. Tomcat is wonderful at running Java servlet based applications. When a client needs to have multiple Tomcats on one server/environment, all going through the same external port and all using the same SSL certificate, the excellent Apache HTTP Server is also needed. Every single time I need to set up a secure Apache proxy, that redirects to internal Tomcats based on the context of an incoming request, I have to spend ages playing around with conf files to get it to work. I can never remember quite how to do it.<br /><br />This blog is a guide to setting up Apache as a secured, external, proxy to multiple internal Tomcats, where Tomcat resolution is managed via the context names in the URL. The advantages of this approach are that you do not need domain names for your forwarding, you only need one SSL certificate for the server, and you only need one external port (443) opened on your server.<br /><br />The architecture is pretty simple, and is illustrated in the following diagram (drawn in the NetBeans UML editor which is handy but has been hit with the ugly stick):<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://antsbull.googlepages.com/apache-proxy-component.png"><img style="display:block; margin:0 10px 10px 0;cursor:pointer; cursor:hand;" src="http://antsbull.googlepages.com/apache-proxy-component.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5225794115203034866" /></a><br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Installing Apache 2.2</span><br /><br />Apache 2.2 is available from the Apache Software Foundation, <a href="http://httpd.apache.org/download.cgi">here</a>. Download the Win 32 binary, with Open SSL support included. Note that this guide will work for non Win servers also, however, the initial install on those boxes can be tough, so I'm not wasting time here explaining it.<br /><br />Run the installer, setup your domain and server name details. You will be wanting to configure Apache to install as a Windows service so that it is always available. Once it is installed, open up a web browser and go to <span style="font-weight:bold;"><span style="font-style:italic;">http://localhost</span></span> - you will see the welcome screen containing the text "It works!".<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Lock it down!</span><br /><br />The version of Apache downloaded above contains all that is necessary to secure it to run over SSL/HTTPS. Open a command prompt and go to the Apache bin folder <span style="font-style:italic;"><span style="font-weight:bold;">C:\Program Files\Apache Software Foundation\Apache2.2\bin</span></span>. Enter the following command to generate a self-signed certificate:<br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes"><br />OPENSSL.exe req -new -x509 -nodes -extensions v3_req <br /> -days 365 -config ..\conf\openssl.cnf -out ..\conf\server.crt <br /> -keyout ..\conf\server.key<br /></pre></div><br />The common name is your domain name (e.g. <span style="font-style:italic;">www.google.com</span>), if you have one. This will generate a key file, <span style="font-style:italic;">server.key</span>, and a certificate file, <span style="font-style:italic;">server.crt</span>. The certificate is for external applications to send encrypted traffic to the Apache server. The key is what the Apache server uses to decrypt that traffic.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Configuring Apache Httpd</span><br /><br />Now that the SSL parts are generated, the final step is to configure the Apache server to be secure and to act as a proxy to our Tomcat(s). The file <span style="font-style:italic;">conf\httpd.conf</span> contains most of the configuration for the Apache server. <br /><br />The following changes need to be applied to it:<br /><br /><ol><li>Uncomment the proxy_module, proxy_http_module and ssl_module modules.</li><li>Uncomment the include statement for conf/extra/httpd-ssl.conf.</li><li>Edit conf/extra/httpd-ssl.conf and include lines similar to the following before the closing VirtualHost tag:</li></ol><br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes"> ProxyPass /context-name-1 http://localhost:8080/context-name-1<br /> ProxyPassReverse /context-name-1 http://localhost:8080/context-name-1<br /><br /> ProxyPass /context-name-2 http://localhost:8080/context-name-2<br /> ProxyPassReverse /context-name-2 http://localhost:8080/context-name-2<br /><br /> ProxyPass /context-name-3 http://localhost:8081/context-name-3<br /> ProxyPassReverse /context-name-3 http://localhost:8081/context-name-3<br /></pre></div><br />These setup the name based proxy - so anything coming into the server with a context name of context-name-1 will go to the Tomcat on port 8080 and to the webapp named context-name-1.<br /><br />Note that the above setup will also have setup an unsecured Apache listener on port 80 also - this can be turned off if all traffic is going to port 443.<br /><br />Now go to the tray icons on your desktop, open the Apache Service Monitor, and hit restart. Its done!<br /><br />If you get any errors, simply check the log files under the logs folder (error.log).<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Tomcat Proxy Settings</span><br /><br />If you are serving up a service then you are all done. If you are also or only serving web pages, then you need to also tell your Tomcat the domain details and that it is a proxy server. If you don't, you will find that all the links on your rendered JSP pages actually contain the local tomcat protocol (HTTP), host (localhost) and port (e.g. 8080), instead of your secured domain.<br /><br />Luckily, Tomcat supports this through its <span style="font-style:italic;">Connector</span> element (which is found in <span style="font-style:italic;">conf/server.xml</span> in your Tomcat <span style="font-style:italic;">CATALINA_BASE</span> folder). Set the following parameters to similar values to match your domain or external IP address (if you have no domain):<br /><br /><ol><li>secure = false</li><br /><li>scheme = https</li><br /><li>proxyName = www.mydomain.com</li><br /><li>proxyPort = 443</li></ol><br /><br />This will force Tomcat to translate all the link addresses to match your domain details. Note that secure is set to false, because the Tomcat is not handling SSL. However, the scheme must be set to HTTPS so that all the links on your pages contain that as their protocol.<br /><br />Enjoy!The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com11tag:blogger.com,1999:blog-5374152728206138749.post-69816302762167307452008-07-07T08:24:00.024+12:002008-10-05T17:33:55.376+13:00Using Apache Directory Server as a Key Distribution Centre (KDC)<br/><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFFk-vlpxc5ITEdBKlxyk0-DY7vaiqN1ZlUs230kWH-eHl_AOb1G9dvqc2xYVvYYQ4VzwaiX-0rzoGbTSaRx9sZ-2R1n_zhyBslJxhWKQNO19HpyFu_DGCtqnEHLfYP99FeVxryPYZD1an/s1600-h/apache-dir.png"><img style="cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFFk-vlpxc5ITEdBKlxyk0-DY7vaiqN1ZlUs230kWH-eHl_AOb1G9dvqc2xYVvYYQ4VzwaiX-0rzoGbTSaRx9sZ-2R1n_zhyBslJxhWKQNO19HpyFu_DGCtqnEHLfYP99FeVxryPYZD1an/s400/apache-dir.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5220010195940560082" /></a><br /><br /><span style="font-weight:bold;">Note: This blog relates to version 1.5.1 of Apache Directory Server - since then they have radically changed the configuration format. That is, version 1.5.3 cannot be configured as a KDC using the steps in this blog.</span><br /><br />All the Kerberos authentication development I have done has been in a Windows environment. All of this development has required single-sign-on (SSO) between interoperable .NET WSE3 and Java applications. Therefore, Active Directory has been the KDC. <br /><br />I really like having a full development environment on my laptop, so that if necessary, I can try things out at home or on my daily commute to work (an hour on the train). Without having a VPN to my work environment, the next best thing is to have my own KDC installed on my laptop.<br /><br />As seems to be the case with nearly every type of Java enterprise component or solution that one needs, the Apache Software Foundation have a great Java-based solution that fits like a hand into a glove - the <a href="http://directory.apache.org/">Apache Directory Project</a>.<br /><br />Out of the box, Apache Directory isn't configured as a KDC, and their site is kind of hard to use to locate and understand this configuration. This blog details how I setup my Apache Directory install as a working KDC for Kerberos authentication and authorisation through Java.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">The Software</span><br />Download both <a href="http://directory.apache.org/apacheds/1.5/downloads.html">Apache Directory Server</a> and its front end, <a href="http://directory.apache.org/studio/downloads.html">Apache Directory Studio</a>. Studio is a pretty impressive front end, and looks <span style="font-weight:bold;"><span style="font-style:italic;">*really*</span></span> nice. A great example of how nice an application developed on the Eclipse platform can look and feel. Anyway, go to those links and download the server and the studio and install them.<br /><br />Apache Directory is Java based and runs as a Windows service. It is configured using Spring, so some familiarity with Spring would be useful, but is not essential. Once it has installed, pull up a file browser and we'll dive in and Kerberos-alize it.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Configuring the Beast (Kerberos)</span><br />Under your Apache Directory Server install (<span style="font-weight:bold;"><span style="font-style:italic;">C:\Program Files\Apache Directory Server</span></span>), navigate to the folder <span style="font-style:italic;"><span style="font-weight:bold;">instances\default\conf</span></span>. This folder contains the config files for your server instance. <span style="font-weight:bold;"><span style="font-style:italic;">server.xml</span></span> is the Spring context configuration for your server, and contains the Kerberos config settings. Open this file up in a text editor.<br /><br />Firstly, make sure that the <span style="font-style:italic;"><span style="font-weight:bold;">kdcConfiguration</span></span> bean is enabled (not commented out). You may also need to set the <span style="font-style:italic;">enabled</span> flag to true (see the part highlighted in <span style="color:blue">blue</span>):<br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes"><br /> <bean id="kdcConfiguration" <br /> class="org.apache.directory.server.kerberos.kdc.KdcConfiguration"><br /> <!-- Whether to enable the Kerberos protocol. --><br /> <span style="color:blue"><property name="enabled" value="true" /></span><br /> <!-- The port to run the Kerberos protocol on. --><br /> <property name="ipPort" value="88" /><br /> <!--<property name="searchBaseDn" value="ou=Users" />--><br /> </bean><br /></pre></div>Secondly, ensure that the Key Derivation Service is enabled:<br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes"><br /> <bean class="org.apache.directory.server.core.configuration.<br /> MutableInterceptorConfiguration"><br /> <property name="name" value="keyDerivationService" /><br /> <property name="interceptorClassName"<br /> value="org.apache.directory.server.core.kerberos.KeyDerivationService" /><br /> </bean><br /></pre></div>Thirdly, ensure that the <span style="font-weight:bold;"><span style="font-style:italic;">kdcConfiguration</span></span> bean is injected onto the <span style="font-style:italic;"><span style="font-weight:bold;">configuration</span></span> bean (as a property ref). That is, the following line must appear and not be commented out on the configuration bean:<br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes"><br /> <property name="kdcConfiguration" ref="kdcConfiguration" /><br /></pre></div>Finally, I always configure my Kerberos principals/identities via an <span style="font-style:italic;"><span style="font-weight:bold;">.ldif</span></span> file, as opposed to trying to do it through the front end. Make sure that on the configuration bean, the <span style="font-weight:bold;"><span style="font-style:italic;">ldifDirectory</span></span> property is enabled and points to a location on your machine where it can find an .ldif file that we are about to prepare.<br /><br />Apache Directory comes with a domain, EXAMPLE.COM already setup. This is fine for the purposes we need it for. Create the following .ldif file, modifying the names where appropriate (i.e. client and server names) and point the server.xml Spring context to this, as mentioned above. Note that the service principal has my laptop machine name "BULLY" in its krb5 principal - this should be changed to the name of the server that your service is going to be running on.<br /><br />Stop and start the Apache Directory service (on Windows this can be done through the Services administrative tool). This causes the .ldif file to load, as long as it has never been loaded before.<br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes"><br /># Web server identity/service principal.<br />dn: uid=webserver,ou=users,dc=example,dc=com<br />objectclass: top<br />objectclass: person<br />objectclass: inetOrgPerson<br />objectclass: krb5Principal<br />objectclass: krb5KDCEntry<br />cn: Web Server<br />sn: Web Server<br />uid: webserver<br />userpassword: Password99<br />krb5PrincipalName: webserver/bully@EXAMPLE.COM<br /><br /># User / client principal.<br />dn: uid=monkey,ou=users,dc=example,dc=com<br />objectclass: top<br />objectclass: person<br />objectclass: inetOrgPerson<br />objectclass: krb5Principal<br />objectclass: krb5KDCEntry<br />cn: Monkey<br />sn: Monkey<br />uid: monkey<br />userpassword: Password99<br />krb5PrincipalName: monkey@EXAMPLE.COM<br /><br /># Ticket Granting Service.<br />dn: uid=krbtgt,ou=users,dc=example,dc=com<br />objectclass: top<br />objectclass: person<br />objectclass: inetOrgPerson<br />objectclass: krb5Principal<br />objectclass: krb5KDCEntry<br />cn: KDC Service<br />sn: KDC Service<br />uid: krbtgt<br />userpassword: randomKey<br />krb5PrincipalName: krbtgt/EXAMPLE.COM@EXAMPLE.COM<br /></pre></div><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Apache Directory Studio</span><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhllTRF2fiDZJNHOZlkwqtMHDSUeWhNQDe-zTAj1nT-OBuHuforVJf7MFg4drSsSgBjhyzhZ9nk6iP8KEfHTMu2mjEDEVnfASxmGv97QEXZacjObx58LuOehtYKB6QRX1LBFDg3RXTW9d_x/s1600-h/ldap-con.gif"><img style="cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhllTRF2fiDZJNHOZlkwqtMHDSUeWhNQDe-zTAj1nT-OBuHuforVJf7MFg4drSsSgBjhyzhZ9nk6iP8KEfHTMu2mjEDEVnfASxmGv97QEXZacjObx58LuOehtYKB6QRX1LBFDg3RXTW9d_x/s400/ldap-con.gif" border="0" alt=""id="BLOGGER_PHOTO_ID_5220103339310342002" /></a><br /><br />Studio itself, can be used to connect to any LDAP server. To connect it to Apache Directory Server, in the connections panel (see screenshot), click the <span style="font-weight:bold;"><span style="font-style:italic;">"New Connection..."</span></span> button, and fill out the details as follows:<br /><br /><span style="font-weight:bold;">Hostname:</span> localhost<br /><span style="font-weight:bold;">Port:</span> 10389<br /><span style="font-weight:bold;">Encryption Method:</span> No encryption<br /><br /><span style="font-weight:bold;">Authentication Method:</span> Simple Authentication<br /><span style="font-weight:bold;">Bind DN or user:</span> uid=admin,ou=system<br /><span style="font-weight:bold;">Bind password:</span> secret<br /><br />These will connect you to the server as the admin user.<br /><br />The .ldif file that was loaded above, will have setup the client principal, the server principal, and the krbtgt service. That is, you now have a fully functioning KDC! You can now try running your Kerberos programs against the KDC. Your Kerberos settings should be similar to the following:<br /><br />java.security.krb5.realm=EXAMPLE.COM<br />java.security.krb5.kdc=localhost<br /><br />Apache Directory Server uses a rolling log file for its logging, which is an invaluable source for tracking down error messages and authentication failures. Under the install of the server, go to the <span style="font-weight:bold;"><span style="font-style:italic;">instances\default\log</span></span> folder and use a tailing program to view the log. <br /><br />As you attempt to authenticate against the KDC, you will get exception stack traces and error messages printed here, which make it pretty obvious what the problem with your request was. Also note that you may have to set the rootCategory in the <span style="font-weight:bold;"><span style="font-style:italic;">log4j.properties</span></span> config file under <span style="font-weight:bold;"><span style="font-style:italic;">instances\default\conf</span></span> to DEBUG to get all the information you need (and of course, restart the Apache Directory Server).<br /><br />Most likely, you will get some error messages from the KDC when initially trying to talk to it, so post them here and we can work through them!<br /><br />Ciao.The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com43tag:blogger.com,1999:blog-5374152728206138749.post-59395617331869179632008-07-04T12:07:00.006+12:002008-07-04T12:27:42.055+12:00Adding ant-contrib to Net BeansSince switching to Net Beans, I've found the need to extend the auto-generated build scripts on more than one occasion (by the way these scripts are great, and another awesome advantage Net Beans has over Eclipse). Tasks such as adding targets to generate zip archives of a client or calling Axis 2 generated build scripts seem to pop up a lot with all the SOA stuff I've been working on.<br /><br />One such change recently, required an <span style="font-weight:bold;"><for-each></span> Ant task. To enable foreach, the <span style="font-style:italic;"><span style="font-weight:bold;">ant-contrib</span></span> library needs to be added to the Net Beans Ant setup - as Net Beans comes packaged with its own version of Ant. If you only run your Net Beans build scripts from the command-line, your default Ant needs to be set up with ant-contrib - you don't need to worry about Net Beamns.<br /><br />Every time I install a new version of Net Beans, I have to put ant-contrib into the new install area (I usually install most beta and release candidates of Net Beans, so this is a fairly common exercise). And every time I have to break out a search program so I can remember exactly where the Ant is in the Net Beans install.<br /><br />So, for future reference, its here:<br /><br /><span style="font-style:italic;"><span style="font-weight:bold;">C:\Program Files\NetBeans x.x\java2</span></span><br /><br />Note that the version of the java folder changes in some releases (e.g. Net Beans 6.0 was <span style="font-style:italic;"><span style="font-weight:bold;">java1</span></span>).The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com2tag:blogger.com,1999:blog-5374152728206138749.post-68603643501285105192008-05-18T10:51:00.021+12:002008-07-27T12:44:27.199+12:00How to decrypt a Kerberos GSS AP-REQ service ticket<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoCQbS2Mxz3NfSMlotnb44BXhHnu2c8back9capt4DSehZgQoz7UklB6E-7N_32HuDDA8TrKvB1qU3_oyOQ_UxX1jynWVM907xZLNlukXdqd9oxlO6QqgJuI9rYdyLLhScy1G9GLp1qkE8/s1600-h/three-pirates.png"><img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoCQbS2Mxz3NfSMlotnb44BXhHnu2c8back9capt4DSehZgQoz7UklB6E-7N_32HuDDA8TrKvB1qU3_oyOQ_UxX1jynWVM907xZLNlukXdqd9oxlO6QqgJuI9rYdyLLhScy1G9GLp1qkE8/s400/three-pirates.png" alt="" id="BLOGGER_PHOTO_ID_5206795646867916018" border="0" /></a><br />In a <a href="http://thejavamonkey.blogspot.com/2008/05/hacking-jvm-kerberos-libraries-session.html">previous blog</a>, I covered how to hack the JVM Kerberos/GSS libraries to enable server-side access to the session key. This was a stop-gap solution to get my SOAP Kerberos enabled web service playing nicely with a WSE 3.0 .NET web service. Obviously its pretty dodgy changing the Sun forbidden packages and then re-distributing them with your product. However, when the pressure is on to get a project done, expediency is pretty attractive.<br /><br /><span style="font-weight:bold;">Note: </span>I have subsequently tested this decoder against Active Directory and Apache Directory Server and it works fine against both - using both DES and ARC-FOUR_HMAC encryption.<br /><br />To make up for this oversight in the JVM (no server-side session key access through GSS), we must come up with our own service ticket decrypting algorithm. This is an interesting exercise, working from <a href="http://www.faqs.org/rfcs/rfc1510.html">an RFC</a> it made me feel like I was back at university again. Once again, checking the Sun forums gave me bits and pieces of info, but nowhere near a complete solution.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">A pocketful of acronyms</span><br />The Kerberos v5 protocol uses <a href="http://www.faqs.org/faqs/kerberos-faq/general/section-13.html">ASN.1 and the DER</a> to encode and decode all of the Kerberos<br />protocol messages. ASN.1 or <span style="font-style: italic;">Abstract Syntax Notation One</span> is a way of describing complex types by building the description from simple types. DER or <span style="font-style: italic;">Distinguished Encoding Rules</span> specifies how to encode the ASN.1 notation into a bunch of bytes. Kerberos v5 uses the ASN.1 and DER to encode and decode its messages. Thankfully, there are classes that come with the JVM that we can make use of to achieve our decryption, so we don't really need to worry about ASN.1 or DER too much.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Pseudo-Echo</span><br />The basic outline of the function (for those who are interested in the detail) is as follows:<br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes"><br />1. Decode the service ticket into a set of DER values.<br />2. Get the AP-REQ packet and extract the encrypted ticket from it.<br />3. Get the private key from the server's credentials that matches<br />the encryption type of the ticket.<br />4. Decrypt the ticket using the private key.<br />5. Extract the detail that is needed from the ticket (session key,<br />client principal name etc)<br />6. Bobs your uncle!<br /></pre></div>The code to do this is nice and compact, and fits in well with the previous Kerberos projects I've posted. That is, it is a simple client that requests a service-ticket and writes the result to disk, and a server that reads the ticket from disk, decrypts it and displays the fields from the decrypted ticket. The project includes the usual bat files for running the client and server, and the config property files for the client and server need to be changed to reflect your domain controller. Also note that as we are using the sun.security libraries you have to use a Sun JVM and you will get compile warnings.<br /><br />The actual project binary distribution (see the link below) is all set to run against your KDC/domain controller. All you need to do are:<br /><br /><ol><li>Change client.properties to point to your realm, KDC and contain your client login/password and the service principal name of the service you want to begin a session with.</li><li>Change server.properties to point to your realm, KDC and contain your service principal's password.</li><li>Change jaas.conf to contain your service principal name.</li><li>Run <span style="font-style:italic;">client.bat</span> then run <span style="font-style:italic;">server.bat</span> and it should print out the session key bytes and the client principal name.</li></ol><br /><br /><table id="codeblock"><tbody><tr><td><img src="http://antsbull.googlepages.com/expand.gif" class="codeexpander" onclick="showHideElement('codebody7')" title="display / hide the file KerberosTicketDecoder.java" /></td><td><h2>KerberosTicketDecoder.java</h2></td></tr></tbody></table><br /><div id="codebody"><pre id="codebody7" class="Classes"><a name="1"></a><span id="Package">package</span> javamonkey.app.gss;<br /><a name="2"> </a><br /><a name="3"></a><span id="Import">import</span> java.util.Iterator;<br /><a name="4"></a><span id="Import">import</span> java.util.Set;<br /><a name="5"></a><span id="Import">import</span> javax.security.auth.Subject;<br /><a name="6"></a><span id="Import">import</span> javax.security.auth.kerberos.KerberosKey;<br /><a name="7"></a><span id="Import">import</span> sun.security.krb5.EncryptionKey;<br /><a name="8"></a><span id="Import">import</span> sun.security.krb5.internal.EncTicketPart;<br /><a name="9"></a><span id="Import">import</span> sun.security.krb5.internal.Ticket;<br /><a name="9"></a><span id="Import">import</span> sun.security.krb5.internal.crypto.KeyUsage;<br /><a name="10"></a><span id="Import">import</span> sun.security.util.DerInputStream;<br /><a name="11"></a><span id="Import">import</span> sun.security.util.DerValue;<br /><a name="12"> </a><br /><a name="13"></a><span id="FormalComment">/**<br /><a name="14"></a> * Kerberos Ticket Decoder provides the ability to decode a Kerberos v5 service<br /><a name="15"></a> * ticket, so the session key and client principal name can be accessed.<br /><a name="16"></a> * @author Ants<br /><a name="17"></a> */</span><br /><a name="18"></a><span id="Public">public</span> <span id="Class">class</span> KerberosTicketDecoder {<br /><a name="19"> </a><br /><a name="20"></a> <span id="Private">private</span> <span id="Byte">byte</span>[] serviceTicket;<br /><a name="21"></a> <span id="Private">private</span> Subject subject;<br /><a name="22"></a> <span id="Private">private</span> <span id="Boolean">boolean</span> decoded = <span id="False">false</span>;<br /><a name="23"></a> <span id="Private">private</span> EncryptionKey sessionKey;<br /><a name="24"></a> <span id="Private">private</span> String cname;<br /><a name="25"> </a><br /><a name="26"></a> <span id="FormalComment">/**<br /><a name="27"></a> * Construct a Kerberos Ticket Decoder. This takes the service ticket that is<br /><a name="28"></a> * to be decoded and the JAAS subject that contains the secret key for the<br /><a name="29"></a> * target service.<br /><a name="30"></a> * @param serviceTicket the AP-REQ service ticket that is to be decode<br /><a name="31"></a> * @param subject the JAAS subject containing the secret key for the server<br /><a name="32"></a> * principal<br /><a name="33"></a> */</span><br /><a name="34"></a> <span id="Public">public</span> KerberosTicketDecoder( <span id="Byte">byte</span>[] serviceTicket, Subject subject) {<br /><a name="35"></a> <span id="This">this</span>.serviceTicket = serviceTicket;<br /><a name="36"></a> <span id="This">this</span>.subject = subject;<br /><a name="37"></a> }<br /><a name="38"> </a><br /><a name="39"></a> <span id="FormalComment">/**<br /><a name="40"></a> * Get the client principal name from the decoded service ticket.<br /><a name="41"></a> * @return the client principal name<br /><a name="42"></a> */</span><br /><a name="43"></a> <span id="Public">public</span> String getClientPrincipalName() {<br /><a name="44"></a> <span id="If">if</span> ( !decoded) {<br /><a name="45"></a> decodeServiceTicket();<br /><a name="46"></a> }<br /><a name="47"></a> <span id="Return">return</span> cname;<br /><a name="48"></a> }<br /><a name="49"> </a><br /><a name="50"></a> <span id="FormalComment">/**<br /><a name="51"></a> * Get the session key from the decoded service ticket.<br /><a name="52"></a> * @return the session key<br /><a name="53"></a> */</span><br /><a name="54"></a> <span id="Public">public</span> EncryptionKey getSessionKey() {<br /><a name="55"></a> <span id="If">if</span> ( !decoded) {<br /><a name="56"></a> decodeServiceTicket();<br /><a name="57"></a> }<br /><a name="58"></a> <span id="Return">return</span> sessionKey;<br /><a name="59"></a> }<br /><a name="60"> </a><br /><a name="61"></a> <span id="SingleLineComment">// Decode the service ticket.<br /><a name="62"></a></span> <span id="Private">private</span> <span id="Void">void</span> decodeServiceTicket() {<br /><a name="63"></a> <span id="Try">try</span> {<br /><a name="64"></a> parseServiceTicket( serviceTicket);<br /><a name="65"></a> decoded = <span id="True">true</span>;<br /><a name="66"></a> }<br /><a name="67"></a> <span id="Catch">catch</span> ( Exception e) {<br /><a name="68"></a> e.printStackTrace();<br /><a name="69"></a> }<br /><a name="70"></a> }<br /><a name="71"> </a> <br /><a name="72"></a> <span id="SingleLineComment">// Parses the service ticket (GSS AP-REQ token)<br /><a name="73"></a></span> <span id="Private">private</span> <span id="Void">void</span> parseServiceTicket( <span id="Byte">byte</span>[] ticket) <span id="Throws">throws</span> Exception {<br /><a name="74"></a> DerInputStream ticketStream = <span id="New">new</span> DerInputStream( ticket);<br /><a name="75"></a> DerValue[] values = ticketStream.getSet( ticket.length, <span id="True">true</span>);<br /><a name="76"> </a><br /><a name="77"></a> <span id="SingleLineComment">// Look for the AP_REQ.<br /><a name="78"></a></span> <span id="SingleLineComment">//<br /><a name="79"></a></span> <span id="SingleLineComment">// AP-REQ ::= [APPLICATION 14] SEQUENCE<br /><a name="80"></a></span> <span id="For">for</span> (<span id="Int">int</span> i=<span id="IntegerLiteral">0</span>; i<values.length; i++) {<br /><a name="81"></a> DerValue value = values[i];<br /><a name="82"></a> <span id="If">if</span> ( value.isConstructed((<span id="Byte">byte</span>)<span id="IntegerLiteral">14</span>)) {<br /><a name="83"></a> value.resetTag( DerValue.tag_Set);<br /><a name="84"></a> parseApReq( value.toDerInputStream(), value.length());<br /><a name="85"></a> <span id="Return">return</span>;<br /><a name="86"></a> }<br /><a name="87"></a> }<br /><a name="88"></a> <span id="Throw">throw</span> <span id="New">new</span> Exception( <span id="StringLiteral">"Could not find AP-REQ in service ticket."</span>);<br /><a name="89"></a> } <br /><a name="90"> </a><br /><a name="91"></a> <span id="SingleLineComment">// Parse the GSS AP-REQ token.<br /><a name="92"></a></span> <span id="Private">private</span> <span id="Void">void</span> parseApReq( DerInputStream reqStream, <span id="Int">int</span> len) <span id="Throws">throws</span> Exception {<br /><a name="93"></a> <span id="Byte">byte</span> apOptions = <span id="IntegerLiteral">0</span>;<br /><a name="94"></a> DerValue ticket = <span id="Null">null</span>;<br /><a name="95"> </a><br /><a name="96"></a> DerValue[] values = reqStream.getSet(len, <span id="True">true</span>);<br /><a name="97"> </a><br /><a name="98"></a> <span id="SingleLineComment">//<br /><a name="99"></a></span> <span id="SingleLineComment">// AP-REQ ::= {<br /><a name="100"></a></span> <span id="SingleLineComment">// pvno[0] INTEGER,<br /><a name="101"></a></span> <span id="SingleLineComment">// msg-type[1] INTEGER,<br /><a name="102"></a></span> <span id="SingleLineComment">// ap-options[2] APOptions,<br /><a name="103"></a></span> <span id="SingleLineComment">// ticket[3] Ticket,<br /><a name="104"></a></span> <span id="SingleLineComment">// authenticator[4] EncryptedData<br /><a name="105"></a></span> <span id="SingleLineComment">// }<br /><a name="106"></a></span> <span id="SingleLineComment">//<br /><a name="107"></a></span> <span id="For">for</span> (<span id="Int">int</span> i=<span id="IntegerLiteral">0</span>; i<values.length; i++) {<br /><a name="108"></a> DerValue value = values[i];<br /><a name="109"></a> <span id="If">if</span> ( value.isContextSpecific((<span id="Byte">byte</span>)<span id="IntegerLiteral">2</span>)) {<br /><a name="110"></a> apOptions = value.getData().getDerValue().getBitString()[<span id="IntegerLiteral">0</span>];<br /><a name="111"></a> <span id="SingleLineComment">// apOptions not used yet.<br /><a name="112"></a></span> }<br /><a name="113"></a> <span id="Else">else</span> <span id="If">if</span> ( value.isContextSpecific((<span id="Byte">byte</span>)<span id="IntegerLiteral">3</span>)) {<br /><a name="114"></a> ticket = value.getData().getDerValue();<br /><a name="115"></a> }<br /><a name="116"></a> }<br /><a name="117"> </a><br /><a name="118"></a> <span id="If">if</span> ( ticket == <span id="Null">null</span>) {<br /><a name="119"></a> <span id="Throw">throw</span> <span id="New">new</span> Exception(<span id="StringLiteral">"No Ticket found in AP-REQ PDU"</span>);<br /><a name="120"></a> }<br /><a name="121"></a> decryptTicket( <span id="New">new</span> Ticket(ticket), subject);<br /><a name="122"></a> }<br /><a name="123"> </a><br /><a name="124"></a> <span id="SingleLineComment">// Decrypt the ticket.<br /><a name="125"></a></span> <span id="SingleLineComment">// APOptions ::= BIT STRING {<br /><a name="126"></a></span> <span id="SingleLineComment">// reserved(0),<br /><a name="127"></a></span> <span id="SingleLineComment">// use-session-key(1),<br /><a name="128"></a></span> <span id="SingleLineComment">// mutual-required(2)<br /><a name="129"></a></span> <span id="SingleLineComment">// }<br /><a name="130"></a></span> <span id="SingleLineComment">// Ticket ::= [APPLICATION 1] SEQUENCE {<br /><a name="131"></a></span> <span id="SingleLineComment">// tkt-vno[0] INTEGER,<br /><a name="132"></a></span> <span id="SingleLineComment">// realm[1] Realm,<br /><a name="133"></a></span> <span id="SingleLineComment">// sname[2] PrincipalName,<br /><a name="134"></a></span> <span id="SingleLineComment">// enc-part[3] EncryptedData<br /><a name="135"></a></span> <span id="SingleLineComment">// }<br /><a name="136"></a></span> <span id="SingleLineComment">//<br /><a name="137"></a></span> <span id="SingleLineComment">// EncTicketPart ::= [APPLICATION 3] SEQUENCE {<br /><a name="138"></a></span> <span id="SingleLineComment">// flags[0] TicketFlags,<br /><a name="139"></a></span> <span id="SingleLineComment">// key[1] EncryptionKey,<br /><a name="140"></a></span> <span id="SingleLineComment">// crealm[2] Realm,<br /><a name="141"></a></span> <span id="SingleLineComment">// cname[3] PrincipalName,<br /><a name="142"></a></span> <span id="SingleLineComment">// transited[4] TransitedEncoding,<br /><a name="143"></a></span> <span id="SingleLineComment">// authtime[5] KerberosTime,<br /><a name="144"></a></span> <span id="SingleLineComment">// starttime[6] KerberosTime OPTIONAL,<br /><a name="145"></a></span> <span id="SingleLineComment">// endtime[7] KerberosTime,<br /><a name="146"></a></span> <span id="SingleLineComment">// renew-till[8] KerberosTime OPTIONAL,<br /><a name="147"></a></span> <span id="SingleLineComment">// caddr[9] HostAddresses OPTIONAL,<br /><a name="148"></a></span> <span id="SingleLineComment">// authorization-data[10] AuthorizationData OPTIONAL<br /><a name="149"></a></span> <span id="SingleLineComment">// } <br /><a name="150"></a></span><br /><a name="151"></a> <span id="Private">private</span> <span id="Void">void</span> decryptTicket( Ticket ticket, Subject svrSub)<br /><a name="152"></a> <span id="Throws">throws</span> Exception {<br /><a name="153"></a> System.out.println( <span id="StringLiteral">"key encryption type = "</span> + ticket.encPart.getEType());<br /><a name="154"></a> <span id="SingleLineComment">// Get the private key that matches the encryption type of the ticket.<br /><a name="155"></a></span> EncryptionKey key = getPrivateKey( svrSub, ticket.encPart.getEType());<br /><a name="156"></a> <span id="SingleLineComment">// Decrypt the service ticket and get the cleartext bytes.<br /><a name="157"></a></span> <span id="Byte">byte</span>[] ticketBytes = ticket.encPart.decrypt( key, KeyUsage.KU_TICKET);<br /><a name="158"></a> <span id="If">if</span> ( ticketBytes.length <= <span id="IntegerLiteral">0</span>) {<br /><a name="159"></a> <span id="Throw">throw</span> <span id="New">new</span> Exception( <span id="StringLiteral">"Key is empty."</span>);<br /><a name="160"></a> }<br /><a name="161"></a> <span id="SingleLineComment">// EncTicketPart provides access to the decrypted attributes of the service<br /><a name="162"></a></span> <span id="SingleLineComment">// ticket.<br /><a name="162.b"></a></span> <span id="Byte">byte</span>[] temp = ticket.encPart.reset( ticketBytes, <span id="Boolean">true</span>);<br /><a name="163"></a></span> EncTicketPart encPart = <span id="New">new</span> EncTicketPart( temp);<br /><a name="165"></a> <span id="This">this</span>.sessionKey = encPart.key;<br /><a name="166"></a> <span id="This">this</span>.cname = encPart.cname.toString();<br /><a name="167"></a> }<br /><a name="168"> </a><br /><a name="169"></a> <span id="SingleLineComment">// Get the private server key.<br /><a name="170"></a></span> <span id="Private">private</span> EncryptionKey getPrivateKey( Subject sub, <span id="Int">int</span> keyType)<br /><a name="171"></a> <span id="Throws">throws</span> Exception {<br /><a name="172"></a> KerberosKey key = getKrbKey( sub, keyType);<br /><a name="173"></a> <span id="Return">return</span> <span id="New">new</span> EncryptionKey( key.getEncoded(), key.getKeyType(),<br /><a name="174"></a> <span id="New">new</span> Integer( keyType));<br /><a name="175"></a> }<br /><a name="176"> </a><br /><a name="177"></a> <span id="SingleLineComment">// Get the Kerberos Key from the subject that matches the given key type.<br /><a name="178"></a></span> <span id="Private">private</span> KerberosKey getKrbKey( Subject sub, <span id="Int">int</span> keyType) {<br /><a name="179"></a> Set<Object> creds = sub.getPrivateCredentials(Object.<span id="Class">class</span>);<br /><a name="180"></a> <span id="For">for</span> ( Iterator<Object> i = creds.iterator(); i.hasNext();) {<br /><a name="181"></a> Object cred = i.next();<br /><a name="182"></a> <span id="If">if</span> ( cred <span id="InstanceOf">instanceof</span> KerberosKey) {<br /><a name="183"></a> KerberosKey key = (KerberosKey) cred;<br /><a name="184"></a> <span id="If">if</span> ( key.getKeyType() == keyType) {<br /><a name="185"></a> <span id="Return">return</span> (KerberosKey) cred;<br /><a name="186"></a> }<br /><a name="187"></a> }<br /><a name="188"></a> }<br /><a name="189"></a> <span id="Return">return</span> <span id="Null">null</span>;<br /><a name="190"></a> }<br /><a name="191"> </a><br /><a name="192"></a>}</pre></div><table class="codelink"><tbody><tr><td><a href="http://antsbull.googlepages.com/kerberos-decoding-gss-ticket.zip"><img class="codeimage" src="http://antsbull.googlepages.com/disks.gif" /> Download the NetBeans project and source/binaries here.</a> Let me know if you are having trouble in getting it to work. Also let me know if you test it against the MIT KDC as I don't have one setup.</td></tr></tbody></table>The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com23tag:blogger.com,1999:blog-5374152728206138749.post-11236668347098071212008-05-04T17:40:00.026+12:002008-08-16T13:08:05.750+12:00Hacking the JVM Kerberos Libraries - Session Key Access<br/><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidLV4t4n3xXcmrwDUKyOnrTaYd_GjX1goeJZ_SbIkQjX9MDQoPYKxWMvNJAVGI7c9GfsVzgUtek7v8nI_PEguKSosWsZCHvUUCLvFE-y-UBSM6GpaGRyWnZTOxXSwRxbMqu-6Y4vn0JPNZ/s1600-h/booty.png"><img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidLV4t4n3xXcmrwDUKyOnrTaYd_GjX1goeJZ_SbIkQjX9MDQoPYKxWMvNJAVGI7c9GfsVzgUtek7v8nI_PEguKSosWsZCHvUUCLvFE-y-UBSM6GpaGRyWnZTOxXSwRxbMqu-6Y4vn0JPNZ/s400/booty.png" alt="" id="BLOGGER_PHOTO_ID_5196394818209686242" border="0" /></a>The <a href="http://www.oasis-open.org/committees/download.php/16788/wss-v1.1-spec-os-KerberosTokenProfile.pdf">WS-Security Kerberos Token Profile specification</a> uses the shared session key for a Kerberos session to generate and verify digital messages signatures. In implementing the Kerberos token profile over SOAP I obviously needed to get hold of the session key at both the client to sign the outgoing SOAP message, and at the server to verify the digital signatures contained in the incoming message. This turned out to be a lot more tricky than I expected.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">The Session Key?</span><br />Firstly, the session key is a symmetric key that the KDC gives to the client when it requests a service ticket for a particular service. The service ticket, which is encrypted with the target service's secret key, also includes the session key in it. When the target service decrypts the service ticket, it gets access to the session key. This session key can then be used to encrypt communications between the client and the server from that point on. It is guaranteed that only the client and service identities (in addition to the KDC) know the session key, hence the session is guaranteed to be secure.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Client side pragmatism - The Session Key</span><br />The Java GSS libaries make client-side access to the session key extremely simple. When the service ticket is acquired, by using the <span style="font-style: italic;">initSecContext()</span> method on <span style="font-style: italic;">GSSContext</span>, the GSS API conveniently stores the session key in the subject you are performing the privileged action as. See my <a href="http://thejavamonkey.blogspot.com/2008/04/clientserver-hello-world-in-kerberos.html">previous blog on the basics of the Java GSS/Kerberos APIs</a> if this sounds foreign to you. To access the session key, you simply iterate through the private credentials in the subject and grab it as follows:<br /><br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes"><br /><a name="3"></a> Set<Object> creds = subject.getPrivateCredentials( Object.class);<br /><a name="4"></a> for (Iterator<Object> i = creds.iterator(); i.hasNext();) {<br /><a name="5"></a> Object cred = i.next();<br /><a name="6"></a> <span id="If">if</span> ( cred instanceof KerberosTicket) {<br /><a name="7"></a> KerberosTicket ticket = (KerberosTicket) cred;<br /><a name="8"></a> <span id="SingleLineComment">// If the ticket service principal matches the target service principal,</span><br /><a name="9"></a> <span id="SingleLineComment">// then this is the service ticket (otherwise it is likely</span><br /><a name="10"></a> <span id="SingleLineComment">// to be the krbtgt ticket).</span><br /><a name="11"></a> <span id="If">if</span> ( ticket.getServer().getName().startsWith( servicePrincipalName)) {<br /><a name="12"></a> <span id="Return">return</span> ticket.getSessionKey();<br /><a name="13"></a> }<br /><a name="14"></a> }<br /><a name="15"></a> }<br /></pre></div><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Server side shenanigans - What session key?</span><br />Session key access on the client-side is easy. Unfortunately when they were implementing the GSS libraries in the JVM, Sun decided that developers would not need to access the session key on the server side. That is, when the security context is accepted, you don't get the session key in your credentials or subject. It is impossible to access through the Kerberos and/or GSS APIs in Java.<br /><br />I spent quite a while inspecting the Sun Kerberos and Java APIs to see if the session key was available, but came up short. The next plan of attack was to try the Sun Java forums which was a wild goose chase. The so called experts kept saying, "You don't need the session key on the server, just use the supplied GSS methods for encoding packets". This is a waste of time because it is nothing to do with the WS-Security OASIS spec. My final point of call was to get in contact with the guys at Sun who actually work on the security APIs. They confirmed that the session key is not available, and mentioned that they didn't realise that it would be required.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Hacking the JVM</span><br />Great, so with it already being 9pm at night, and desperately needing to get this stuff wrapped up, I decided to do my own build of the Java Kerberos/GSS library components that I would need to perform Kerberos authentication AND get access to the session key. This ended up being harder than I thought it would, as the Kerberos libraries are in whats known as a <span style="font-style: italic;">forbidden package</span>. That means you can't inherit from them by extending them and the source code is really hard to find on the 'net.<br /><br />To get around this, I eventually found the source code for these classes, then copied them into my own package structure (so there wouldn't be package name clashes), redid all their imports, and changed the lines of code I needed to so that the session key is placed into the subject, during the <span style="font-style: italic;">acceptSecContext()</span> call on the <span style="font-style: italic;">GSSContext</span>. Additionally, the JVM source code I downloaded didn't support the <span style="font-style:italic;">ARCFOUR_HMAC</span> signature algorithm that is available in newer JDKs, so I had to hook that up also.<br /><br />The main change is pretty simple, simply add the key to the subject at the point in time that the subject is being accessed in the <span style="font-style:italic;">GSSContext.acceptSecContext()</span> call. In our case, since our security mechanism is Kerberos v5, this change is made to the <span style="font-style:italic;">Krb5Context</span> class, as follows:<br /><br /><br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes"><br /><A NAME="3"></A><span id="Final">final</span> AccessControlContext acc =<br /><A NAME="3"></A> AccessController.getContext();<br /><A NAME="3"></A><span id="Final">final</span> Subject subject =<br /><A NAME="3"></A> (Subject)AccessController.doPrivileged<br /><A NAME="3"></A> (<span id="New">new</span> java.security.PrivilegedAction() {<br /><A NAME="3"></A> public Object run() {<br /><A NAME="3"></A> <span id="Return">return</span> (Subject.getSubject(acc));<br /><A NAME="3"></A> }<br /><A NAME="3"></A> });<br /><A NAME="3"></A><span id="If">if</span> (subject != null) {<br /><A NAME="3"></A> <font id="SingleLineComment">// Put the session key into the private credentials.</font><br /><A NAME="3"></A> subject.getPrivateCredentials().add( <span id="New">new</span> SecretKeySpec( this.key.getBytes(), <font id="StringLiteral">"DES"</font>));<br /><A NAME="3"></A>}<br /></pre></div><br />The other part is to make sure that the security mechanism provider for our duplicated Kerberos/GSS libraries actually loads our mechanism factory (with its changed package path) instead of the standard Kerberos/GSS factory. This part took me and my debugger a while to figure out. Everytime I was calling <span style="font-style:italic;">acceptSecContext()</span> my code wasn't running and the standard Sun code was. That change is as follows, to the <span style="font-style:italic;">jgss.ProviderList</span> class:<br /><br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes"><br /><A NAME="3"></A><span id="SingleLineComment">// private static final String SPI_MECH_FACTORY_TYPE</span><br /><A NAME="3"></A><span id="SingleLineComment">// = "sun.security.jgss.spi.MechanismFactory";</span><br /><A NAME="3"> </A><br /><A NAME="3"></A><span id="SingleLineComment">// Java monkey version (my new package structure).</span><br /><A NAME="3"></A><font id="Private">private</font> <font id="Static">static</font> <font id="Final">final</font> String SPI_MECH_FACTORY_TYPE<br /><A NAME="3"></A> = <font id="StringLiteral">"kerberos.spi.MechanismFactory";</font><br /></pre></div><br /><br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes"><br /><A NAME="3"></A><font id="Private">private</font> boolean addAllMechsFromProvider( Provider p) {<br /><A NAME="3"></A>......<br /><A NAME="3"></A> Oid mechOid = getOidFromMechFactoryProperty( prop);<br /><A NAME="3"></A> if ( defaultMechFactory == null && DEFAULT_MECH_OID.equals( mechOid)) {<br /><A NAME="3"></A> String className = p.getProperty( prop);<br /><A NAME="3"></A> <span id="SingleLineComment">// Java Monkey switcheroo.</span><br /><A NAME="3"></A> className = <font id="StringLiteral">"kerberos.Krb5MechFactory"</font>;<br /><A NAME="3"></A> defaultMechFactory = getMechFactoryImpl( p, className);<br /><A NAME="3"></A> PreferencesEntry factoryEntry = <span id="New">new</span> PreferencesEntry( p, DEFAULT_MECH_OID);<br /><A NAME="3"></A> factories.put( factoryEntry, defaultMechFactory);<br /><A NAME="3"></A> }<br /></pre></div><br /><br /><br />To use this instead of the standard Sun libraries, instantiate the <span style="font-style:italic;">kerberos.jgss.GSSManagerImpl</span> as follows: <span style="font-style:italic;">GSSManager gssManager = new GSSManagerImpl();<br /></span> Then the result of your <span style="font-style:italic;">acceptSecContext()</span> code is the same, however you get the session key placed in your private credentials (in the subject instance).<br /><table class="codelink"><tbody><tr><td><a href="http://antsbull.googlepages.com/kerberos-session-hack.zip"><img class="codeimage" src="http://antsbull.googlepages.com/disks.gif" /> Download the NetBeans project and source/binaries here.</a> Let me know if you are having trouble in getting it to work. Note that this is a temporary solution to the problem. The ideal solution is to write my own service-ticket decoding algorithm, which will appear in a future blog.</td></tr></tbody></table>The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com4tag:blogger.com,1999:blog-5374152728206138749.post-32464707541542864822008-04-16T08:12:00.020+12:002008-06-30T13:41:30.188+12:00Axis 2 web services with Spring and iBatis inside the AARAt work, our standard software component stack is made up of Spring, iBatis SQL maps and this is generally put into either a servlet, RMI service or a web service. Achieving this inside an Axis 2 AAR web service took a lot of experimentation, to finally nail how to create a completely independent web service that will not tread all over other AAR files in the same container.<br /><br />The reason for this independence is that the web services, which are for different systems for the same client, are released at different points in time and end up using different versions of the overall database schema. More recently released services end up having newer tables, indexes, associations, columns etc. We don't want to have to re-release every web service everytime a new system goes online, so we absolutely need them to be independent of each other.<br /><br />Axis 2 has a cool classloading system that gives you this independence. However, when you throw Spring and iBatis (or to a lesser extent, Hibernate) and such cool features as auto-proxied transactional beans into the mix, you start running into all sorts of problems, as unfortunately these great projects don't seem to share the same classloading philosophies that Axis 2 does. You will be faced with the error "Unable to find SQL map" over and over, no matter how many different tutorials you follow.<br /><br />This blog outlines how I build my Axis 2, Spring and iBatis web services, so that they do remain independent and work out of the box. I'm sure there are many variations to this, so I'll keep adding to this post if people come up with them. By the way this took me <span style="font-weight: bold;">*weeks*</span> to get working in the exact way I needed, so every angle has been considered, just in case you beg to differ.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Web Service Architecture</span><br /><br />The key point in the whole setup seems to be the point at when Spring gets loaded, which in turn causes your iBatis layer to load. This is all to do with classloader shenanigans. Spring *must* be loaded from within the web service, you cannot use Spring to instantiate your Skeleton. Additionally, the SpringAwareService technique, as in the following <a href="http://ws.apache.org/axis2/1_3/spring.html">Apache article</a> does not quite work with iBatis (or in my case anyway).<br /><br />My setup has services that are request scope only, and are stateless. The Spring context is loaded by calling the <span style="font-style: italic;">loadSpring()</span> method from the <span style="font-style: italic;">init()</span> method of the Skeleton as follows:<br /><br /><br /><div id="codebody" style="display: block;"><pre id="codebody-show" class="Classes"><br /><A NAME="53"></A><FONT ID="Public">public</FONT> <FONT ID="Class">class</FONT> ExampleServiceSkeleton <FONT ID="Implements">implements</FONT> <A HREF="../../../../../nz/co/toyota/datahub/webservice/ExampleServiceSkeletonInterface.java.html">ExampleServiceSkeletonInterface</A> {<br /><A NAME="54"> </A><br /><A NAME="58"></A> <FONT ID="Public">public</FONT> <FONT ID="Void">void</FONT> init( ServiceContext context) {<br /><A NAME="59"></A> System.out.println( <FONT ID="StringLiteral">"ExampleServiceSkeleton.init()"</FONT>);<br /><a name="84"></a> <span id="If">if</span> ( applicationImpl == <span id="Null">null</span>) {<br /><A NAME="78"></A> loadSpring();<br /><A NAME="79"></A> }<br /><A NAME="79"></A> }<br /><A NAME="80"> </A><br /><a name="82"></a> <span id="Private">private static</span> <span id="Synchronized">synchronized</span> <span id="Void">void</span> loadSpring() {<br /><a name="83"></a> <span id="Try">try</span> {<br /><a name="84"></a> <span id="If">if</span> ( applicationImpl == <span id="Null">null</span>) {<br /><a name="85"></a> <span id="SingleLineComment">// Load Spring.<br /><a name="86"></a></span> ClassPathXmlApplicationContext appCtx =<br /><a name="87"></a> <span id="New">new</span> ClassPathXmlApplicationContext(<br /> <span id="New">new</span> String[]{ <span id="StringLiteral">"com/example/conf/spring-context.xml"</span>}, <span id="False">false</span>);<br /><a name="88"></a> appCtx.setClassLoader( ExampleServiceSkeleton.<span id="Class">class</span>.getClassLoader());<br /><a name="89"></a> appCtx.refresh();<br /><a name="90"></a> applicationImpl = (ApplicationFacade) appCtx.getBean( <span id="StringLiteral">"applicationImpl"</span>);<br /><a name="92"></a> }<br /><a name="93"></a> }<br /><a name="94"></a> <span id="Catch">catch</span> ( Throwable e) {<br /><a name="95"></a> logger.error( e.getMessage(), e);<br /><a name="96"></a> }<br /><a name="97"></a> }<br /><a name="98"> </a><br /><a name="405"></a> <span id="Private">private</span> <span id="Static">static</span> ApplicationFacade applicationImpl;<br /><a name="98"> </a><br /><a name="85"></a> <span id="SingleLineComment">// Rest of web service snipped.</span><br /><a name="98"> </a><br /><A NAME="406"></A>}<br /></pre></div><br />Note that the <span style="font-style: italic;">applicationImpl</span> is kept as a class level member variable, and its loading is synchronised on the class. The <span style="font-style: italic;">init()</span> method should only call the <span style="font-style: italic;">loadSpring()</span> method if the <span style="font-style: italic;">applicationImpl</span> is unavailable. <br /><br />The impact of performing your Spring configuration via the <span style="font-style:italic;">init()</span> method of your web service is that Spring will <a href="http://en.wikipedia.org/wiki/Lazy_loading">lazily load</a>, instead of loading up front when Axis 2 is initialised. This means that the first request that hits your web service will force your Spring context to load. Subsequent requests to your web service after that will re-use the Spring context, as it is staticly stored on your skeleton.<br /><br />To get the web service itself to be request scope, you should either explicitly define it in the <span style="font-style: italic;">services.xml</span> file, or leave off a scope definition (as it defaults to request level). You <span style="font-weight:bold;">*cannot*</span> use <span style="font-style:italic;"><span style="font-weight:bold;">application</span></span> scope for your web services to get this setup working. Additionally, the service class should be defined using the <span style="font-style: italic;">ServiceClass parameter</span>, not in the top-level <span style="font-style: italic;"><service></span> element. Also note that the <span style="font-style: italic;">composite</span> class loader must be used (as is illustrated below):<br /><br /><br /><br /><div id="codebody" style="display: block;"><pre id="Classes"><br /><serviceGroup><br /><service name="ExampleService-1.0" scope="request"><br /><br /> <description><br /> Example web service<br /> </description><br /><br /> <parameter name="ServiceTCCL" locked="false">composite</parameter><br /> <parameter name="ServiceClass">com.example.ExampleServiceSkeleton</parameter><br /> .....<br /></service><br /></serviceGroup><br /></pre></div><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">AAR Structure</span><br /><br />Your AAR file should include all the iBatis and Spring related jars that it needs. That is, it should have the iBatis SQL Maps jar, the database driver and the Spring framework jar. I found that when any of these were in the <span style="font-style: italic;">WEB-INF/lib</span> folder of Axis 2, that the iBatis resource loader simply cannot find the SQL maps (unless you also place them under WEB-INF. When you build your AAR file, all these jars should live in the <span style="font-style: italic;">lib</span> folder in it. Additionally, it is worth exploding your AAR when you deploy it, instead of dropping the AAR file in - this seems to have some effect on the classloader.<br /><br /><span style="font-weight:bold;">Update:</span> Recently I have been deploying web services with Spring contexts that import other Spring contexts which are inside different jar files. For these to work, I had to have both these Spring contexts in the expanded classes inside the AAR - they do not work when they are in different jars. iBatis SQL Maps loaded by these Spring contexts are fine when inside a jar though.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Axis 2 WEB-INF/lib</span><br /><br />It is fairly important that you don't have Spring, your database driver or iBatis jar library files under the WEB-INF/lib folder of Axis 2. When you do, and you have multiple web services using different versions of the same SQL maps, they start getting loaded using the same classloader, and you end up with them all using the same version of the SQL map. Additionally, I always delete the <span style="font-style: italic;">axis2-spring.jar</span> file from <span style="font-style: italic;">WEB-INF/lib</span>.<br /><br /><span style="font-weight: bold; color: rgb(255, 204, 102);">Log4j Logging</span><br /><br />I've also had just as much fun and games getting multiple web-services to log independently of each other to their own log files, so if anyone is having such difficulties in addition to the above, let me know and I'll extend this blog a bit.The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com10tag:blogger.com,1999:blog-5374152728206138749.post-75851641075254894242008-04-08T21:47:00.018+12:002008-04-13T17:47:41.758+12:00NetBeans 6.1 - seamless switching between org.jdesktop and javax.swing codeThe other day I was putting together a test harness for a project. I decided to try the cool Swing Desktop Application framework that comes with NetBeans 6. An hour later I had a fully working and tested test harness (rapid app development in Java is now possible!). The last part was to build a standalone binary distribution of this and run it the test environment.<br /><br />At this point I hit a major stumbling block. As my NetBeans runs on JDK 1.6, NetBeans created all the group layout GUI code with the GroupLayout <span style="font-style: italic;">javax.swing</span> packages and classes that come by default with Java 1.6. However, the test environment I was going to be running on, only had Java 1.5, and could not be upgraded easily. The GroupLayout code does not work in Java 1.5. I couldn't find much in google on how to switch between the two, and remembered that a year ago I tried to do a similar conversion with find..replace from <span style="font-style: italic;">javax.swing</span> to <span style="font-style: italic;">org.jdesktop</span> which didn't work due to class interface differences.<br /><br />After playing around in Matisse for a while, I worked out how to get NetBeans 6 to do this conversion for you. The best part is it does it quickly and effortlessly.<br /><br />Bring up the GUI class in question, and switch to the Matisse designer view of that class. In the Inspector pane, select the form, right-click on it and click <span style="font-style: italic;">Properties</span> as the following screenshot illustrates:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVGwGw12-TgJtLBCYb6TAXPVpKFm9QGAUhlX8g2enYtLZcCqnwK61bLNRG2B5iP2yivYOL-hMfNnGNsJ4F2ATwrFmPvJoEJqwRkeyaA0-iZxeFdynRoZq3ePUmSsLh55_PAE9p3hoPd9eG/s1600-h/inspector.jpg"><img style="border: 1px dashed rgb(255, 255, 102); padding: 6px; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVGwGw12-TgJtLBCYb6TAXPVpKFm9QGAUhlX8g2enYtLZcCqnwK61bLNRG2B5iP2yivYOL-hMfNnGNsJ4F2ATwrFmPvJoEJqwRkeyaA0-iZxeFdynRoZq3ePUmSsLh55_PAE9p3hoPd9eG/s400/inspector.jpg" alt="" id="BLOGGER_PHOTO_ID_5188598542341696722" border="0" /></a><br /><br />In the resulting properties dialog, there is an option <span style="font-style: italic;">Layout Generation Style</span>, which facilitates the switching from pre-Java 1.6 and Java 1.6. Simply select <span style="font-style: italic;">Swing Layout Extensions Library</span> and NetBeans immediately converts your code. The next screenshot illustrates the options in the aforementioned properties dialog:<br /><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8-M2_DeDtYXnH3XpJiuOKi7Gj1R-7dy-xhDqAMo88tWgezXf3gV9NhUaxS6uN2T-EyUlL47Ouvi3XXko0YI3qPjpQCim0TnS-sIRtjyz_ODXYDZSZY9W2POD8_Uy6EqcU2wcd6WUQDvjp/s1600-h/switcheroo.jpg"><img style="cursor: pointer;border:1px dashed #ffff66;padding:6px" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8-M2_DeDtYXnH3XpJiuOKi7Gj1R-7dy-xhDqAMo88tWgezXf3gV9NhUaxS6uN2T-EyUlL47Ouvi3XXko0YI3qPjpQCim0TnS-sIRtjyz_ODXYDZSZY9W2POD8_Uy6EqcU2wcd6WUQDvjp/s400/switcheroo.jpg" alt="" id="BLOGGER_PHOTO_ID_5188602214538734834" border="0" /></a><br /><br />Sweet!The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com12tag:blogger.com,1999:blog-5374152728206138749.post-45589721658414406002008-04-08T21:47:00.011+12:002008-04-13T17:17:09.112+12:00Clearing out your NetBeans 6.0 class cacheThe other day, I had to delete some generated <a href="http://xmlbeans.apache.org/">XML beans</a> from a NetBeans project, and ended up doing so from Windows file explorer because I was in a hurry. I regenerated the XML beans, and put the new source into the existing NetBeans project and recompiled from the command-line. A core namespace change in my library schema was the reason for this regeneration (on a SOAP interop project with a .NET system).<br /><br />Then when I opened up another NetBeans project that had the XML beans project as a dependency, and attempted to fix the imports, the previous package structure kept getting placed in there by NetBeans, instead of the new package structure.<br /><br />Firstly, I tried to clean and build the XML beans project and then the dependent project. This still gave the same errors.<br /><br />So I fixed the import statements by hand and then rebuilt from the command-line. This worked fine. I then did another build in NetBeans and it worked fine, however the code completion was not working, and trying to do fix imports again resulted in the non-existent packages being put back in my code!<br /><br />After several restarts, clean and build variations and lots of frustration as the clock ticked away, I deleted the NetBeans cache and restarted, and it worked fine!<br /><br />This cache is at:<br /><br />C:\Documents and Settings\username\.netbeans\6.0\var\cacheThe Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com7tag:blogger.com,1999:blog-5374152728206138749.post-55242815769840443132008-04-06T23:01:00.092+12:002008-07-27T12:44:56.897+12:00Client/Server Hello World in Kerberos, Java and GSS!When I first started out with Kerberos and Java, time was running out on a project at work, and it got thrown upon me out of the blue (dreaded scope creep!). At that point I didn't have a clue about how even the Kerberos algorithm worked. The client put me on the spot and said "Can you do Kerberos interop with .NET SOAP services?" - I never back down from a challenge - in fact the satisfaction in completing something like that is one of the best parts of my job. It was like "Yeah, that should take 4-6 days. I'll just write it myself". Underneath this calm exterior I was looking for a big hole to open up and swallow me!<br /><br />Firstly, I checked the web services framework we were using and found that there was no Kerberos support. The best answer I got from the Axis 2 community was "No, this is not supported and we don't know of anyone doing it in Axis 2". So this meant doing everything from the ground on up. The first step was to work out how the heck Kerberos worked, and how to authenticate with it in Java.<br /><br />The first place people end up when on such a quest is usually the <a href="http://java.sun.com/j2se/1.5.0/docs/guide/security/jgss/tutorials/index.html">Sun tutorial on JAAS and GSS</a>. While this tutorial kind've explains the concepts and how to do it, it is hard to get a complete picture of what you're doing. Secondly, the code is a socket based client/server which is not useful at all, as only a lunatic would be writing their own server communications layer in these days of NIO and SOA. The second place is the <a href="http://forum.java.sun.com/forum.jspa?forumID=545">Sun Java forum for Kerberos</a>, which has a couple of smart people who answer a lot of questions, but I found their responses more often than not ended up confusing me.<br /><br />This article is my take on a Kerberos "Hello World" client/server in a more applicable way to modern systems. I changed the concept slightly to suit my needs - that is, it needed to be the foundation for a SOAP inter-operable system. Instead of using socket communications, my code base-64 encodes the Kerberos service ticket and writes it to a file. Subsequently, when the server is run, it opens that file, reads the base-64 string and decodes it to a service ticket which is then used to initiate the Kerberos context/session. When sending Kerberos tickets over SOAP they are base-64 encoded, hence my implementation. Once the security context has been initialised, the server determines the client who made the request and prints "Hello World <client>!".<br /><br />An intended 'feature' of my blogs is that I will include fully working projects and code with them, that should run straight out of the box. Below you can find a downloadable source distribution of my NetBeans project, that includes compiled classes, and .bat files for running the client and server. All that needs to be done before running, is to re-configure the properties files to work with your domain/KDC.<br /><br />The code is fairly straight-forward, and I've commented it heavily. Points to note for newbies are:<br /><br /><span style="font-weight: bold;color:#ffff66">1.</span> JAAS is used to login at both the client and server sides. Once you have successfully logged in via JAAS, you get a <span style="font-weight: bold; font-style: italic;">subject</span> which contains all your secret keys that the KDC gave you after logging in. These keys are used to encrypt and decrypt tickets that are only meant for you. Only you and the KDC know your secret keys, hence only you can decrypt tokens meant for you.<br /><br /><span style="font-weight: bold;color:#ffff66">2.</span> When the JAAS login is performed in the client, the ticket that is retrieved (and placed into your subject) is the TGT (ticket granting ticket). This is required to then subsequently request a service ticket for accessing a service. Additionally, when the server logs in, it gets its own TGT and a set of keys which are used to decode the incoming service ticket.<br /><br /><span style="font-weight: bold;color:#ffff66">3.</span> The call to <span style="font-style:italic;">initSecContext()</span> in the Client is the act of requesting a service ticket. The raw byte array that this returns is the service ticket. This contains the client principal user name and a session key that only the client, server and KDC know about. The ticket is encrypted by the KDC so that only the server can decode it.<br /><br /><span style="font-weight: bold;color:#ffff66">4.</span> The security context initation and acceptance are <span style="font-weight: bold;"><span style="font-style: italic;">privileged actions</span></span>. This means that they can only be performed by the identity with the correct credentials/keys to do so. In the examples, the code <span style="font-style: italic;"><span style="font-weight: bold;">subject.doAs()</span></span> is how this is implemented. The subject that is passed in as a parameter is used to decode whatever token is being accepted or used.<br /><br /><span style="font-weight: bold;color:#ffff66">5.</span> The code:<br /></client><pre class="Classes">krb5Oid = <span id="New">new</span> Oid( <span id="StringLiteral">"1.2.840.113554.1.2.2"</span>);</pre>instructs the Java APIs that the security mechanism you are wanting to use is Kerberos version 5.<br /><br /><span style="font-weight: bold;color:#ffff66">6.</span> In my examples, <span style="font-style: italic;"><span style="font-weight: bold;">requestMutualAuth</span></span> is always turned off, so that there is only one token sent from client to server to initiate and accept a security context. This is different from the Sun tutorial in that many passes can be made. In web service security, it is a big hassle if more than one pass is required to initiate the context, as the SOAP message may be asynchronous or atomic.<br /><br /><span style="font-weight: bold;color:#ffff66">7.</span> The GSS API that you will often see mentioned is the best means of doing Kerberos authentication in Java. GSS stands for <a href="http://en.wikipedia.org/wiki/Generic_Security_Services_Application_Program_Interface">Generic Security Services API</a>. It provides a generic way of performing security/authentication services, that does not tie you down to a specific implementation (e.g. Kerberos). The benefit of using it here, other than being able to use a different security mechanism is that it makes interoperability with .NET extremely simple because .NET also uses GSS. A GSS token (service ticket) generated in Java, can be easily understood by a .NET program, and vice-versa. Web service Kerberos security uses GSS tokens when initiating secured sessions. Note that it is possible to use the actual Kerberos libraries in Java to do authentication, but there is no benefit at all to doing so.<br /><br /><span style="font-weight:bold;color:#ffff66">Configuring the example project</span><br />Before running the project, it needs to be configured to use your domain and KDC correctly. The settings in the included config files are specific to running on my laptop against <a href="http://directory.apache.org/">Apache Directory Server</a>. I have also tested it against a Windows 2003 domain running Active Directory.<br /><br />Firstly, I am assuming that your KDC has been setup correctly. That is, you have a user account, a server account and the latter has a Kerberos service principal setup (see my previous <a href="http://thejavamonkey.blogspot.com/2008/03/active-directory-and-kerberos-service.html">blog</a> on how to do this in Active Directory).<br /><br />The file <span style="font-style:italic;">jaas.conf</span> has to be changed to put the correct service principal in the Server configuration section. This is the Kerberos principal that your service account is mapped to. My example is set to <span style="font-style:italic;">principal="webserver/bully@EXAMPLE.COM";</span> bully is my laptop computer name, and webserver is the name of the service account.<br /><br />The <span style="font-style:italic;">client.properties</span> file needs to be changed to include your realm name (which HAS to be capitalised), the ip address of your KDC, your client account login details and the service account name. The service account name gets converted into a Kerberos principal by the Java libraries, so you don't need to specify it as a Kerberos name here.<br /><br />The <span style="font-style:italic;">server.properties</span> file needs to be changed to specify your KDC, your realm and the password of your service account.<br /><br />You can now run <span style="font-style:italic;">client.bat</span> and then <span style="font-style:italic;">server.bat</span> and see if your Kerberos Hello World application is working. Note that you should be running with at least Java 1.5.07 and it needs to be the Sun JVM. Older JVMs don't support all the encryption types that Kerberos v5 uses, and the Sun JVM specific base-64 libraries are used in my demo.<br /><br /><span style="font-weight:bold;color:#ffff66">Error Messages</span><br /><br /><span style="font-weight:bold;">Identifier doesn't match expected value (906) </span> - this is because your password is wrong, so you couldn't be authenticated against the KDC.<br /><br /><span style="font-weight:bold;">Server not found in Kerberos database</span> - this is either because the service account doesn't exist in your database OR the Kerberos service principal name wasn't recognised by your KDC. Try another SPN mapping or modify the config file to have a different service principal name. If you are using Apache Directory, you can check the server logs for what SPN is actually getting passed through to the KDC.<br /><br /><span style="font-weight:bold;">Integrity check on decrypted field failed</span> - this generally means that <span style="font-weight:bold;">(a)</span> your password failed during the JAAS login to the KDC, or <span style="font-weight:bold;">(b)</span> the ticket you are trying to decode couldn't be decrypted. This is because the ticket is not meant for your server, so it doesn't have the key for decrypting it. In this case, change the service principal that your server is logging in as to resolve this.<br /><br /><span style="font-weight:bold;">Encryption type not supported by KDC</span> - this is generally due to using Active Directory on a Windows 2003 domain. This requires setting a registry entry on both your client and domain machines, <span style="font-style:italic;">allowTGTSessionKey</span>. See the section in the following <a href="http://java.sun.com/j2se/1.5.0/docs/guide/security/jgss/tutorials/Troubleshooting.html">Sun document</a> for the complete guide to doing this. Note that I also have had this error while using Apache Directory server, which was due to my JDK 1.5 not being past version 7 (or thereabouts).<br /><br /><table class="codelink"><tbody><tr><td><a href="http://antsbull.googlepages.com/kerberos-gss-getting-started.zip"><img class="codeimage" src="http://antsbull.googlepages.com/disks.gif" /> Download the NetBeans project and source/binaries here.</a></td></tr></tbody></table><br /><hr /><br /><table id="codeblock"><tr><td><img src="http://antsbull.googlepages.com/expand.gif" class="codeexpander" onclick="showHideElement('codebody1')" title="display / hide the file Client.java" /></td><td><h2>Client.java</h2></td></tr></table><br /><div id="codebody"><pre id="codebody1" class="Classes"><br /><a name="1"></a><span id="Package">package</span> javamonkey.app.gss;<br /><a name="2"> </a><br /><a name="3"></a><span id="Import">import</span> java.io.File;<br /><a name="4"></a><span id="Import">import</span> java.io.FileInputStream;<br /><a name="5"></a><span id="Import">import</span> java.io.FileWriter;<br /><a name="6"></a><span id="Import">import</span> java.io.IOException;<br /><a name="7"></a><span id="Import">import</span> java.security.PrivilegedAction;<br /><a name="8"></a><span id="Import">import</span> java.util.Properties;<br /><a name="9"></a><span id="Import">import</span> javax.security.auth.Subject;<br /><a name="10"></a><span id="Import">import</span> javax.security.auth.login.LoginContext;<br /><a name="11"></a><span id="Import">import</span> javax.security.auth.login.LoginException;<br /><a name="12"></a><span id="Import">import</span> org.ietf.jgss.GSSContext;<br /><a name="13"></a><span id="Import">import</span> org.ietf.jgss.GSSException;<br /><a name="14"></a><span id="Import">import</span> org.ietf.jgss.GSSManager;<br /><a name="15"></a><span id="Import">import</span> org.ietf.jgss.GSSName;<br /><a name="16"></a><span id="Import">import</span> org.ietf.jgss.Oid;<br /><a name="17"></a><span id="Import">import</span> sun.misc.BASE64Encoder;<br /><a name="18"> </a><br /><a name="19"></a><span id="FormalComment">/**<br /><a name="20"></a> * <p>Client logs in to a Key Distribution Center (KDC) using JAAS and then<br /><a name="21"></a> * requests a service ticket for the server, base 64 encodes it and writes it<br /><a name="22"></a> * to the file <i>service-ticket.txt</i>.</p><br /><a name="23"></a> * <p>This class, in combination with the <i>Server</i> class illustrates the<br /><a name="24"></a> * use of the JAAS and GSS APIs for initiating a security context using the<br /><a name="25"></a> * Kerberos protocol.</p><br /><a name="26"></a> * <p>This requires a KDC/domain controller such as Active Directory or Apache<br /><a name="27"></a> * Directory. The KDC configuration details are stored in the<br /><a name="28"></a> * <i>client.properties</i> file, while the JAAS details are stored in the<br /><a name="29"></a> * file <i>jaas.conf</i>.</p><br /><a name="30"></a> * @author Ants<br /><a name="31"></a> */</span><br /><a name="32"></a><span id="Public">public</span> <span id="Class">class</span> Client {<br /><a name="33"> </a><br /><a name="34"></a> <span id="Public">public</span> <span id="Static">static</span> <span id="Void">void</span> main( String[] args) {<br /><a name="35"></a> <span id="Try">try</span> {<br /><a name="36"></a> <span id="SingleLineComment">// Setup up the Kerberos properties.<br /><a name="37"></a></span> Properties props = <span id="New">new</span> Properties();<br /><a name="38"></a> props.load( <span id="New">new</span> FileInputStream( <span id="StringLiteral">"client.properties"</span>));<br /><a name="39"></a> System.setProperty( <span id="StringLiteral">"sun.security.krb5.debug"</span>, <span id="StringLiteral">"true"</span>);<br /><a name="40"></a> System.setProperty( <span id="StringLiteral">"java.security.krb5.realm"</span>, props.getProperty( <span id="StringLiteral">"realm"</span>)); <span id="SingleLineComment"><br /><a name="41"></a></span> System.setProperty( <span id="StringLiteral">"java.security.krb5.kdc"</span>, props.getProperty( <span id="StringLiteral">"kdc"</span>));<br /><a name="42"></a> System.setProperty( <span id="StringLiteral">"java.security.auth.login.config"</span>, <span id="StringLiteral">"./jaas.conf"</span>);<br /><a name="43"></a> System.setProperty( <span id="StringLiteral">"javax.security.auth.useSubjectCredsOnly"</span>, <span id="StringLiteral">"true"</span>);<br /><a name="44"></a> String username = props.getProperty( <span id="StringLiteral">"client.principal.name"</span>);<br /><a name="45"></a> String password = props.getProperty( <span id="StringLiteral">"client.password"</span>);<br /><a name="46"></a> <span id="SingleLineComment">// Oid mechanism = use Kerberos V5 as the security mechanism.<br /><a name="47"></a></span> krb5Oid = <span id="New">new</span> Oid( <span id="StringLiteral">"1.2.840.113554.1.2.2"</span>);<br /><a name="48"></a> <a href="http://www.blogger.com/javamonkey/app/gss/Client.java.html">Client</a> client = <span id="New">new</span> <a href="http://www.blogger.com/javamonkey/app/gss/Client.java.html">Client</a>();<br /><a name="49"></a> <span id="SingleLineComment">// Login to the KDC.<br /><a name="50"></a></span> client.login( username, password);<br /><a name="51"></a> <span id="SingleLineComment">// Request the service ticket.<br /><a name="52"></a></span> client.initiateSecurityContext( props.getProperty( <span id="StringLiteral">"service.principal.name"</span>));<br /><a name="53"></a> <span id="SingleLineComment">// Write the ticket to disk for the server to read.<br /><a name="54"></a></span> encodeAndWriteTicketToDisk( client.serviceTicket, <span id="StringLiteral">"./security.token"</span>);<br /><a name="55"></a> System.out.println( <span id="StringLiteral">"Service ticket encoded to disk successfully"</span>);<br /><a name="56"></a> }<br /><a name="57"></a> <span id="Catch">catch</span> ( LoginException e) {<br /><a name="58"></a> e.printStackTrace();<br /><a name="59"></a> System.err.println( <span id="StringLiteral">"There was an error during the JAAS login"</span>);<br /><a name="60"></a> System.exit( -<span id="IntegerLiteral">1</span>);<br /><a name="61"></a> }<br /><a name="62"></a> <span id="Catch">catch</span> ( GSSException e) {<br /><a name="63"></a> e.printStackTrace();<br /><a name="64"></a> System.err.println( <span id="StringLiteral">"There was an error during the security context initiation"</span>);<br /><a name="65"></a> System.exit( -<span id="IntegerLiteral">1</span>);<br /><a name="66"></a> }<br /><a name="67"></a> <span id="Catch">catch</span> ( IOException e) {<br /><a name="68"></a> e.printStackTrace();<br /><a name="69"></a> System.err.println( <span id="StringLiteral">"There was an IO error"</span>);<br /><a name="70"></a> System.exit( -<span id="IntegerLiteral">1</span>);<br /><a name="71"></a> }<br /><a name="72"></a> }<br /><a name="73"> </a><br /><a name="74"></a> <span id="Public">public</span> Client() {<br /><a name="75"></a> <span id="Super">super</span>();<br /><a name="76"></a> }<br /><a name="77"> </a><br /><a name="78"></a> <span id="Private">private</span> <span id="Static">static</span> Oid krb5Oid;<br /><a name="79"> </a><br /><a name="80"></a> <span id="Private">private</span> Subject subject;<br /><a name="81"></a> <span id="Private">private</span> <span id="Byte">byte</span>[] serviceTicket;<br /><a name="82"> </a><br /><a name="83"></a> <span id="SingleLineComment">// Authenticate against the KDC using JAAS.<br /><a name="84"></a></span> <span id="Private">private</span> <span id="Void">void</span> login( String username, String password) <span id="Throws">throws</span> LoginException {<br /><a name="85"></a> LoginContext loginCtx = <span id="Null">null</span>;<br /><a name="86"></a> <span id="SingleLineComment">// "Client" references the JAAS configuration in the jaas.conf file.<br /><a name="87"></a></span> loginCtx = <span id="New">new</span> LoginContext( <span id="StringLiteral">"Client"</span>,<br /><a name="88"></a> <span id="New">new</span> <a href="http://www.blogger.com/javamonkey/app/gss/LoginCallbackHandler.java.html">LoginCallbackHandler</a>( username, password));<br /><a name="89"></a> loginCtx.login();<br /><a name="90"></a> <span id="This">this</span>.subject = loginCtx.getSubject();<br /><a name="91"></a> }<br /><a name="92"> </a><br /><a name="93"></a> <span id="SingleLineComment">// Begin the initiation of a security context with the target service.<br /><a name="94"></a></span> <span id="Private">private</span> <span id="Void">void</span> initiateSecurityContext( String servicePrincipalName)<br /><a name="95"></a> <span id="Throws">throws</span> GSSException {<br /><a name="96"></a> GSSManager manager = GSSManager.getInstance();<br /><a name="97"></a> GSSName serverName = manager.createName( servicePrincipalName,<br /><a name="98"></a> GSSName.NT_HOSTBASED_SERVICE);<br /><a name="99"></a> <span id="Final">final</span> GSSContext context = manager.createContext( serverName, krb5Oid, <span id="Null">null</span>,<br /><a name="100"></a> GSSContext.DEFAULT_LIFETIME);<br /><a name="101"></a> <span id="SingleLineComment">// The GSS context initiation has to be performed as a privileged action.<br /><a name="102"></a></span> <span id="This">this</span>.serviceTicket = Subject.doAs( subject, <span id="New">new</span> PrivilegedAction<<span id="Byte">byte</span>[]>() {<br /><a name="103"></a> <span id="Public">public</span> <span id="Byte">byte</span>[] run() {<br /><a name="104"></a> <span id="Try">try</span> {<br /><a name="105"></a> <span id="Byte">byte</span>[] token = <span id="New">new</span> <span id="Byte">byte</span>[<span id="IntegerLiteral">0</span>];<br /><a name="106"></a> <span id="SingleLineComment">// This is a one pass context initialisation.<br /><a name="107"></a></span> context.requestMutualAuth( <span id="False">false</span>);<br /><a name="108"></a> context.requestCredDeleg( <span id="False">false</span>);<br /><a name="109"></a> <span id="Return">return</span> context.initSecContext( token, <span id="IntegerLiteral">0</span>, token.length);<br /><a name="110"></a> }<br /><a name="111"></a> <span id="Catch">catch</span> ( GSSException e) {<br /><a name="112"></a> e.printStackTrace();<br /><a name="113"></a> <span id="Return">return</span> <span id="Null">null</span>;<br /><a name="114"></a> }<br /><a name="115"></a> }<br /><a name="116"></a> });<br /><a name="117"> </a><br /><a name="118"></a> }<br /><a name="119"> </a><br /><a name="120"></a> <span id="SingleLineComment">// Base64 encode the raw ticket and write it to the given file.<br /><a name="121"></a></span> <span id="Private">private</span> <span id="Static">static</span> <span id="Void">void</span> encodeAndWriteTicketToDisk( <span id="Byte">byte</span>[] ticket, String filepath)<br /><a name="122"></a> <span id="Throws">throws</span> IOException {<br /><a name="123"></a> BASE64Encoder encoder = <span id="New">new</span> BASE64Encoder(); <br /><a name="124"></a> FileWriter writer = <span id="New">new</span> FileWriter( <span id="New">new</span> File( filepath));<br /><a name="125"></a> String encodedToken = encoder.encode( ticket);<br /><a name="126"></a> writer.write( encodedToken);<br /><a name="127"></a> writer.close();<br /><a name="128"></a> }<br /><a name="129"></a>}<br /><a name="130"></a></pre></div><br /><table id="codeblock" style="top: -30px;"><tbody><tr><td><img src="http://antsbull.googlepages.com/expand.gif" class="codeexpander" onclick="showHideElement('codebody2')" title="display / hide the file LoginCallbackHandler.java" /></td><td><h2>LoginCallbackHandler.java</h2></td></tr></tbody></table><div id="codebody"><pre id="codebody2" class="Classes"><br /><a name="1"></a><span id="Package">package</span> javamonkey.app.gss;<br /><a name="2"> </a><br /><a name="3"></a><span id="Import">import</span> java.io.IOException;<br /><a name="4"></a><span id="Import">import</span> javax.security.auth.callback.Callback;<br /><a name="5"></a><span id="Import">import</span> javax.security.auth.callback.CallbackHandler;<br /><a name="6"></a><span id="Import">import</span> javax.security.auth.callback.NameCallback;<br /><a name="7"></a><span id="Import">import</span> javax.security.auth.callback.PasswordCallback;<br /><a name="8"></a><span id="Import">import</span> javax.security.auth.callback.UnsupportedCallbackException;<br /><a name="9"> </a><br /><a name="10"></a><span id="FormalComment">/**<br /><a name="11"></a> * Password callback handler for resolving password/usernames for a JAAS login.<br /><a name="12"></a> * @author Ants<br /><a name="13"></a> */</span><br /><a name="14"></a><span id="Public">public</span> <span id="Class">class</span> LoginCallbackHandler <span id="Implements">implements</span> CallbackHandler {<br /><a name="15"></a><br /><a name="16"></a> <span id="Public">public</span> LoginCallbackHandler() {<br /><a name="17"></a> <span id="Super">super</span>();<br /><a name="18"></a> }<br /><a name="19"> </a><br /><a name="20"></a> <span id="Public">public</span> LoginCallbackHandler( String name, String password) {<br /><a name="21"></a> <span id="Super">super</span>();<br /><a name="22"></a> <span id="This">this</span>.username = name;<br /><a name="23"></a> <span id="This">this</span>.password = password;<br /><a name="24"></a> }<br /><a name="25"> </a><br /><a name="26"></a> <span id="Public">public</span> LoginCallbackHandler( String password) {<br /><a name="27"></a> <span id="Super">super</span>();<br /><a name="28"></a> <span id="This">this</span>.password = password;<br /><a name="29"></a> }<br /><a name="30"> </a><br /><a name="31"></a> <span id="Private">private</span> String password;<br /><a name="32"></a> <span id="Private">private</span> String username;<br /><a name="33"> </a><br /><a name="34"></a> <span id="FormalComment">/**<br /><a name="35"></a> * Handles the callbacks, and sets the user/password detail.<br /><a name="36"></a> * @param callbacks the callbacks to handle<br /><a name="37"></a> * @throws IOException if an input or output error occurs.<br /><a name="38"></a> */</span><br /><a name="39"></a> <span id="Public">public</span> <span id="Void">void</span> handle( Callback[] callbacks)<br /><a name="40"></a> <span id="Throws">throws</span> IOException, UnsupportedCallbackException {<br /><a name="41"> </a><br /><a name="42"></a> <span id="For">for</span> ( <span id="Int">int</span> i=<span id="IntegerLiteral">0</span>; i<callbacks.length; i++) {<br /><a name="43"></a> <span id="If">if</span> ( callbacks[i] <span id="InstanceOf">instanceof</span> NameCallback && username != <span id="Null">null</span>) {<br /><a name="44"></a> NameCallback nc = (NameCallback) callbacks[i];<br /><a name="45"></a> nc.setName( username);<br /><a name="46"></a> }<br /><a name="47"></a> <span id="Else">else</span> <span id="If">if</span> ( callbacks[i] <span id="InstanceOf">instanceof</span> PasswordCallback) {<br /><a name="48"></a> PasswordCallback pc = (PasswordCallback) callbacks[i];<br /><a name="49"></a> pc.setPassword( password.toCharArray());<br /><a name="50"></a> }<br /><a name="51"></a> <span id="Else">else</span> {<br /><a name="52"></a> <span id="MultiLineComment">/*throw new UnsupportedCallbackException(<br /><a name="53"></a> callbacks[i], "Unrecognized Callback");*/</span><br /><a name="54"></a> }<br /><a name="55"></a> }<br /><a name="56"></a> }<br /><a name="57"> </a><br /><a name="59"></a>}<br /><a name="60"> </a><br /><a name="61"></a></pre></div><br /><table id="codeblock"><tbody><tr><td><img src="http://antsbull.googlepages.com/expand.gif" class="codeexpander" onclick="showHideElement('codebody3')" title="display / hide the file Server.java" /></td><td><h2>Server.java</h2></td></tr></tbody></table><br /><div id="codebody"><pre id="codebody3" class="Classes"><br /><a name="1"></a><span id="Package">package</span> javamonkey.app.gss;<br /><a name="2"> </a><br /><a name="3"></a><span id="Import">import</span> java.io.BufferedReader;<br /><a name="4"></a><span id="Import">import</span> java.io.File;<br /><a name="5"></a><span id="Import">import</span> java.io.FileInputStream;<br /><a name="6"></a><span id="Import">import</span> java.io.FileReader;<br /><a name="7"></a><span id="Import">import</span> java.io.IOException;<br /><a name="8"></a><span id="Import">import</span> java.security.PrivilegedAction;<br /><a name="9"></a><span id="Import">import</span> java.util.Properties;<br /><a name="10"></a><span id="Import">import</span> javax.security.auth.Subject;<br /><a name="11"></a><span id="Import">import</span> javax.security.auth.login.LoginContext;<br /><a name="12"></a><span id="Import">import</span> javax.security.auth.login.LoginException;<br /><a name="13"></a><span id="Import">import</span> org.ietf.jgss.GSSContext;<br /><a name="14"></a><span id="Import">import</span> org.ietf.jgss.GSSCredential;<br /><a name="15"></a><span id="Import">import</span> org.ietf.jgss.GSSException;<br /><a name="16"></a><span id="Import">import</span> org.ietf.jgss.GSSManager;<br /><a name="17"></a><span id="Import">import</span> org.ietf.jgss.GSSName;<br /><a name="18"></a><span id="Import">import</span> org.ietf.jgss.Oid;<br /><a name="19"></a><span id="Import">import</span> sun.misc.BASE64Decoder;<br /><a name="20"> </a><br /><a name="21"></a><span id="FormalComment">/**<br /><a name="22"></a> * <p>Server logs in to a Key Distribution Center (KDC) using JAAS and then<br /><a name="23"></a> * reads the encoded service ticket from the file <i>service-ticket.txt</i>.<br /><a name="24"></a> * This ticket is decoded into a byte array and the security context is<br /><a name="25"></a> * attempted to be accepted, using the GSS API.</p><br /><a name="26"></a> * <p>This class, in combination with the <i>Client</i> class illustrates the<br /><a name="27"></a> * use of the JAAS and GSS APIs for initiating a security context using the<br /><a name="28"></a> * Kerberos protocol.</p><br /><a name="29"></a> * <p>This requires a KDC/domain controller such as Active Directory or Apache<br /><a name="30"></a> * Directory. The KDC configuration details are stored in the<br /><a name="31"></a> * <i>server.properties</i> file, while the JAAS details are stored in the<br /><a name="32"></a> * file <i>jaas.conf</i>.</p><br /><a name="33"></a> * @author Ants<br /><a name="34"></a> */</span><br /><a name="35"></a><span id="Public">public</span> <span id="Class">class</span> Server {<br /><a name="36"> </a><br /><a name="37"></a> <span id="Public">public</span> <span id="Static">static</span> <span id="Void">void</span> main( String[] args) {<br /><a name="38"></a> <span id="Try">try</span> {<br /><a name="39"></a> <span id="SingleLineComment">// Setup up the Kerberos properties.<br /><a name="40"></a></span> Properties props = <span id="New">new</span> Properties();<br /><a name="41"></a> props.load( <span id="New">new</span> FileInputStream( <span id="StringLiteral">"server.properties"</span>));<br /><a name="42"></a> System.setProperty( <span id="StringLiteral">"sun.security.krb5.debug"</span>, <span id="StringLiteral">"true"</span>);<br /><a name="43"></a> System.setProperty( <span id="StringLiteral">"java.security.krb5.realm"</span>, props.getProperty( <span id="StringLiteral">"realm"</span>));<br /><a name="44"></a> System.setProperty( <span id="StringLiteral">"java.security.krb5.kdc"</span>, props.getProperty( <span id="StringLiteral">"kdc"</span>));<br /><a name="45"></a> System.setProperty( <span id="StringLiteral">"java.security.auth.login.config"</span>, <span id="StringLiteral">"./jaas.conf"</span>);<br /><a name="46"></a> System.setProperty( <span id="StringLiteral">"javax.security.auth.useSubjectCredsOnly"</span>, <span id="StringLiteral">"true"</span>);<br /><a name="47"></a> String password = props.getProperty( <span id="StringLiteral">"service.password"</span>);<br /><a name="48"></a> <span id="SingleLineComment">// Oid mechanism = use Kerberos V5 as the security mechanism.<br /><a name="49"></a></span> krb5Oid = <span id="New">new</span> Oid( <span id="StringLiteral">"1.2.840.113554.1.2.2"</span>);<br /><a name="50"></a> <a href="http://www.blogger.com/javamonkey/app/gss/Server.java.html">Server</a> server = <span id="New">new</span> <a href="http://www.blogger.com/javamonkey/app/gss/Server.java.html">Server</a>();<br /><a name="51"></a> <span id="SingleLineComment">// Login to the KDC.<br /><a name="52"></a></span> server.login( password);<br /><a name="53"></a> <span id="Byte">byte</span> serviceTicket[] = loadTokenFromDisk();<br /><a name="54"></a> <span id="SingleLineComment">// Request the service ticket.<br /><a name="55"></a></span> String clientName = server.acceptSecurityContext( serviceTicket);<br /><a name="56"></a> System.out.println( <span id="StringLiteral">"\nSecurity context successfully initialised!"</span>);<br /><a name="57"></a> System.out.println( <span id="StringLiteral">"\nHello World "</span> + clientName + <span id="StringLiteral">"!"</span>);<br /><a name="58"></a> }<br /><a name="59"></a> <span id="Catch">catch</span> ( LoginException e) {<br /><a name="60"></a> e.printStackTrace();<br /><a name="61"></a> System.err.println( <span id="StringLiteral">"There was an error during the JAAS login"</span>);<br /><a name="62"></a> System.exit( -<span id="IntegerLiteral">1</span>);<br /><a name="63"></a> }<br /><a name="64"></a> <span id="Catch">catch</span> ( GSSException e) {<br /><a name="65"></a> e.printStackTrace();<br /><a name="66"></a> System.err.println( <span id="StringLiteral">"There was an error during the security context acceptance"</span>);<br /><a name="67"></a> System.exit( -<span id="IntegerLiteral">1</span>);<br /><a name="68"></a> }<br /><a name="69"></a> <span id="Catch">catch</span> ( IOException e) {<br /><a name="70"></a> e.printStackTrace();<br /><a name="71"></a> System.err.println( <span id="StringLiteral">"There was an IO error"</span>);<br /><a name="72"></a> System.exit( -<span id="IntegerLiteral">1</span>);<br /><a name="73"></a> }<br /><a name="74"></a> }<br /><a name="75"> </a><br /><a name="76"></a> <span id="SingleLineComment">// Load the security token from disk and decode it. Return the raw GSS token.<br /><a name="77"></a></span> <span id="Private">private</span> <span id="Static">static</span> <span id="Byte">byte</span>[] loadTokenFromDisk() <span id="Throws">throws</span> IOException {<br /><a name="78"></a> BufferedReader in = <span id="New">new</span> BufferedReader( <span id="New">new</span> FileReader( <span id="StringLiteral">"security.token"</span>));<br /><a name="79"></a> System.out.println( <span id="New">new</span> File( <span id="StringLiteral">"security.token"</span>).getAbsolutePath());<br /><a name="80"></a> String str;<br /><a name="81"></a> StringBuffer buffer = <span id="New">new</span> StringBuffer();<br /><a name="82"></a> <span id="While">while</span> ((str = in.readLine()) != <span id="Null">null</span>) {<br /><a name="83"></a> buffer.append( str + <span id="StringLiteral">"\n"</span>);<br /><a name="84"></a> }<br /><a name="85"></a> in.close();<br /><a name="86"></a> <span id="SingleLineComment">//System.out.println( buffer.toString());<br /><a name="87"></a></span> BASE64Decoder decoder = <span id="New">new</span> BASE64Decoder();<br /><a name="88"></a> <span id="Return">return</span> decoder.decodeBuffer( buffer.toString());<br /><a name="89"></a> }<br /><a name="90"> </a><br /><a name="91"></a> <span id="Private">private</span> <span id="Static">static</span> Oid krb5Oid;<br /><a name="92"> </a><br /><a name="93"></a> <span id="Private">private</span> Subject subject;<br /><a name="94"> </a><br /><a name="95"></a> <span id="SingleLineComment">// Authenticate against the KDC using JAAS.<br /><a name="96"></a></span> <span id="Private">private</span> <span id="Void">void</span> login( String password) <span id="Throws">throws</span> LoginException {<br /><a name="97"></a> LoginContext loginCtx = <span id="Null">null</span>;<br /><a name="98"></a> <span id="SingleLineComment">// "Client" references the JAAS configuration in the jaas.conf file.<br /><a name="99"></a></span> loginCtx = <span id="New">new</span> LoginContext( <span id="StringLiteral">"Server"</span>,<br /><a name="100"></a> <span id="New">new</span> <a href="http://www.blogger.com/javamonkey/app/gss/LoginCallbackHandler.java.html">LoginCallbackHandler</a>( password));<br /><a name="101"></a> loginCtx.login();<br /><a name="102"></a> <span id="This">this</span>.subject = loginCtx.getSubject();<br /><a name="103"></a> }<br /><a name="104"> </a><br /><a name="105"></a> <span id="SingleLineComment">// Completes the security context initialisation and returns the client name.<br /><a name="106"></a></span> <span id="Private">private</span> String acceptSecurityContext( <span id="Final">final</span> <span id="Byte">byte</span>[] serviceTicket)<br /><a name="107"></a> <span id="Throws">throws</span> GSSException {<br /><a name="108"></a> krb5Oid = <span id="New">new</span> Oid( <span id="StringLiteral">"1.2.840.113554.1.2.2"</span>);<br /><a name="109"> </a><br /><a name="110"></a> <span id="SingleLineComment">// Accept the context and return the client principal name.<br /><a name="111"></a></span> <span id="Return">return</span> Subject.doAs( subject, <span id="New">new</span> PrivilegedAction<String>() {<br /><a name="112"></a> <span id="Public">public</span> String run() {<br /><a name="113"></a> <span id="Try">try</span> {<br /><a name="114"></a> <span id="SingleLineComment">// Identify the server that communications are being made to.<br /><a name="115"></a></span> GSSManager manager = GSSManager.getInstance();<br /><a name="116"></a> GSSContext context = manager.createContext( (GSSCredential) <span id="Null">null</span>);<br /><a name="117"></a> context.acceptSecContext( serviceTicket, <span id="IntegerLiteral">0</span>, serviceTicket.length);<br /><a name="118"></a> <span id="Return">return</span> context.getSrcName().toString();<br /><a name="119"></a> }<br /><a name="120"></a> <span id="Catch">catch</span> ( Exception e) {<br /><a name="121"></a> e.printStackTrace();<br /><a name="122"></a> <span id="Return">return</span> <span id="Null">null</span>;<br /><a name="123"></a> }<br /><a name="124"></a> }<br /><a name="125"></a> });<br /><a name="126"></a> }<br /><a name="127"></a>}<br /><a name="128"></a></pre></div><br /><table id="codeblock"><tbody><tr><td><img src="http://antsbull.googlepages.com/expand.gif" class="codeexpander" onclick="showHideElement('codebody4')" title="display / hide the config file jaas.conf" /></td><td><h2>jaas.conf</h2></td></tr></tbody></table><br /><div id="codebody"><pre id="codebody4" class="Classes"><br /><br />Client {<br />com.sun.security.auth.module.Krb5LoginModule required<br />useTicketCache=false;<br />};<br /><br />Server {<br />com.sun.security.auth.module.Krb5LoginModule required<br />useKeyTab=false<br />storeKey=true<br />useTicketCache=false<br />principal="webserver/bully@EXAMPLE.COM";<br />};<br /></pre></div><br /><table id="codeblock"><tbody><tr><td><img src="http://antsbull.googlepages.com/expand.gif" class="codeexpander" onclick="showHideElement('codebody5')" title="display / hide the file client.properties" /></td><td><h2>client.properties</h2></td></tr></tbody></table><br /><div id="codebody"><pre id="codebody5" class="Classes"><br />realm=EXAMPLE.COM<br />kdc=127.0.0.1<br />client.principal.name=anthony<br />client.password=Password99<br />service.principal.name=webserver<br /></pre></div><br /><table id="codeblock"><tbody><tr><td><img src="http://antsbull.googlepages.com/expand.gif" class="codeexpander" onclick="showHideElement('codebody6')" title="display / hide the file server.properties" /></td><td><h2>server.properties</h2></td></tr></tbody></table><br /><div id="codebody"><pre id="codebody6" class="Classes"><br />realm=EXAMPLE.COM<br />kdc=localhost<br />service.password=Password99<br /></pre></div>The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com56tag:blogger.com,1999:blog-5374152728206138749.post-80810166588948680692008-04-04T08:40:00.005+13:002008-04-04T09:13:51.452+13:00Axis 2 Client Side - Getting the Message ContextI have done nothing but <a href="http://ws.apache.org/axis2/">Apache Axis 2</a> SOAP service/client development for the last year and a half. In the shop that I work in, we are using most of the advanced features of Axis 2, including JMS transportation, Spring injected services, proxy-aware transactional web services running inside Axis 2 service archives, iBatis mapped services and Kerberos, username token and public-key secured interoperable services/clients that interop with Microsoft WSE3, Delphi and other web services.<br /><br />Axis 2 has some really strong points - It has a great community backing, it evolved from the Axis 1 project and hence is mature, it fully supports most of the W3C web service standards, it supports many different XML data-bindings and its architecture is really nice (and hence, easy to modify).<br /><br />It is also really configurable, and all its features can be accessed through your own code. While this is really good, it also means that when you need to do something that is not quite out-of-the box, you have to search around for how to do it or perhaps even do your own build of it. In my opinion this is great, as it has allowed me to truly understand what SOAP, web services, security, transports and interoperability are really about - which is what I love to do - knowledge is the key to becoming a better developer and gives me real confidence to debate architectures and technologies with technical architects and the like.<br /><br />Along the way, I have learnt a lot of tricks and techniques that every self-respecting Axis 2<br />developer should have in their toolbox, so I'm going to post them here for the benefit of everyone.<br /><br />The first one that is more than useful, is getting hold of the MessageContext object. This gives you access to the security headers, such as the username token and binary tokens, as well as the ability to print out the entire message to a log.<br /><br />Server-side, this is very easy, simply call a static method on MessageContext:<br /><br /><i>MessageContext.getCurrentMessageContext();</i><br /><br />Client side, this returns you null. You have to use the following code to get hold of it at the client:<br /><br /><i>stub._getServiceContext().getCurrentOperationContext().getMessageContext("In");</i>The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com10tag:blogger.com,1999:blog-5374152728206138749.post-86681944731261843472008-03-29T11:24:00.029+13:002008-04-16T10:27:19.189+12:00Active Directory and Kerberos Service Principal Names<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_GhMZpYf2UgXgfsZoOrmAaHeqFAyixaDGyPdtzNJ21rvI0cSPrF3MmoM4uZOIfE7Hg35O7narSVj60NTsr5Rcg3K4Oy2GAgAtRAVRooiW32LK66HXhKNUWONW6ESznXbvSlPVjqElMH1-/s1600-h/padlock.gif"><img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_GhMZpYf2UgXgfsZoOrmAaHeqFAyixaDGyPdtzNJ21rvI0cSPrF3MmoM4uZOIfE7Hg35O7narSVj60NTsr5Rcg3K4Oy2GAgAtRAVRooiW32LK66HXhKNUWONW6ESznXbvSlPVjqElMH1-/s400/padlock.gif" alt="" id="BLOGGER_PHOTO_ID_5189602211774262546" border="0" /></a><br /><br />I had to recently modify a web service container (<a href="http://ws.apache.org/axis2/">Apache Axis 2</a>) to support the <a href="http://xml.coverpages.org/WSS-KerberosProfileV10.pdf">WS-Security Kerberos token profile.</a><br /><br />To achieve this, I had to go from knowing absolutely nothing about Kerberos, to being a near expert in it and its related technologies. This involved having to do my own builds of the Apache WS-Security implementation, getting a complete understanding of the WS-Security Kerberos token profile for SOAP messages, learning how to configure and setup Key Distribution Centers (KDCs) such as Active Directory and Apache Directory and finally learning the ins and outs of the Java JAAS/GSS APIs to implement all this.<br /><br />Part of this process involved me having to get a complete understanding of how the Kerberos algorithm works, including what on earth <span style="font-weight: bold;">Service Principal Names </span>(SPNs) are.<br /><br />Googling around for this, and trawling through the Sun Java forums was only enough to give me bits and pieces of info. Even talking to so called "experts" in the Sun forums was not enough to answer these questions. The ironic thing about this is, that the answers are extremely simple, once you understand the Kerberos algorithm.<br /><br />Note that this blog is specific to Windows domains running Active Directory. I will do a blog in the future about setting up <a href="http://directory.apache.org/">Apache Directory Server</a> as your KDC.<br /><br /><span style="font-weight: bold;">1. What is a SPN?</span><br /><br />A service that is being secured by Kerberos, is required to have an identity within the realm that the system exists. On a Windows network, your service must have an identity in Active Directory (if AD is the domain controller), in the same way a user logging in to your client application must also have an identity in AD (user account).<br /><br />In Kerberos, when a client identifies the service they are wanting to talk to, a Kerberos naming convention is used to identify that service. This convention is different from how AD would identify that service's user account. Whereas AD would know the account by a simple username, the Kerberos standard includes information such as the machine and the domain that the service runs on.<br /><br />An SPN, is simply a way of telling AD that the Kerberos identity <span style="font-weight: bold;">x</span> maps to the AD account <span style="font-weight: bold;">y</span>. Then when Kerberos requests are made to AD, it can translate the Kerberos identities to the correct AD accounts.<br /><br /><span style="font-weight: bold;">2. What does a Kerberos SPN look like?<br /><br /><span style="font-weight: bold;"><span style="font-weight: bold;"><span style="font-weight: bold;"> </span></span></span></span>This is the part where it is quite confusing. The actual format for SPNs is supposed to be something like:<br /><br />< <i>service type</i> >/< <i>instance name</i> >:< <i>port number</i> >/< <i>service name</i> ><br /><br />However, not all this information is needed. I never specify the port number, as there is only ever one web services container running on the target machine. Additionally, I don't usually specify the realm as my stuff only runs within one realm anyway, and the JAAS library appears to append this information anyway.<br /><br />For me, the best answer to this question is "Something that works!". Hence I use one of the following :<br /><br />< <span style="font-style: italic;">service name</span> >/< <i>instance name</i> ><br />< <span style="font-style: italic;">service name</span> >/< <i>instance name</i> > @ < <span style="font-style: italic;">realm name</span> ><br /><br />These have worked fine for me with both an AD domain and an Apache Directory domain.<br /><br /><span>E.g.</span><span style="font-style: italic;"> mywebservice/testmachine </span><span>or</span><span style="font-style: italic;"> mywebservice/testmachine@example.com<br /><br /></span><span>Note that in some cases, computers are also given Kerberos identities to prevent spoofing of machines. The format for a computer on a network is:<br /></span><span style="font-style: italic;"><br />host</span> / < <span style="font-style: italic;">machine name</span> ><span style="font-style: italic;">.realm.com@REALM.COM<br /></span><br /><span style="font-weight: bold;">3. How do I set up SPNs in Active Directory?<br /><br /></span>Most of the info I found on the net pointed to the <span style="font-style: italic;">ktpass</span> tool for setting up Kerberos SPNs. I had real trouble getting this to work correctly in Active Directory, specifically with the encryption types being incompatible.<br /><br />Then I found the <span style="font-style: italic;">setspn.exe</span> program that comes with Active Directory, and it sorted all my problems out.<br /><br />Setspn.exe lets you set your Kerberos to AD mappings up and also will list the SPNs for a given AD account. It is simple to use, and you don't have to worry about DesMd5 vs ArcFourHmac encryption errors.<br /><br />The following command adds an SPN for a user account:<br /><br /><span style="font-style: italic;">setspn -A servicename/machine ad-service-account-name<br /><br /></span>e.g.<span style="font-style: italic;"> setspn -A datawebservice/testserver datawebservice<br /><br /></span>where <span style="font-style: italic;">datawebservice </span>is the AD identity/account for the web service.<br /><span style="font-style: italic;"></span><br />Use the <span style="font-style: italic;">-l </span>parameter to list the SPNs for a given account.<br /><br /><span style="font-weight: bold;">4. Sidestep SPNs.<br /><br /></span>I also found that it is possible to simply ignore SPNs when using the Java GSS API. When specifying a GSS server name to create a context with, simply specify it as an <span style="font-style: italic;"></span><span style="font-style: italic;">NT_USERNAME </span>instead of an <span style="font-style: italic;">NT_HOSTBASED_SERVICE</span>.<br /><br />e.g.<span style="font-style: italic;"> GSSName serverName = manager.createName( "datawebservice", GSSName.NT_USER_NAME);<br /><br /></span>Note that if you are having to interop with .NET services, you may not be able to do this. Additionally, .NET Kerberos applications may not be as flexible in their Kerberos naming rules.<span style="font-style: italic;"><br /></span><span style="font-weight: bold;"></span>The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com14tag:blogger.com,1999:blog-5374152728206138749.post-14783807251701903542008-03-26T19:41:00.005+13:002008-03-26T20:24:33.624+13:00Making sense in a crazy Java worldThis is my first blog post - so hello there!<br /><br />The reason for this blog is to document, discuss and dissect problems and solutions I have come across while being a Java software developer. I have found that more often than not, using Java and its related technologies requires you to take things several steps further than the tutorials, guides and forum discussions describe. In other cases you have to spend days trawling the web and starting forum discussions to piece all the bits of information you need together to actually solve a problem.<br /><br />While this is all very satisfying when you finally achieve your goal, you realise that suddenly, your project budget has shot out of control, and you have slipped against the project plan. Many late nights and extreme bouts of stress are to follow.....<br /><br />So the purpose of this blog is to publish the complete solutions to all these non-trivial problems that us humble Java programmers have to face from day to day.<br /><br />Hopefully I can help some of you to meet your project deadlines! Also, hopefully other Java monkeys will find solutions here that they can't get anywhere else on the net....The Java Monkeyhttp://www.blogger.com/profile/06593236838532411278noreply@blogger.com5