Paul Römer

This tutorial isn’t like others: it is about explicitly answering your questions. It deals with issues that come up again and again, and are best answered by examples, rather than comments. We cover:

We plan to publish solutions to new issues as and when they arise. We will also update this content based on your comments. So, let’s get started with pushing stuff.

Vaadin server-side push to the client

Most of the time, the UI of a Vaadin application only updates on client request when the UI is accessible and locked. The client, more or less, pulls the data on user requests. But, there are, of course, a lot of cases, especially if you go reactively, when the server should push data to the client. Vaadin supports this using the WebSocket protocol. The good thing is that the default transport mechanism in Vaadin 14 is WEBSOCKET_XHR: this translates directly to "WebSocket for server to client and XHR for client to server". This means the client to server transport layer does not change and is handled by our Spring Security filter chain.

Let’s start with enabling Push for the MainView:

MainView.java
@Push // (1)
@Route
@PWA(name = "Project Base for Vaadin Flow with Spring", shortName = "Project Base")
public class MainView extends VerticalLayout {
[...]
  1. As stated above, defaults are fine and we can now start pushing data from the server to the client in the main view.

MainView.java
public MainView(@Autowired MessageBean bean) {
    final Button button = new Button("Click me", e -> {
        Notification.show(bean.getMessage());
        final UI ui = UI.getCurrent(); // (1)
        ExecutorService executor = Executors.newSingleThreadExecutor(); // (2)
        executor.submit(() -> {
            doHeavyStuff(); // (3)
            ui.access(() -> { // (4)
                Notification.show("Calculation done"); // (5)
            });
        });
    });
    add(button);
    // simple link to the logout endpoint provided by Spring Security
    Element logoutLink = ElementFactory.createAnchor("logout", "Logout");
    getElement().appendChild(logoutLink);
}
  1. This is probably the trickiest part. We need a reference to the UI, as we are not able to access it from the background thread.

  2. This gets you some backup from Java to execute background tasks.

  3. In this case "heavy stuff" means sleeping for a while.

  4. This allows exclusive access to the UI (no, it’s not the big kernel lock).

  5. Do something that needs the UI for sure.

Wow, that’s it already. You can give it a try by checking out https://github.com/vaadin-learning-center/spring-secured-vaadin/tree/server-push and running the webapp in the usual way.

Can you trust Vaadin Upload?

Yes you can, but only after altering your security configuration. In this section, we cover how to get an initialized security context in the Vaadin Upload component’s listeners. Using the Vaadin Upload component is straight forward:

MainView.java
MemoryBuffer buffer = new MemoryBuffer();
Upload upload = new Upload(buffer);
add(upload);
upload.addSucceededListener(e -> { // (1)
   Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // (2)
   Notification.show(String.format("Upload finished: %s for %sauthenticated user ", e.getFileName(), authentication == null ? "non-" : "")); // (3)
});
  1. Adds the success listener.

  2. Gets the authentication object with plain Spring Security utils. No magic involved.

