Paul Römer

Setting up Spring Security for Vaadin applications

Warning
There was a security issue in the first version of the tutorial: The navigation listener that checks permissions for router navigation was missing. This will allow attackers to manipulate your login view to trigger router navigation and access secured pages! Please, check your code if an UI listener is registered as explained here.

After discussing the goals and setting up the project base, we can finally start with the actual work!

Enable Spring Security

First, we have to add the needed Spring Security dependencies to our POM:

pom.xml
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
</dependency>

Second, we have to disable Spring’s MVC error handler. Since Vaadin 14 it causes strange reload behavior. Check this forum thread for details. Thank you all, for fighting this bug down!

Application.java
@SpringBootApplication(exclude = ErrorMvcAutoConfiguration.class)
public class Application {

Third, we will add a Vaadin aware Spring Security configuration via the SecurityConfiguration class that uses some helpers you can check in the sources.

SecurityConfiguration.java
/**
 * Require login to access internal pages and configure login form.
 */
@Override
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() // (1)

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

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

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

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

            // Configure the login page.
            .and().formLogin().loginPage(LOGIN_URL).permitAll() // (5)
            .loginProcessingUrl(LOGIN_PROCESSING_URL) // (6)
            .failureUrl(LOGIN_FAILURE_URL)

            // Configure logout
            .and().logout().logoutSuccessUrl(LOGOUT_SUCCESS_URL);
}
  1. Vaadin has built-in Cross-Site Request Forgery already.

  2. We add a customized request cache to filter out framework internal request. Check CustomRequestCache implementation for details.

  3. Permits a set of Vaadin related request types (check SecurityUtils for details).

  4. Force authentication for all views.

  5. Configure the URL to the login page for redirects and permit access to everyone.

  6. Configure the login URL Spring Security is expecting POST requests to (form submit).

Next, we have to make sure that resources Vaadin needs are bypassed and not affected by our security configuration above:

SecurityConfiguration.java
/**
 * Allows access to static resources, bypassing Spring security.
 */
@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers(
            // Vaadin Flow static resources // (1)
            "/VAADIN/**",

            // the standard favicon URI
            "/favicon.ico",

            // the robots exclusion standard
            "/robots.txt",

            // web application manifest // (2)
            "/manifest.webmanifest",
            "/sw.js",
            "/offline-page.html",

            // (development mode) static resources // (3)
            "/frontend/**",

            // (development mode) webjars // (3)
            "/webjars/**",

            // (production mode) static resources // (4)
            "/frontend-es5/**", "/frontend-es6/**");
}
  1. Mandatory.

  2. Needed only when developing a Progressive Web Application.

  3. Allows access to frontend resources in development mode.

  4. Grants access to all bundled resources. This is important for your login view (if a Polymer template needs to be accessed) or for every other public page.

Secure Router Navigation

Finally, we have to secure router navigation by registerring a before navigation listener that checks for permissions. This is very important as Spring Security is not aware of the single page application behavior of a Vaadin application. That means AJAX requests as they are done during navigation via router links are not protected by the default Spring Security filters.

ConfigureUIServiceInitListener.java
@Component // (1)
public class ConfigureUIServiceInitListener implements VaadinServiceInitListener { // (1)

    @Override
    public void serviceInit(ServiceInitEvent event) {
        event.getSource().addUIInitListener(uiEvent -> {
        final UI ui = uiEvent.getUI();
        ui.addBeforeEnterListener(this::beforeEnter); // (2)
        });
    }

    /**
     * Reroutes the user if (s)he is not authorized to access the view.
     *
     * @param event
     *            before navigation event with event details
     */
    private void beforeEnter(BeforeEnterEvent event) {
        if (!LoginView.class.equals(event.getNavigationTarget()) // (3)
            && !SecurityUtils.isUserLoggedIn()) { // (4)
            event.rerouteTo(LoginView.class); // (5)
        }
    }
}
  1. Allows adding the navigation listener globally to all UI instances by using a service init listener. Spring takes care of registering it.

  2. Adds the before enter listener.

  3. Ignores the login view itself.

  4. Only redirects if user is not logged in. See below.

  5. Actual rerouting the login view if needed.

SecurityUtils.java
static boolean isUserLoggedIn() {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // (1)
    return authentication != null // (2)
            && !(authentication instanceof AnonymousAuthenticationToken) // (3)
            && authentication.isAuthenticated(); // (4)
}
  1. Gets the authentication token from the security context.

  2. Fail if no authentication is available.

  3. Fail for anonymous authentication tokens. Spring Security will add this type of token if all other authentication mechanism failed by default.

  4. Fail if the authentication token is available but is not authenticated.

Once again, run mvn spring-boot:run to build and start the web application and notice the redirection to /login. So far, so good.

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

Comments (48)

Tatu Lund
11 months ago Oct 16, 2020 11:58am
Ml LL
11 months ago Oct 16, 2020 2:39pm
Mark HM
2 years ago Feb 24, 2020 4:13pm
Walid Haider
1 year ago Jul 08, 2020 6:12am
Mark HM
1 year ago Jul 08, 2020 7:39am
Walid Haider
1 year ago Jul 08, 2020 5:21pm
Mark HM
1 year ago Jul 08, 2020 5:27pm
Walid Haider
1 year ago Jul 08, 2020 11:39pm
Mark HM
1 year ago Jul 09, 2020 6:07am
Walid Haider
1 year ago Jul 09, 2020 2:17pm
Mark HM
1 year ago Jul 09, 2020 2:45pm
Paul Römer
2 years ago Jul 09, 2019 7:15pm
Lars
1 year ago May 04, 2020 2:02pm
Paul Römer
1 year ago Apr 02, 2020 7:57am
Paul Römer
2 years ago Feb 04, 2020 7:57am
Paul Römer
2 years ago Dec 06, 2019 4:22pm
Mark HM
2 years ago Dec 09, 2019 7:36am
Paul Römer
2 years ago Jul 09, 2019 7:17pm
Paul Römer
2 years ago Dec 06, 2019 4:18pm
uros kristan
2 years ago Oct 06, 2019 6:46pm
Paul Römer
2 years ago Dec 06, 2019 4:13pm
Centurion Dobrius
2 years ago Nov 29, 2019 11:25am
Paul Römer
2 years ago Nov 18, 2019 12:33pm
Daniel Schumacher
2 years ago Sep 16, 2019 2:00pm
Paul Römer
2 years ago Sep 16, 2019 7:38pm
Paul Römer
2 years ago Aug 20, 2019 7:26am
Paul Römer
2 years ago Jun 12, 2019 6:45am