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!

30 comments:

  1. I created "pull request" to V7 project:
    https://github.com/davidsowerby/v7/pulls

    - Guice, Shiro, Vaadin 7, and now (thank you Mike!) Server-side Push

    It works for me:
    https://github.com/FuadEfendi/v7

    ReplyDelete
  2. You might be interested in a new feature that I've been working on for Vaadin 7.2 that could simplify the integration with frameworks like Shiro: http://dev.vaadin.com/ticket/12256

    ReplyDelete
  3. Mike

    thanks this was a great start for us to get the Vaadin 7 push feature into V7 (with help from Fuad Efendi, above).

    Leif

    The ticket looks interesting ..

    ReplyDelete
  4. 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.

    ReplyDelete
    Replies
    1. I use a custom realm because my application authenticates against our own DB of users which uses custom password hashing. You should be able to use one of the default realms without issue.

      Delete
  5. 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?

    ReplyDelete
    Replies
    1. 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:

      subject = (new Subject.Builder(securityManager)).buildSubject();

      Delete
    2. 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 :/

      Delete
    3. This comment has been removed by the author.

      Delete
    4. 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.

      Delete
    5. Hi,
      the same issue is discussed here https://issues.apache.org/jira/browse/SHIRO-457

      Is there a simple way to avoid such behavior (avoid the exception)
      Thanks

      Delete
  6. Hello Mike,

    I'm new on shiro and vaadin, do you have a complete project example of this implementation?

    ReplyDelete
    Replies
    1. 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.

      Delete
  7. Hi,
    thanks for the interesting tutoriel
    I have tried your example with vaadin 7.1.7 and every time I get an exception:

    java.lang.IllegalArgumentException: SessionContext must be an HTTP compatible implementation.

    Any idea of the cause ??

    ReplyDelete
    Replies
    1. 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.

      Delete
  8. Hi Mike,
    how can i implement it if I'm not using spring?
    which of the objects should i initialize in the login form for example?

    ReplyDelete
  9. Hi Mike, How to wire everything up when not using spring but simply web.xml.

    ReplyDelete
  10. Thanks for this Mike - just what I was looking for.
    One question though, the VaadinSession needs to be serializable. From the javadoc:

    "Everything inside a VaadinSession should be serializable to ensure compatibility with schemes using serialization for persisting the session data. "

    The Shiro Subject is not Serializable though, can you see any problems here? I'm not sure what schemes the javadoc is talking about.

    Thanks
    Paul

    ReplyDelete
    Replies
    1. 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.

      Delete
  11. Mike Pilone referred to a recent Vaadin webinar on the subject of Authentication/Authorization (and such). I believe meant this one from 2013-11:

    Vaadin Application Security Webinar (blog post with video)

    https://vaadin.com/blog/-/blogs/vaadin-application-security-webinar

    ReplyDelete
  12. Thanks for your effort, Mike!
    I have tried to reproduce the server push/shiro problem in our Vaadin project. However everything seems to work fine whatever I do. I am running my tests locally on a Macbook with Vaadin 7.1.11, JBoss 7.1.1 and Netbeans 7.4.

    Can anyone give me ideas how I can get this problem to happen.

    Until now I have created a UI which does a shiro login first. Then it starts a thread which checks

    if ( currentUser.isAuthenticated() )...

    at intervals gradually increasing from 1 second to several hours and pushes a timestamp to the browser and just sleeps the rest of the time.

    I have played with different combinations of
    @Push(value=PushMode.MANUAL,transport=Transport.WEBSOCKET)
    //@Push(PushMode.MANUAL)
    //@Push

    ...and the thing just seems to work. What am I missing here?

    ReplyDelete
  13. This comment has been removed by the author.

    ReplyDelete
  14. The problem is that ShiroSecurityNavigator use SecurityUtils and not through VaadinSecurityContext ..
    There is a solution?

    ReplyDelete
  15. Great work! The only problem I see with this approach is that you cannot serialize your session, since Subject from shiro is not serializable. In a cluster environment that is a huge problem.

    ReplyDelete
    Replies
    1. 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.

      Delete
  16. Now this is in actual fact cooperative. It’s very openhanded of you to share this with us. Password Manager

    ReplyDelete
  17. Hi,
    Here is how I made the whole thing work without getting rid of SecurityUtils call in my business logic : https://vaadin.com/forum#!/thread/14001302

    ReplyDelete