Spring Security’s web infrastructure is completely based on standard servlet filters. All requests go through a filter chain, where each filter does something with the request and decides whether to continue down the chain or stop and do something else. For example, a filter could check if the user is authenticated and redirect to a login page, if not. Another filter could analyse the URL, check that the current user has the proper role to access the page and show an error page, if not. A third filter could fetch the current security context from the session, store it in a thread local context holder and clear it when the request has been processed.
Since Spring Security only uses filters, it does not care about how the actual page is being served and thus works with any web framework, including Vaadin. There are, however, some things you need to pay attention to when securing a Vaadin application using Spring Security filters.
When you enable server push in a Vaadin Framework application, Vaadin will by default attempt to use websocket communication. When using websockets, only the first requests to your application will go through the security filter chain. Once the websocket channel has been established, all other requests will go through it, bypassing the security filter chain completely. No security checks will be performed and the security context holder will not be populated, causing any method-level security checks in the backend layer to deny access.
In other words, if you want to use filter based security and server push in your Vaadin application, you have to use a mechanism that is based on basic HTTP, such as long polling. If you are using Vaadin 7.6 or newer, you can also use the new
WEBSOCKET_XHR transport. This will use websockets for pushing data from the server to the client, but basic HTTP for everything else.
Access based on URL patterns
In a multi-page web application protected by Spring Security, a filter can analyze the path of the request and determine whether to grant access to a certain page or not. A Vaadin application, on the other hand, is a single page application and only has one entry point - the Vaadin servlet. All requests are either sent to the root of this servlet or to some of the built-in paths, such as
/[servletPath]/HEARTBEAT. When filter based security is used, this servlet should either be fully protected (meaning you cannot access it at all without being logged on) or fully open (meaning you can access it without logging in). This also applies to the static resources in
If you are using the navigation API, you might be tempted to use pattern matching on the navigation view paths (e.g.
/myUI#!admin/*). This will, however, not work, since the view paths are actually URL fragments and are not handled in the same way as paths to other pages in a traditional web application. If you want to restrict access to certain views based on the current user’s roles, you have to implement it inside your Vaadin application, e.g. in a custom
ViewProvider. The Vaadin Spring add-on also has some built in hooks for this, for example the
Both Vaadin and Spring Security have built-in protection for Cross-Site Request Forgery (CSRF). You have to disable one of them, otherwise your application will not work properly. The sample project (see below) contains an example of how to do this. It disables Spring Security’s protection and uses Vaadin’s.
Spring Security form login support is based on the user being redirected to a login page when access is denied. The user will then fill in the form and submit the login request back to the server, where a filter will pick it up and process it. If access is granted, the user will be redirected back to the original page, otherwise, an error page will be shown. Additional features such as Remember Me authentication is also handled via filters.
If you want to use this infrastructure, your life will be easier, if you implement the login form using a page-based framework (such as Spring MVC) and theme it to look like your Vaadin application. The reason for this is that if you choose to implement the login form using Vaadin, you have to handle the authentication yourself, either by invoking the Spring Security backend services directly or somehow tapping into the filter chain.
Even if you choose to implement your login form with some other technology than Vaadin, there are a few issues you might run into.
The first one is the URL the user is redirected to after logging in. By default, Spring will save the URL the user originally tried to access and redirect the user back to that URL after authentication. In a Vaadin application, this URL might be a heartbeat or push URL on some occasions. If this happens, the user would only see a blank page and would have to manually change the URL in the browser to get back to the application.
The second one is how Vaadin reacts when the reply to one of the Vaadin requests (push, heartbeat, etc.) is a redirect to the login form. On certain occasions, this can lead to a situation where Vaadin shows a communication error message that includes the login form. One way to reproduce this behavior is to start up the application, login, restart the server and then return to the application.
This is both ugly and confusing for the user. The easiest way to fix this is to customize the Vaadin
SystemMessages to reload the page immediately instead of showing a notification when a communication error occurs.
Session expiration and logout
Vaadin has a heartbeat signal that is regularly sent to the server to make sure that the browser is still alive. By default, the interval is five minutes, but you can change this. When three consecutive heartbeat signals have been missed, the server will dispose of the UI in question.
However, this also means that by default, these heartbeat signals will keep the HTTP session alive for as long as the browser is open. You can change this by configuring Vaadin to close idle sessions. This means that once a browser session has been idle for a duration equal to the session timeout, Vaadin will dispose of the UIs and close the session. The problem is that Vaadin will not close the HTTP session, but only the Vaadin session which lives inside the HTTP session. This means that the HTTP session will remain active until the next timeout occurs. Let’s illustrate this with a small example. Let’s say the session timeout is 60 seconds and the heartbeat interval is 20 seconds. This is what would happen:
00:00 Vaadin session is opened and left idle
00:20 Heartbeat signal, Vaadin session still idle, HTTP session kept alive
00:40 Heartbeat signal, Vaadin session still idle, HTTP session kept alive
01:00 Heartbeat signal, Vaadin session closed, HTTP session kept alive
02:00 HTTP session closed
In other words, the actual session timeout is 120 seconds.
Also, since Vaadin checks and possibly closes the session when a heartbeat signal is received, you should configure the heartbeat interval to be shorter than and a factor of the session timeout. For example, if the session timeout is 60 seconds and the heartbeat interval is 25 seconds, you would get the following behavior:
00:00 Vaadin session is opened and left idle
00:25 Heartbeat signal, Vaadin session still idle, HTTP session kept alive
00:50 Heartbeat signal, Vaadin session still idle, HTTP session kept alive
01:15 Heartbeat signal, Vaadin session closed, HTTP session kept alive
02:15 HTTP session closed
In other words, the actual session timeout is 135 seconds.
In most cases, you want the HTTP session and the Vaadin session to end at the same time. The easiest way of doing this is to customize the Vaadin
SystemMessages to redirect to the logout URL when the Vaadin session expires.
Finally, it is good practice to create a Spring logout handler that will explicitly close all the
VaadinSessions in the HTTP session. This will make sure that any resources used by the Vaadin UIs etc. are disposed of properly.
For a code example of the principles outlined in this blog post, please check https://github.com/peholmst/SpringSecurityDemo
. The module you want to have a closer look at is filter-based-security.