Flexible OSGi Servlet Filters

Posted on August 29, 2010 by Tommy McGuire
Labels: eclipse virgo, SAML, authentication, http, OpenAM, osgi, java
As part of Dr. McGuire's Journey into Authentication-land, I needed a Java Servlet filter to handle the application end of the SAML2 authentication mambo. For a normal Java web application, I would just write the code and include it in a .jar file in the lib directory of the application's .war or in the application server's lib directory. However, with the OSGi-based Virgo application server (Was: The SpringSource dm Server), I can do a bit better by installing my .jar file as an OSGi bundle. The upside of this is that multiple applications could share the filter class as they would if the .jar were on the server's class path, while letting me subsequently install a newer version of the bundle which other applications can simultaneously use.

But wait, you say, if I put the filter in a separate bundle, how am I going to use it in the application? Fortunately, with Virgo that is relatively simple. In the first place, I export the package in which the Filter class lives in the MANIFEST.MF of the bundle. (Or, the bnd file used to generate the MANIFEST.MF.)

Export-Package: \
gov.nasa.web.saml2.endpoint; \
version=${bundle.version}; \
uses:="javax.servlet,javax.servlet.http", \

Then, in the MANIFEST.MF of the application .war file, I import the package,

Import-Package: gov.nasa.web.saml2.endpoint;version="[1.1.0,2.0.0)"

and add it to the application's web.xml normally.

<filter>
<filter-name>SAMLFilter</filter-name>
<filter-class>net.crsr.saml2.endpoint.Filter</filter-class>
<init-param>
<param-name>BACKUP-LOGIN-PAGE</param-name>
<param-value>/login.jsp</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>SAMLFilter</filter-name>
<url-pattern>/test.jsp</url-pattern>
</filter-mapping>

It just works(tm). The classloader used by the servlet container correctly looks at the OSGi manifest-based class space and picks up the filter class.

The downside? If I need to change the code in the filter I will need to redeploy all of the applications that use it, in order to force them to re-resolve their imports. (Yes, I still have not gotten deeper into the Virgo/SSdm 2.0; this step may not actually require redeployment any more, but it still will require some work to force the applications to re-resolve.) And I am certain to need to change the code in the filter. Fortunately, OSGi includes this nifty service registry that allows dynamic code replacement.

The architecture gets a little more complex at this point. Fundamentally, what I need to do is to move the filter code to a separate "filter backing service" class in a second bundle to make replacement easier. Then I need to replace the filter itself with a shim that makes use of the backing service. The replacement could be done several ways, but since I have applications that do not currently know anything about OSGi, I chose to make it look much like the scenario described above in order to minimize the consequences to those applications. Then, I make the changes above to the applications' MANIFEST.MF and web.xml, and we are good to go.

I came up with the following code for the Filter. Since the filter configuration is actually defined by the backing service, I created a Configuration class, based on the original FilterConfig, as part of the backing service interface that is in turn implemented by a class in the backing service.

When the backing service is replaced, the new backing service will have a new Configuration class; when the application calls the filter backing service and tries to use the old Configuration, the backing service will get a ClassCastException, which it converts to an IllegalArgumentException. In the filter below, the IAE is used as a indication to regenerate the Configuration and retry the operation.

public class Filter implements javax.servlet.Filter
{
private FilterConfig filterConfig;
private volatile ISamlFilterBackingService.Configuration configuration = null;

@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse, servletResponse, FilterChain filterChain)
throws IOException, ServletException {
try {
tryFilter(servletRequest, servletResponse, filterChain);
}
catch (IllegalArgumentException e) {
unsetConfiguration();
tryFilter(servletRequest, servletResponse, filterChain);
}
}

@Override
public void destroy() {
try {
tryDestroy();
}
catch (IllegalArgumentException e) {
// Assume this has something to do with the configuration....
unsetConfiguration();
tryDestroy();
}
}

private void tryFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
FilterServices.getBackingService()
.doFilter(getConfiguration(), (HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse, filterChain);
}

private ISamlFilterBackingService.Configuration getConfiguration() {
if (configuration == null) {
synchronized (this) {
if (configuration == null) {
this.configuration = FilterServices.getBackingService().getConfiguration(filterConfig);
}
}
}
return configuration;
}

private synchronized void unsetConfiguration() { configuration = null; }

private void tryDestroy() { FilterServices.getBackingService().destroy(getConfiguration()); }
}

If you look closely, you notice a FilterServices.getBackingService() call in tryFilter; this method returns the current backing service; FilterServices is a combination of a static accessor and a Spring bean into which the service is injected. I tend to use this sort of technique to avoid relying on the Spring infrastructure and programming in XML (or annotations).

public class FilterServices {
private static volatile ISamlFilterBackingService samlFilterBackingService = null;
public static ISamlFilterBackingService getBackingService() {
return samlFilterBackingService;
}
public void setSamlFilterBackingService(ISamlFilterBackingService samlFilterBackingService) {
FilterServices.samlFilterBackingService = samlFilterBackingService;
}
}

Some strange classloading issues do arise at this point. I would expect the Filter to be able to access the FilterServices class (and the ISamlFilterBackingService interface) no matter which package the FilterServices class was in, as long as it was part of the SAML filter API bundle, and no matter which application was using the instance of the Filter class. However, that appears not to be the case. The FilterServices class needs to be visible to the application in order for the application's Filter instance to see it. So I put FilterServices in the same package as the Filter.

The interface for the backing service significantly resembles that of a servlet filter, with the additional complication of managing the Configuration.

public interface ISamlFilterBackingService
{
public Configuration getConfiguration(FilterConfig filterConfig);
public void doFilter(Configuration config, HttpServletRequest rqst, HttpServletResponse resp, FilterChain chain)
throws IOException, ServletException;
public void destroy(Configuration config);

public static abstract class Configuration { }
}

The implementation of the backing service is another kettle of very stinky fish, that I will get into at a later time.
active directory applied formal logic ashurbanipal authentication books c c++ comics conference continuations coq data structure digital humanities Dijkstra eclipse virgo electronics emacs goodreads haskell http java job Knuth ldap link linux lisp math naming nimrod notation OpenAM osgi parsing pony programming language protocols python quote R random REST ruby rust SAML scala scheme shell software development system administration theory tip toy problems unix vmware yeti
Member of The Internet Defense League
Site proudly generated by Hakyll.