Thursday, December 20, 2012

Apache Wicket + Spring Security + Atmosphere server push

We recently started using Apache Wicket for the front end of one of our new applications. The work on the prototype went fine, well, actually it was a pleasant surprise after two years with JSF.

We have decided to dive into more advanced topics like performance, clustering and server push as we know they'll come on the way and we want to be sure we made right decision by picking Wicket.

I spent last couple of days with my colleague figuring out configuration of Apache Wicket together with Spring Security and Atmosphere framework to allow for server push.

Server push is pretty sleek feature of modern web applications which allows client to subscribe for certain events on the server and then server notifies client (the page in Wicket case) when such an event occurs. Page refreshes without user interaction which comes handy for feeds, messages, etc. There are several transfer technologies at place - starting with long polling and streaming ending with web sockets and SSE. As usual support varies from browser to browser so we can't for instance use only web sockets as they are supported very well by Firefox and Chrome but IE supports them from version 10! (see http://caniuse.com/#search=websocket). Hence we decided to give a try to Atmosphere - framework developed by Jeanfrancois Arcand which claims to mitigate these issues from you. Wicket provides some experimental integration for Atmosphere (wicket-atmosphere - in version 0.5) so let's give it a try.

 (I assume you already have an application using Maven, Spring, Spring Security and Apache Wicket)
pom.xml




Add atmosphere.xml to webapp/META-INF.

The web.xml should look like this:

Few points to the web.xml:

  • We need to provide a custom Broadcaster to the Atmosphere as broadcast happens in different thread, here the SecurityContext is not set to the thread so it results in unauthenticated error hence a redirect to the login page:)
  • Definition of WicketFilter in atmosphere.xml does not allow us to add init-params. Though when AtmosphereFramework sets up filters it provides them servlet context so we can specify applicationFactoryClassName and applicationClassName and it will just work:)

The SpringSecurityAwareBroadcaster looks like this:

Now the application should start and we can use wicket-atmosphere EventBus and @Subscribe to get it working.

In your WicketApplication.java add to the init method:


Now you can subscribe for event in a page by adding method like:


Now let's trigger some event. We have a separate Maven module (let's call it core module) for services and DB access which is not aware of Wicket existence thus we needed to get events to the EventBus from services somehow. Here I would point out Google Guava com.google.common.eventbus.EventBus.

All we you need is to add
to the spring-context.xml of the core module. This can be then autowired to services and in our example in we would have a MessageServiceImpl like:


Then we have to register a handler to Guava EventBus which would then post events to Atmosphere EventBus. Go to the WicketApplication.java and add these few lines:


Now you can schedule for instance a cron job which submits new message every now and then and observe it popping up in the application.

One would thing that that is all. Well, not really. We found out that if you let the session expire it blows up. Well, long story short, Wicket has its WicketSession which has a session ID. Our login page is a Wicket page so this session ID is set from the first http session which is created when user enters the page. Spring Security has a SessionFixationProtectionStrategy which invalidates this HttpSession and creates new one with authentication in onAuthentication method. Hence WicketSession ID no longer matches HttpSession ID. That was not a problem until we started with Atmosphere.

Wicket-Atmosphere EventBus manages subscriptions to events. It also unsubscribes client when session expires (in unboundSession method). The subscription is identified by a page and by a session ID. I guess you sense the disruption in the Source here;). The problem is that client subscribes under the WicketSession ID which is the ID of the first HttpSession. When it unsubscribes in sessionUnbound() it uses the second HttpSession ID hence it does not unsubscribe client and EventBus keeps sending events to that subscription.

We handled this problem by adding a RequestCycleListner in the WicketApplication. This listener removes old HttpSession from WicketSession and binds the new HttpSession to it.


The Listener looks like this:



Well, we will be happy to hear your comments.

5 comments:

  1. What exceptions happened without the SpringSecurityAwareBroadcaster?

    ReplyDelete
    Replies
    1. Hi Jason,

      I'm not sure about the precise exception, it was something like Unauthorized. I remember roughly that there was a problem that something in Atmosphere called flush on the request which resulted in check of SecurityContext and as it was not set to the broadcasting thread it failed and redirected you to the login page. It does not happen any more with the newer Tomcat. So SpringSecurityAwareBroadcaster is not needed anymore.

      I thought I'll just follow my steps, it will just work and then I can tell you exactly. Meanwhile, I installed newer version of Tomcat 7 (and made it work asynchronously as you can see in the following post). Then I encountered an issue with Tomcat settings called sessionCookiePathUsesTrailingSlash which prevented me from logging in from Chrome (took me some time to figure that one out as that would be problem for our production application as well). And then the setup described here did not work. I don't have time to dig into that (I used more than a year old version of Tomcat 7 before so it may be caused by that).

      However, I developed today a tiny application with working async Atmosphere push, Spring Security and Wicket. I'll put it online and share it here later on today.

      Delete
    2. Glad it is not happening anymore. I did not see that and was wondering if I missed something! I've had issues with Spring Security filters and Atmosphere.

      The Spring Security filters are standard JavaEE APIs, yet Tomcat (and JBoss) use proprietary COMET event-based servlet and filter APIs, which does not map up with Spring Security filters.

      I feel like I had to write major hacks to get it to work. Anything you post around setting up the filters would be appreciated :)

      Delete
    3. We gave it a second thought and I think the right explanation for SpringSecurityAwareBroadcaster was that we had Spring Security filter defined in the web.xml so it was called even for Atmosphere push events so we needed to be logged in. Our current solution, described in following post, defines Spring Security filter as a part of the filter chain to which delegates Atmosphere servlet. We will have a look whether it presents a security threat or not...

      I put the example application to my Dropbox public folder:
      http://dl.dropbox.com/u/71731051/push-test.zip

      Delete
  2. Thanks a lot! Your SpringSecurityAwareBroadcaster saved my time

    ReplyDelete