  3. Shows a message containing the file name and info about the authentication state.

If you run the code without further changes, you would never get an authentication object. This is because our Spring Security configuration ignores requests to /VAADIN completely, so you do not get an initialized security context. To fix this, you need to make sure requests to /VAADIN are also handled by the filter chain:

SecurityConfiguration.java
protected void configure(HttpSecurity http) throws Exception {
    // Not using Spring CSRF here to be able to use plain HTML for the login page
    http.csrf().disable()

    // Register our CustomRequestCache that saves unauthorized access attempts, so
    // the user is redirected after login.
    .requestCache().requestCache(new CustomRequestCache())

    // Restrict access to our application.
    .and().authorizeRequests()

    // Allow all flow internal requests.
    .requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()

    // Vaadin Flow static resources
    // Now, those requests are handled by Spring Security's filter chain which results in a fully initialized
    // security context. This is used in the upload's success listener to do additional authentication checks for example.
    .antMatchers("/VAADIN/**").permitAll() // (1)

    // Allow all requests by logged in users.
    .anyRequest().authenticated()

[...]
  1. Moving the filter for /VAADIN from configure(WebSecurity web) to configure(HttpSecurity http) ensures that Spring Security checks the requests and can initialize the context.

Now, the notification will change and tell you that the authentication object was successfully resolved. The code is here: https://github.com/vaadin-learning-center/spring-secured-vaadin/tree/login-overlay-form-authenticated-upload, try it! Good, let’s move on to the next issue :

Changing the Context Path or just the URL Mapping

First things first! This topic requires some explanation for a better understanding. Context path changes affect the whole application and all its servlets. This means, if you change the context path to /deadbeef, all your servlets, no matter what their own URL mapping is, can only be accessed by URLs starting with /deadbeef. Spring Boot provides the server.servlet.context-path property to configure this. The URL mapping, on the other hand, is set per servlet. As you are using Spring Boot, you would typically use the servlet registration bean’s method to define the URL mapping. As Vaadin registers the servlet on its own, the vaadin.urlMapping configuration property is provided. Again, this only affects the Vaadin servlet. Spring Security, for example, does not know about it and still redirects to /login instead of /<your-vaadin-servlet-mapping>/login. This means you have to update your security configuration if you change the URL mapping. To me, which option to use is a matter of personal preference. Because I prefer to keep life simple, in Spring Boot applications I normally have only one servlet per app. In this scenario, the server.servlet.context-path property is the way to go. But, of course, there are many use-cases in which managing several servlets in one app makes much more sense. Feel free to use whichever approach you want, but do not forget to update your security configuration!

Get me out of here

We always talk about logging in, but never about logging out. From a security point of view, logging out is even more important. You need to get rid of your entire security context, so others cannot (re-)use it. I prefer to use the endpoints provided by Spring Security and the examples below assume this has been defined:

SecurityConfiguration.java
@Override
protected void configure(HttpSecurity http) throws Exception {

    [...]

		// Configure logout
		.and().logout().logoutSuccessUrl(LOGOUT_SUCCESS_URL); // (1)
}
  1. Enables the /logout endpoint and redirects to LOGOUT_SUCCESS_URL afterwards. Check the Spring Security docs for details.

Next, we have to be able to redirect the user to the endpoint. This part is not trivial, as Vaadin’s router is not aware of any view that corresponds to the logout endpoint provided by Spring Security and simply ignores the request. This is actually a good thing, as Spring Security expects a full page request anyway. Luckily, Vaadin provides low-level access and allows you to easily add an anchor element using Java:

MainView.java
public MainView(@Autowired MessageBean bean) {
    [...]
    // simple link to the logout endpoint provided by Spring Security
    Element logoutLink = ElementFactory.createAnchor("logout", "Logout"); // (1)
    getElement().appendChild(logoutLink); // (2)
}
  1. Asks the element factory to create an anchor element.

  2. Adds it to the main view.

The same approach also works for Polymer templates, just add the anchor element declaratively.

Remember Me

In case you want to keep your users logged in, even if the session was destroyed already, Spring Security provides out-of-the-box support for remember me cookies. You just have to activate it in the security configuration:

SecurityConfiguration.java
@Override
protected void configure(HttpSecurity http) throws Exception {

    [...]
    // Configure remember me cookie
    .and().rememberMe().key("pssssst").alwaysRemember(true) // (1)
}
  1. This enables the remember me services provided by Spring Security, defines an application specific key and activates it by default.

And that’s it already!

You may wonder why remember me is always activated. This hides the issue that our current login dialog does not support a remember me checkbox, yet. Please vote! If a checkbox has to be provided, you have to use a custom login form or use the low level API to access and extend the internal form.

Use a Spring Thymeleaf login view

Long time ago, one of our users came up with the following question

[…​] I wonder, does this present a security risk in the form of a DoS vulnerability? Each request creates a Vaadin session, with all the server-side structures (UI, etc) that go along with it, eating memory. For a public site it seems like it would be fairly easy to overwhelm the server with login page requests, no?

— Morgan Pittkin

This bugs me for quite some time now and the only way I can think of to mitigate the issue in the webapp itself, is by not using Vaadin for the login view. Instead, a Thymeleaf based login form is introduced. Besides adding another technology to your techstack this also means you have to style the login dialog on your own.

To get Thymeleaf support we first have to add the needed dependency:

pom.xml
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Secondly, we have to take care of the Web MVC configuration:

WebConfig.java
@EnableWebMvc // (1)
@Component
public class WebConfig implements WebMvcConfigurer {
  @Override
  public void addViewControllers(ViewControllerRegistry registry) { // (2)
    registry.addViewController("/login").setViewName("login"); // (3)
    registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
  }
}
  1. Enables Web MVC.

  2. Instead of implementing a controller class the view controller registry is used.

  3. Registers a view controller on-the-fly, configures its request path and the name of the view to load.

Finally, the login view template needs to be implemented and put into src/main/resources/templates/ folder by convention.

login.html
<html xmlns:th="https://www.thymeleaf.org">
<bod>
  <title>Spring Secured Vaadin</title>
  <div th:fragment="content">
    <form name="f" th:action="@{/login}" method="post">
      <fieldset>
        <legend>Please Login</legend>
        <div th:if="${param.error}" class="alert alert-error">
        Invalid username and password.
        </div>
        <div th:if="${param.logout}" class="alert alert-success">
        You have been logged out.
        </div>
        <label for="username">Username</label>
        <input type="text" id="username" name="username"/>
        <label for="password">Password</label>
        <input type="password" id="password" name="password"/>
        <div class="form-actions">
          <button type="submit" class="btn">Log in</button>
        </div>
      </fieldset>
    </form>
  </div>
</body>
</html>

Done! When accessing the application you get redirected to the new login view provided by Spring MVC instead of loading the Vaadin context. This will only happen after successfully logging in.

Vaadin is an open-source framework offering the fastest way to build web apps on Java backends
GET STARTED

Comments (15)

Stefan Uebe
5 months ago Apr 12, 2021 7:07am
Stefan Uebe
5 months ago Apr 12, 2021 6:35am
Paul Römer
1 year ago Apr 02, 2020 7:49am
Paul Römer
2 years ago Feb 06, 2020 7:14am
Alexander W
2 years ago Feb 06, 2020 8:32am
Paul Römer
2 years ago Feb 06, 2020 10:19am
Paul Römer
2 years ago Feb 05, 2020 2:35pm
Paul Römer
2 years ago Feb 06, 2020 8:45am