Ramblings on my life and work including family, coding, and whatever else I'm doing.

Wednesday, July 31, 2013

Vaadin, Shiro, and Push

I've been using Vaadin for the past few months on a large project and I've been really impressed with it. I've also been using Apache Shiro for all of the projects authentication, authorization, and crypto needs. Again, very impressed.

Up until Vaadin 7.1, I've just been relying on my old ShiroFilter based configuration of Shiro using the DefaultWebSecurityManager. While this configuration wasn't an exact fit for a Vaadin rich internet application (RIA), it worked well enough that I never changed it. The filter would initialize the security manager and the Subject and it was available via the SecurityUtils as expected.

Then Vaadin 7.1 came along with push support via Atmosphere. Depending on the transport used, Shiro's SecurityUtils can no longer be used because it depends on the filter to bind the Subject to the current thread but, for example, a Websocket transport won't use the normal servlet thread mechanism and a long standing connection may be suspended and resumed on different threads.

There is a helpful tip for using Shiro with Atmosphere where the basic idea is to not use SecurityUtils and to simply bind the subject to the Atmosphere request. Vaadin does a good job of abstracting away the underlying transport which means there is little direct access to the Atmosphere request; however Vaadin does implement a VaadinSession which is the obvious place to stash the Shiro Subject.

First things first, I switched from using the DefaultWebSecurityManager to just using the DefaultSecurityManager. I also removed the ShiroFilter from my web.xml. With the modular design of Shiro I was still able to use my existing Realm implementation and just rely on implementing authc/authz in the application logic itself. The Vaadin wiki has some good, general examples of how to do this. Essentially this changes the security model from web security where you apply authc/authz on each incoming HTTP request to native/application security where you implement authc/authz in the application and assume a persistent connection to the client.

Next up, I needed a way to locate the Subject without relying on SecurityUtils due to the thread limitations mentioned above. Following the general idea of using Shiro with Atmosphere, I wrote a simple VaadinSecurityContext class that provides similar functionality but binds the Subject to the VaadinSession rather than to a thread. Now that I don't have the SecurityUtils singleton anymore, I rely on Spring to inject the context into my views (and view-models) as need using the elegant spring-vaadin plugin.

At this point everything was working and I have full authc/authz with Shiro and Vaadin push support. But, the Shiro DefaultSecurityManager uses a DefaultSessionManager internally to manage the security Session for the Subject. While you could leave it like this, I didn't like the fact that my security sessions were being managed separately from my Vaadin/UI sessions. This was going to be a problem when it came to session expiration because Vaadin already has UI expiration times and VaadinSession expiration times and I was now introducing security Session expiration times. The odds of getting them all to work together nicely was slim and I can imagine users getting randomly logged out while still having valid UIs or VaadinSessions.

My solution was to write a custom Shiro SessionManager and inject it into the DefaultSecurityManager. My implementation is very simple with the assumption that whenever a Shiro Session is needed, a user specific VaadinSession is available. The VaadinSessionManager creates a new session (using Shiro's SimpleSessionFactory) and stashes it in the user specific VaadinSession. Expiration of the Shiro Session (and Subject) are now tied to the expiration of the VaadinSession. While I could have used the DefaultSessionMananger and implemented a custom Shiro SessionDAO, I didn't see that the DefaultSessionManager offered me much given that I did not want Session expiration/validation support.

So that's it. I wire it all up with Spring and I now have Shiro working happily with Vaadin. The best part is that none of my existing authc/authz code changed because it all simply works with the Shiro Subject obtained via the VaadinSecurityContext. In the future if I need to change up this configuration, I expect that my authc/authz code will remain exactly the same and all the changes will be under the hood with some Spring context updates.

I'm interested to hear if anyone else found a good way to link up these two great frameworks or if you see any holes in my approach. I'm no expert on Atmosphere and Vaadin does a good bit of magic to dynamically kickoff server push, but so far things have been working well. Best of luck!

I am new to Vaadin and Shiro. It looks like you override the shiro Realm with your own class. Could you plese explain why, and if that is critical to using the vaadin session to hold shiro session information? Thanks.

Hi there,as soon as I call "securityContext.getSubject().login(token)", an exception is thrown because no security manager is found. The DefaultSecurityManager used creates a new Subject upon login and tries to find a Thread-local SecurityManager, but (obviously) there is none. One could use SecurityUtils.setSecurityManager, but this is - by the looks of it - strongly discouraged. Any Ideas?

Take a look at the security context implemented above. You need to explicitly set the security manager because of exactly this problem. At line 69, it then uses the security manager rather than trying lookup the default in the thread local:

I've done that. Take a look at the login implementation of DefaultSecurityManager (line 267++)This method returns "Subject loggedIn = createSubject(token, info, subject);", createSubject does not use the set securityManager, but creates a new SubjectContext and searches a securityManager on its own (ensureSecurityManager, line 410). The Exception gets caught by shiro internally and "only" gets printed to debug, I still think there is some work to be done :/

Tobias, I see what you're saying. It looks like Shiro handles this because if resolveSecurityManager doesn't find a security manager it defaults to the current security manager (i.e. this) which is the desired behavior. So you could probably get rid of the debug message by extending the DefaultSecurityManager and put the security manager in the context before resolveSecurityManager is called but it seems like that happens now and the debug message is informational.

No, sorry, I don't have a full example application put together. You might want to check some of the recent webinars from Vaadin. I know they just completed one on authc/authz where they give a quick example of using Shiro to limit access to views. You can also check the vaadin-spring add-on for examples of using Spring with Vaadin.

My first guess would be to make sure you're using DefaultSecurityManager instead of DefaultWebSecurityManager. My approach removes any of the HTTP/Web support from Shiro and implements security as though the application was a normal thick client application so you shouldn't see any HTTP related errors.

In a clustered environment the HTTP session, including the Vaadin session may be serialized and persisted or shipped off to another server. Think this approach would break in that case because the Shiro Subject isn't serializable. I don't do any clustering and just use node affinity in my current setup so it hasn't been an issue. It is definitely something to think about if you plan on clustering web servers though.

Andre, good point. I don't do any clustering in my current applications so it isn't a huge deal for me. This seems like it would be a general problem with Shiro as there is no way to move a subject from server to server.

You could try wrapping the subject stored in the session with a helper class of some kind that marks the subject transient and has the ability to rebuild a subject if one isn't found. Maybe by keeping the principal around (like a username) while the subject is transient. Just an idea, I haven't tried it.