Chaining SSH Tunnels
So in an early post I described how to create an SSH tunnel. That is fine if you only need to connect to a server by going through a single bastion. In this post, I am going to provide an example for how to connect by going through multiple servers; in other words, how to chain SSH tunnels.
Suppose you want to connect to server E but in order to do so, you have to be on server D that you can only access from C which in turn can only be accessed from B that only A can access. The following should setup the necessary chain of SSH tunnels.
ssh -NL 6000:B:22 -o TCPKeepAlive=no -o ServerAliveInterval=15 A &
ssh -NL 6001:C:22 -o TCPKeepAlive=no -o ServerAliveInterval=15 localhost -p 6000 &
ssh -NL 6002:D:22 -o TCPKeepAlive=no -o ServerAliveInterval=15 localhost -p 6001 &
ssh -NL 6003:E:22 -o TCPKeepAlive=no -o ServerAliveInterval=15 localhost -p 6002 &
ssh localhost -p 6003
Welcome to E!
$
Rotating log files in log4j
In Java, it is pretty standard to use log4j to handle your logging. In a production environment, you rarely want to all your log data to be kept in a single file. If you did, eventually that file would become very large and difficult to use especially when you are looking for specific error messages.
A good practice is to employ rotating log files. Old log data gets moved into other files and really old log data gets removed automatically. It is pretty easy to setup this style of logging up in log4j. You need to use a RollingFileAppender.
log4j.rootCategory=WARN, stdout, rollFile
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.appender.rollFile=org.apache.log4j.RollingFileAppender
log4j.appender.rollFile.File=/web/logs/sample.log
log4j.appender.rollFile.MaxFileSize=10MB
log4j.appender.rollFile.MaxBackupIndex=5
log4j.appender.rollFile.layout=org.apache.log4j.PatternLayout
log4j.appender.rollFile.layout.ConversionPattern=%d %p [%c] - <%m>%n
In the above example, I send log data to both the console and a rotation log file.
Adding authentication to an Apache CXF Web Service Client
In my last post, I provided steps for creating an Apache CXF Web Service Client. Now that client only works with Web Services that do not require you to authenticate. In this post, I will provide an example of how add authentication.
Step 1. Create a CXF Web Service client
Step 2. Create a CallbackHandler. The code below is actually from a previous post pertaining to authenticating an Axis 1.4 Web Service Client:
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ws.security.WSPasswordCallback;
public class SamplePWCallback implements CallbackHandler {
private Configuration _config;
private static Log _logger = LogFactory.getLog(SamplePWCallback.class);
public SamplePWCallback () {
super();
_logger.info("Initializing Sample PW Callback");
_config = null;
try {
_config = new PropertiesConfiguration("sample.properties");
}
catch (ConfigurationException ce) {
_logger.error("Error loading configuration file", ce);
}
}
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof WSPasswordCallback) {
WSPasswordCallback pc = (WSPasswordCallback)callbacks[i];
// set the password given a username
if (_config.getString("username").equals(pc.getIdentifier())) {
pc.setPassword(_config.getString("password"));
}
}
else {
throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback");
}
}
}
}
Step 3. Update your client to use your CallbackHandler. For the purposes of demonstration, let’s assume the global weather Web Service that I used in the previous blog post required authentication. The client would have to be updated to look similar to the sample one below:
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import net.webservicex.*;
import org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;
import org.apache.ws.security.handler.WSHandlerConstants;
import org.apache.ws.security.WSConstants;
public class HelloCXF {
public static void main (String args[]) {
try {
GlobalWeather service = new GlobalWeather(new URL("file:globalweather.wsdl"));
GlobalWeatherSoap weatherSoap = service.getGlobalWeatherSoap();
Client client = ClientProxy.getClient( weatherSoap );
Endpoint endPoint = client.getEndpoint();
Map<String, Object> outProperties = new HashMap<String, Object>();
outProperties.put( WSHandlerConstants.ACTION,
WSHandlerConstants.USERNAME_TOKEN );
outProperties.put( WSHandlerConstants.USER, _config.getString("username"));
outProperties.put( WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT );
outProperties.put( WSHandlerConstants.PW_CALLBACK_CLASS,
SamplePWCallback.class.getName() );
WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor( outProperties );
endPoint.getOutInterceptors().add( wssOut );
endPoint.getOutInterceptors().add( new SAAJOutInterceptor() );
endPoint.getInInterceptors().add( new LoggingInInterceptor() );
endPoint.getOutInterceptors().add( new LoggingOutInterceptor() );
System.out.println("Weather for New York");
System.out.println(weatherSoap.getWeather("New York", "United States"));
}
catch (MalformedURLException mue) {
System.err.println("problem with the wsdl url");
System.exit(1);
}
}
}
Creating an Apache CXF Web Service Client
So in early blog posts, I discussed how to make an Axis 1.4 client and secure it. Axis 1.4, however, has been end of lifed; no more work is being done on it. Now, if you are thinking about using Axis 2 to make a client in order to stick with a package that is actively being maintained, I would strongly advise against it. Axis 2 is really designed to be used on a Java application server and it is not friendly at all for standalone clients. There are literally 2 dozen jars plus modules that you need to add to your classpath if you want your client to work.
Fortunately, there is another Apache Web Service project, CXF. From what I can see, CXF is really a continuation of where Axis 1.4 left off. Even for Java Web apps, CXF is much friendlier to use than Axis 2.
Below is an example of how to setup a CXF client
Step 1. Download CXF
Step 2. Download the wsdl that you want to use. For this post, similar to the previous ones, I am going to use the global weather wsdl
Step 3. Use the wsdl2java CXF utility to generate Java code representing the global weather Web Service. The command should look like the following:
wsdl2java globalweather.wsdl
Step 4. Copy the code from step 3 into your source code directory. If you are working in Eclipse, you can drag and drop the code into your src directory
Step 5. Add the following files from the CXF lib directory to your classpath. If you are working in Eclipse, you can drag and drop these files into your own lib directory
- commons-collections-3.2.1.jar
- commons-lang-2.4.jar
- commons-logging-1.1.1.jar
- cxf-2.2.12.jar
- geronimo-activation_1.1_spec-1.0.2.jar
- geronimo-annotation_1.0_spec-1.1.1.jar
- geronimo-javamail_1.4_spec-1.6.jar
- geronimo-jaxws_2.1_spec-1.0.jar
- geronimo-servlet_2.5_spec-1.2.jar
- geronimo-stax-api_1.0_spec-1.0.1.jar
- geronimo-ws-metadata_2.0_spec-1.1.2.jar
- jaxb-api-2.1.jar
- jaxb-impl-2.1.13.jar
- neethi-2.0.4.jar
- saaj-api-1.3.jar
- saaj-impl-1.3.2.jar
- wsdl4j-1.6.2.jar
- wss4j-1.5.10.jar
- wstx-asl-3.2.9.jar
- xml-resolver-1.2.jar
- XmlSchema-1.4.7.jar
- xmlsec-1.4.3.jar
Step 6. Create a Web Service client. Below is an example of a client that works with the global weather Web Service:
import java.net.MalformedURLException;
import java.net.URL;
import net.webservicex.*;
public class HelloCXF {
public static void main (String args[]) {
try {
GlobalWeather service = new GlobalWeather(new URL("globalweather.wsdl"));
GlobalWeatherSoap weatherSoap = service.getGlobalWeatherSoap();
System.out.println("Weather for New York");
System.out.println(weatherSoap.getWeather("New York", "United States"));
}
catch (MalformedURLException mue) {
System.err.println("problem with the wsdl url");
System.exit(1);
}
}
}
Axis 1.4 Web Service Client Side authentication
So in a previous post, I gave an example of how to setup an Axis 1.4 Web Service client. That is fine if you do not have to login to use the service. Often times however, you do have to authenticate your client especially if your work has paid for the third party Web Service that you want to use. Here you want to use WSS4J. Below are the steps to get yourself setup:
Step 1. Download WSS4J
Step 2. Add the contents of the WSS4J lib directory to your classpath. If you are working with a project in Eclipse, you probably want to copy the contents of WSS4J lib to your own lib directory
Step 3. Create a deployment descriptor file. Below is an example of one that we will refer to as sample.wsdd:
<deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
<transport name="http" pivot="java:org.apache.axis.transport.http.HTTPSender"/>
<globalConfiguration >
<requestFlow >
<handler type="java:org.apache.ws.axis.security.WSDoAllSender" >
<parameter name="action" value="UsernameToken"/>
<parameter name="user" value="sampleUsername"/>
<parameter name="passwordCallbackClass" value="SamplePWCallback"/>
<parameter name="passwordType" value="PasswordDigest"/>
</handler>
</requestFlow >
</globalConfiguration >
</deployment>
Step 4. Create a CallbackHandler. In my example below, I use the Apache configuration package to get the username and password from a properties file in order to avoid hardcoding it.
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ws.security.WSPasswordCallback;
public class SamplePWCallback implements CallbackHandler {
private Configuration _config;
private static Log _logger = LogFactory.getLog(SamplePWCallback.class);
public SamplePWCallback () {
super();
_logger.info("Initializing Sample PW Callback");
_config = null;
try {
_config = new PropertiesConfiguration("sample.properties");
}
catch (ConfigurationException ce) {
_logger.error("Error loading configuration file", ce);
}
}
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof WSPasswordCallback) {
WSPasswordCallback pc = (WSPasswordCallback)callbacks[i];
// set the password given a username
if (_config.getString("username").equals(pc.getIdentifier())) {
pc.setPassword(_config.getString("password"));
}
}
else {
throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback");
}
}
}
}
Step 5. Access your wsdd in your Web Service client. Let’s assume the global weather Web Service that I used in the previous Axis 1.4 blog post required authentication. Here is what your client would look like.
import NET.webserviceX.www.*;
import org.apache.axis.configuration.FileProvider;
public class HelloWeather {
public static void main(String[] args) throws Exception {
FileProvider wsdd = new FileProvider("sample.wsdd");
wsdd.setSearchClasspath(true);
GlobalWeatherLocator locator = new GlobalWeatherLocator(wsdd);
GlobalWeatherSoap globalWeatherSoap = locator.getGlobalWeatherSoap();
System.out.println("Weather for New York");
System.out.println(globalWeatherSoap.getWeather("New York", "United States"));
}
}
Tomcat 5.5/6.0 SSL Setup
If you want to serve pages from Tomcat over https, you are going to have to setup SSL in Tomcat. This is important if you are going to perform user authentication or serve sensitive data.
Here are the steps you need to do to get SSL up and running on your Tomcat instance.
Steps 1: Create an certificate keystore. In lieu of buying a certificate, you can create a self signed one. You don’t really need to buy one unless you are going to be serving work related content from your Tomcat server. You can use the keytool utility that comes with the Java SDK in the bin directory. The command should look something like the one below:
keytool -genkey -alias tomcat -keyalg RSA -keystore /path/to/my/keystore
Step 2: Update your server.xml to contain a connector similar to the one below. If you are working with the server.xml that is in the Tomcat tar ball, this tag should be commented out. Uncomment it and update the location of the keystoreFile path to where yours is located.
<Connector
port="8443" maxThreads="200"
scheme="https" secure="true" SSLEnabled="true"
keystoreFile="/path/to/my/keystore" keystorePass="changeit"
clientAuth="false" sslProtocol="TLS"/>
References:
http://tomcat.apache.org/tomcat-5.5-doc/ssl-howto.html
http://tomcat.apache.org/tomcat-6.0-doc/ssl-howto.html
Logging uncaught exceptions in a Java application
Logging exceptions is pretty important as it allows you to troubleshoot problems that come up with your production applications. If you have a regular Java application, there is a trick to logging uncaught exceptions. Those are the exceptions that do not require you to put your code in a try/catch block. To log these exceptions, you need to set a default uncaught exception handler. The example below defines one that uses log4j to write an ERROR log entry.
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class HelloWorld {
private static Log _logger = LogFactory.getLog(HelloWorld.class);
public static void main (String args[]) {
//setting uncaught exception logging
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) {
_logger.error("An uncaught exception has been thrown", e); }
});
}
}
Using the Spring Expression Language with static properties
One of the things that I love about Spring 3 is the Spring Expression Language (SPEL). At first glance, SPEL does not appear to give you that much; with it, you can execute Java expressions in your Spring config files. This capability, however, is awesome when have properties that take values defined by static variables. For example, how days of the week are defined in the Calendar class:
Calendar.MONDAY
Calendar.TUESDAY
Calendar.WEDNESDAY
Calendar.THURSDAY
Calendar.FRIDAY
Calendar.SATURDAY
Calendar.SUNDAY
Prior to SPEL, if you wanted to set an integer property to Calendar.MONDAY, you had to know and specify the actual integer value for it. Using SPEL, can simply use the following snippet instead; you need to add the org.springframework.expression-3.X.X.jar to your classpath as well.
<property name="dayOfWeek" value="<entry key="#{T(java.util.Calendar).MONDAY}" />
Java case insensitive String replaceAll
Here’s a quick handy tip for replacing all occurrences of a substring regardless of case in Java. Put “(?i)” at the beginning of your substring. For example:
String a = "hello HELLO hEllO";
String b = a.replaceAll("(?i)hello", "bye");
System.out.println(a);
System.out.println(b);
The above code will output:
hello HELLO hEllO
bye bye bye
Load and Unload Mac daemons
So I recently upgrade to Postgresql 9.0 from 8.4 on my Mac. I realized after installing that I did not remember how to stop my old Postgresql instance from startup on boot. Basically, both versions were trying to run on the same port whenever I booted up. I did not want to uninstall my old version just yet so I needed to stop it from starting up. You can see the list of daemons on your Mac that will startup on boot using the command below:
ls /Library/LaunchDaemons/
I stopped Postgresql 8.4 from startup on boot using the following command.
sudo launchctl unload -w com.edb.launchd.postgresql-8.4.plist