Docs

Documentation versions (currently viewingVaadin 25.4 (pre-release))

Securing Spring Boot Applications

Enable and configure security in a Vaadin Flow application using built-in security helpers with Spring Boot.

Vaadin Flow is fully compatible with the most-popular security solutions in the Java ecosystem, including but not limited to Spring Security, Java Authentication and Authorization Service (JAAS), and Apache Shiro.

Although you can choose the security framework you want, Vaadin Flow comes with built-in security helpers that are most conveniently implemented with Spring Security. Using these built-in helpers with Spring Security makes it easier, faster, and safer to secure your Vaadin applications compared to using the Spring Security framework directly — or with any other security framework.

This page focuses on enabling security using the built-in helpers in combination with Spring Boot and Spring Security. For instructions on using the built-in helpers in non-Spring projects, see the Securing Plain Java Applications documentation page.

Introduction

In a Spring Boot application, Vaadin Flow’s built-in security helpers enable a view-based access control mechanism with minimum Spring Security configuration. This mechanism allows you to secure view flexibly in Vaadin Flow applications, based on different access level annotations. Specifically, the view-based access control mechanism uses the @AnonymousAllowed, @PermitAll, @RolesAllowed, and @DenyAll annotations on view classes to define the access control rules.

To enable the mechanism in a Vaadin Flow Spring Boot application without any security, be sure to add the following to your project:

  • Login view;

  • Spring Security dependencies;

  • Log-out capability;

  • Security configuration class that uses VaadinSecurityConfigurer; and

  • One of these annotations on each view class: @AnonymousAllowed, @PermitAll, or @RolesAllowed.

A complete example project, including the source code from this document, is available at github.com/vaadin/flow-crm-tutorial.

Log-in View

A log-in view is a basic requirement of many authentication and authorization mechanisms. It allows you to redirect anonymous users to that page, before allowing them to view any protected resources. Navigation access control always allows navigation to the configured log-in view, so the view doesn’t need an access annotation.

Source code
LoginView.java
@Route(value = "login", autoLayout = false)
@PageTitle("Login")
public class LoginView extends VerticalLayout implements BeforeEnterObserver {

    private LoginForm login = new LoginForm();

    public LoginView() {
        addClassName("login-view");
        setSizeFull();

        setJustifyContentMode(JustifyContentMode.CENTER);
        setAlignItems(Alignment.CENTER);

        login.setAction("login");

        add(new H1("Test Application"), login);
    }

    @Override
    public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
        if(beforeEnterEvent.getLocation()
            .getQueryParameters()
            .getParameters()
            .containsKey("error")) {
            login.setError(true);
        }
    }
}

This example uses Vaadin’s Login Form component for brevity in this implementation. However, there’s no requirement to use it. Use whatever log-in view you prefer.

Setting autoLayout = false keeps the log-in view out of an automatic layout — a class annotated with @Layout — so that the log-in form isn’t shown inside the application’s main layout and navigation.

Spring Security Dependencies

To enable Spring Security, certain dependencies should be added to the project. Since the examples on this page are based on Spring Boot, you would add the following dependency:

Source code
pom.xml
<dependencies>
    <!-- other dependencies -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!-- other dependencies -->
</dependencies>

Security Utilities

To access authenticated user details and to log users out, Vaadin provides an AuthenticationContext component — which is strictly integrated with Spring Security — that can be injected into views and services.

The AuthenticationContext by design does not implement java.io.Serializable. Vaadin view fields referencing this object must be defined transient. The class exposes the following utility methods:

  • isAuthenticated() checks if a user is currently logged in. The Spring Anonymous user is considered not authenticated.

  • getAuthenticatedUser(Class<U> userType) gets user details. If userType doesn’t match the actual user implementation, the method throws a ClassCastException.

  • getGrantedRoles() gets the roles assigned to the user, stripping the role prefix (e.g. ROLE_USER is returned as USER).

  • hasRole(String role), hasAnyRole(String…​ roles), hasAllRoles(String…​ roles) check if the user is assigned to given roles. Roles should be provided without role prefix.

  • getGrantedAuthorities() gets the authorities that have been granted to the user.

  • hasAuthority(String authority), hasAnyAuthority(String…​ authorities), hasAllAuthorities(String…​ authorities) check if the user has been granted certain authorities.

  • logout initiates the Spring Security logout process and redirects the user to the configured logout URL.

To initiate logging out of a web application, you would typically provide a log-out button. Here’s an implementation of a log-out button shown on the header of the main layout that uses the AuthenticationContext component:

Source code
MainLayout.java
public class MainLayout extends AppLayout {

    private final transient AuthenticationContext authContext;

    public MainLayout(AuthenticationContext authContext) {
        this.authContext = authContext;

        H1 logo = new H1("Vaadin CRM");
        logo.addClassName("logo");
        HorizontalLayout
        header =
        authContext.getAuthenticatedUser(UserDetails.class)
                .map(user -> {
                    Button logout = new Button("Logout", click ->
                            this.authContext.logout());
                    Span loggedUser = new Span("Welcome " + user.getUsername());
                    return new HorizontalLayout(logo, loggedUser, logout);
                }).orElseGet(() -> new HorizontalLayout(logo));

        // Other page components omitted.

        addToNavbar(header);
    }
}

The same component also covers programmatic access checks elsewhere in the application, such as deciding whether to render a navigation item or to enable an action. Roles are given without the role prefix, which AuthenticationContext adds when comparing against the granted authorities (see Roles & the Role Prefix). An application migrating from a custom security mechanism therefore doesn’t need a security utility class of its own:

Source code
Java
SideNav nav = new SideNav();
nav.addItem(new SideNavItem("Dashboard", DashboardView.class));
if (authContext.hasRole("ADMIN")) { // Not "ROLE_ADMIN".
    nav.addItem(new SideNavItem("Administration", AdminView.class));
}

Hiding a navigation item is a usability measure, not a security measure. The view it points to still needs its own access annotation, since a user can navigate there by entering the URL directly.

Custom Security Utilities

AuthenticationContext covers what most applications need. If you still want a security utility of your own — for example, to combine the user lookup with application-specific logic, such as loading your own user entity from a repository — you can build it directly on the Spring Security API. Here’s a basic example that shows the underlying calls:

Source code
SecurityService.java
@Component
public class SecurityService {

    private static final String LOGOUT_SUCCESS_URL = "/";

    public Optional<UserDetails> getAuthenticatedUser() {
        Authentication authentication = SecurityContextHolder.getContext()
                .getAuthentication();
        if (authentication != null
                && authentication.getPrincipal() instanceof UserDetails userDetails) {
            return Optional.of(userDetails);
        }
        // Anonymous or no authentication.
        return Optional.empty();
    }

    public void logout() {
        UI.getCurrentOrThrow().getPage().setLocation(LOGOUT_SUCCESS_URL);
        SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
        logoutHandler.logout(
                VaadinServletRequest.getCurrent().getHttpServletRequest(), null,
                null);
    }
}

You can inject the service into views in the same way as AuthenticationContext. Unlike AuthenticationContext.logout(), the logout() method in this example only clears the security context and redirects to a fixed URL. It doesn’t run the other logout handlers or the logout success URL configured for Spring Security.

Security Configuration Class

The next step is to have a Spring Security configuration class that uses VaadinSecurityConfigurer. There’s no convention for naming this class, so here it’s named SecurityConfiguration. However, take care with Spring Security annotations.

This is a minimal implementation of such a class:

Source code
SecurityConfiguration.java
@EnableWebSecurity 1
@Configuration
public class SecurityConfiguration {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // Configure your static resources with public access
        http.authorizeHttpRequests(auth -> auth.requestMatchers("/public/**")
            .permitAll());

        // Configure Vaadin's security using VaadinSecurityConfigurer
        http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { 2
            // This is important to register your login view to the
            // navigation access control mechanism:
            configurer.loginView(LoginView.class); 3

            // You can add any possible extra configurations of your own
            // here (the following is just an example):
            // configurer.enableCsrfConfiguration(false);
        });

        return http.build();
    }

    /**
     * Demo UserDetailsManager which only provides two hardcoded
     * in memory users and their roles.
     * NOTE: This shouldn't be used in real world applications.
     */
    @Bean
    public UserDetailsManager userDetailsService() {
        UserDetails user =
                User.withUsername("user")
                        .password("{noop}user")
                        .roles("USER")
                        .build();
        UserDetails admin =
                User.withUsername("admin")
                        .password("{noop}admin")
                        .roles("ADMIN")
                        .build();
        return new InMemoryUserDetailsManager(user, admin);
    }
}
SecurityConfiguration.java
  1. Enables Spring Security’s web security support. Together with @Configuration, it makes Spring pick up the security filter chain defined in the class.

  2. Applies the Vaadin-specific Spring Security configuration.

  3. Registers the log-in view with Spring Security’s form login and with navigation access control.

VaadinSecurityConfigurer is a helper class that configures the common Vaadin-related Spring Security settings. By using it, the view-based access control mechanism is enabled automatically, and no further configuration is needed.

The VaadinSecurityConfigurer handles all of the Vaadin-related configuration. For example, it permits access to Vaadin’s static resources, enables CSRF checking while ignoring unnecessary checking for Vaadin internal requests, and configures proper exception handling for Vaadin applications.

The log-in view can be configured via the provided loginView() method on the configurer.

Warning
Configure Form Login Through the Configurer

Configure form login with loginView() rather than by calling Spring Security’s formLogin() directly. The configurer applies FormLoginConfigurer internally, and at the same time permits Vaadin’s internal requests, ignores them in the CSRF check, installs a Vaadin-aware request cache and success handler, and registers the log-in view with navigation access control.

A hand-written formLogin() configuration does none of that. The internal request that the log-in view itself makes to the server is then treated as an unauthenticated request and redirected back to the log-in view, which produces an endless redirect loop — typically visible as an ERR_TOO_MANY_REDIRECTS page or a "Connection lost" notification instead of a working log-in form.

Note
Blocked Sub-Resources Answer With 401
With a log-in view configured, an unauthorized request that the browser makes for a sub-resource — a stylesheet, script, image, font, or web app manifest — is answered with 401 Unauthorized rather than redirected to the log-in view, which such a request can’t render. The browser then reports the resource that was blocked, instead of failing in a redirect loop. Permit the path in the security configuration to serve the resource; see Spring Security and StyleSheet.
Warning
Never Use Hard-Coded Credentials in Production
The implementation of the userDetailsService() method is just an in-memory implementation for the sake of brevity in this documentation. In a normal application, you can change the Spring Security configuration to use an authentication provider for Lightweight Directory Access Protocol (LDAP), JAAS, and other real-world sources. See the Spring Security authentication documentation to read more about them.

The most important configuration in the previous example is the call to configurer.loginView(LoginView.class). This is how the view-based access control mechanism knows where to redirect users when they try to navigate to a protected view.

The log-in view is always accessible to anonymous users: navigation access control allows navigation to the configured log-in view regardless of its access annotations, and the configurer permits HTTP requests to the log-in page. The log-in view therefore doesn’t need the @AnonymousAllowed annotation.

For additional information about navigation access control, consult the related documentation.

Once the LoginView is ready, and you’ve set it as the log-in view in the security configuration, you’re ready to move ahead and see how the security annotations work on the views.

Annotating View Classes

Before providing some usage examples of access annotations, it would be good to have a closer look at the annotations and their meaning when applied to a view:

  • @AnonymousAllowed permits anyone to navigate to a view without any authentication or authorization.

  • @PermitAll allows any authenticated user to navigate to a view.

  • @RolesAllowed grants access to users having the roles specified in the annotation value.

  • @DenyAll disallows everyone from navigating to a view. This is the default; if a view isn’t annotated, the @DenyAll logic is applied.

When using VaadinSecurityConfigurer, Vaadin’s SpringSecurityAutoConfiguration comes into play and enables the navigation access control mechanism with view-based security. Therefore, none of the views are accessible until one of these annotations is applied to them — except @DenyAll.

Below is an example using @AnonymousAllowed to enable all users to navigate to this view:

Source code
Java
@Route(value = "", layout = MainView.class)
@PageTitle("Public View")
@AnonymousAllowed
public class PublicView extends VerticalLayout {
    // ...
}

This next example is using @PermitAll to allow only authenticated users — with any role — to navigate to this view:

Source code
Java
@Route(value = "private", layout = MainView.class)
@PageTitle("Private View")
@PermitAll
public class PrivateView extends VerticalLayout {
    // ...
}

This example is using @RolesAllowed to enable only the users with ADMIN role to navigate to this view:

Source code
Java
@Route(value = "admin", layout = MainView.class)
@PageTitle("Admin View")
@RolesAllowed("ADMIN") // <- Should match one of the user's roles (case-sensitive)
public class AdminView extends VerticalLayout {
    // ...
}

Roles & the Role Prefix

Spring Security distinguishes between authorities, which are arbitrary strings, and roles, which are authorities carrying a prefix. The default prefix is ROLE_. Role names in @RolesAllowed are given without the prefix, and the prefix is added when the granted authorities are checked. So @RolesAllowed("ADMIN") matches the granted authority ROLE_ADMIN.

This matters when writing a custom UserDetailsService or authentication provider, since the authorities have to be created with the prefix included:

Source code
Java
List<GrantedAuthority> authorities = user.getRoles().stream()
        .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
        .collect(Collectors.toList());

The roles() builder method of Spring’s User class adds the prefix for you, whereas authorities() doesn’t. That’s why the examples on this page use User.withUsername("admin").roles("ADMIN") to grant the ROLE_ADMIN authority.

Warning
Missing Prefixes Fail Silently
If the authority is stored as ADMIN instead of ROLE_ADMIN, no error is reported. The role check never matches: views annotated with @RolesAllowed("ADMIN") become inaccessible, and any menu item shown conditionally on that role disappears. If a role check doesn’t behave as expected, log AuthenticationContext.getGrantedAuthorities() and confirm that the authorities carry the prefix.

To use a prefix other than ROLE_, or none at all, define a GrantedAuthorityDefaults bean. Vaadin picks up the configured prefix and applies it to the access annotations and to the role-checking methods of AuthenticationContext:

Source code
Java
@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
    return new GrantedAuthorityDefaults(""); // No prefix.
}

Annotation Inheritance & Overrides

As shown in the example here, the security annotations are inherited from the closest parent class that has them. Annotating a child class overrides any inherited annotations. Interfaces aren’t checked for annotations, only classes.

Source code
Java
@RolesAllowed("ADMIN")
public abstract class AbstractAdminView extends VerticalLayout {
    // ...
}

@Route(value = "user-listing", layout = MainView.class)
@PageTitle("User Listing")
public class UserListingView extends AbstractAdminView {
    // ...
}

Security annotations on parent layouts are also checked. This applies to auto-layouts (i.e., classes annotated with @Layout), to parent layouts set via the layout attribute in @Route, and to layouts defined with @ParentLayout. If a parent layout lacks an access annotation, the default @DenyAll logic is applied to the layout, and access to views within it is denied. Both the layout and the view must independently grant access for navigation to succeed. If multiple annotations are specified on a single view class, the following rules are applied:

  • DenyAll overrides other annotations;

  • AnonymousAllowed overrides RolesAllowed, as well as PermitAll; and

  • RolesAllowed overrides PermitAll.

Specifying more than one of the above access annotations on a view class isn’t recommended. Besides the fact that there’s probably no logical reason to do so, it would be confusing.

Authenticated User Information

To access the authenticated user’s information (e.g., name, email and roles), Vaadin Flow provides the AuthenticationContext class that can be used to retrieve this information. AuthenticationContext is a Spring bean that can be injected as a view constructor argument. And AuthenticationContext can be useful for rendering the UI differently based on the user’s roles.

The following example shows how to use AuthenticationContext to retrieve the authenticated user’s information and render a button only if the user has the ADMIN role:

Source code
Java
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.auth.AnonymousAllowed;
import com.vaadin.flow.spring.security.AuthenticationContext;
import org.springframework.security.core.userdetails.UserDetails;

@Route(value = "my-view")
@AnonymousAllowed
public class MyView extends VerticalLayout {

    public MyView(AuthenticationContext authContext) {

        add(new H2("Everyone can see this"));

        // Ensure that the class used by getAuthenticatedUser() matches the object type created
        // by the authentication providers used in Spring Security.
        authContext.getAuthenticatedUser(UserDetails.class).ifPresent(user -> {
            add(new H2("Welcome " + user.getUsername()));
            if (authContext.hasRole("ADMIN")) { // Not "ROLE_ADMIN".
                add(new Button("Admin button"));
            } else {
                add(new H2("You are not an admin"));
            }
        });
    }
}

Error Messages for Unauthorized Views

If the user is already authenticated and tries to navigate to a view for which they don’t have permission, an error message is displayed. The message depends on the application mode.

In development mode, Vaadin shows the RouteAccessDeniedError view, which shows an Access Denied message with a list of available routes. In production mode, Vaadin shows the RouteAccessDeniedError view, which shows by default a message that reads, Could Not Navigate to 'RequestedRouteName'. As a security precaution, the message won’t say whether the navigation target exists.

The RouteAccessDeniedError is not by default a view, but a reroute to the RouteNotFoundError view for better backwards compatibility.

Custom Error Messages for Unauthorized Views

Vaadin shows by default the RouteAccessDeniedError view for unauthorized views. This can be customized in the following ways:

  • Providing custom implementation by overriding RouteAccessDeniedError class;

  • Providing custom implementation by implementing HasErrorParameter<AccessDeniedException> interface; and

  • Rerouting to a different error type with @AccessDeniedErrorRouter annotation.

The following is one example of this approach, using a custom error by overriding a class:

Source code
Java
public class CustomAccessDeniedError extends RouteAccessDeniedError {
    @Override
    public int setErrorParameter(BeforeEnterEvent event,
            ErrorParameter<AccessDeniedException> parameter) {
        getElement().setText("Nothing to see here, please move on");
        return HttpStatusCode.FORBIDDEN.getCode();
    }
}

This next example provides a custom error by implementing an interface:

Source code
Java
@Tag(Tag.DIV)
@PermitAll
public static class CustomAccessDeniedError extends Component
        implements HasErrorParameter<AccessDeniedException> {
    @Override
    public int setErrorParameter(BeforeEnterEvent event,
            ErrorParameter<AccessDeniedException> parameter) {
        getElement().setText("Access denied.");
        return HttpStatusCode.FORBIDDEN.getCode();
    }
}

HasErrorParameter error view needs an access control annotation, so that Vaadin allows navigation to it. The example above uses @PermitAll, but @RolesAllowed can also be used. @AnonymousAllowed isn’t recommended, as it exposes information about access restrictions to the anonymous users.

The same applies to custom RouteNotFoundError subclasses — they also need a security annotation to be accessible. Note that, for a not-found view, the annotation alone isn’t enough: requests for non-existing pages are denied at the HTTP layer unless the catch-all request rule is also relaxed. See Router Exception Handling.

If you want to reroute to a different error type, you would do something like the following example. It reroutes unauthorized administrative views to the RouteNotFoundError view, which is the default view for NotFoundException type.

Source code
Java
@Route(value = "admin", layout = MainView.class)
@PageTitle("Admin View")
@RolesAllowed("ADMIN")
@AccessDeniedErrorRouter(rerouteToError = NotFoundException.class)
public class AdminView extends VerticalLayout {
    // ...
}

AccessDeniedErrorRouter annotation redirects by default to AccessDeniedException, if not changed. Annotation is to be used together with @Route, or if present, together with access annotation: @AnonymousAllowed, @PermitAll, @RolesAllowed, or @DenyAll.

The Navigation Access Control feature allows mixing any of the view access annotations with Spring’s URL-pattern-based HTTP security — which possibly exists in older Vaadin Spring Boot applications. However, it may result in extra configuration, since the security annotation on the views and the URL-pattern-based rules must be consistent.

URL-based security can be enabled by activating the RoutePathAccessChecker component provided by Flow. To activate it, you need to define a NavigationAccessControlConfigurer bean in your Spring configuration, creating a new instance of that class and calling the withRoutePathAccessChecker() method.

Source code
Activate Both Annotated Views and URL-Pattern-Based Security
@Bean
NavigationAccessControlConfigurer navigationAccessControlConfigurer() {
    return new NavigationAccessControlConfigurer()
            .withAnnotatedViewAccessChecker() 1
            .withRoutePathAccessChecker();    2
}
  1. Activation of annotation base view access checker.

  2. Activation of Spring URL-pattern-based checker.

Important
If you add the bean definition method in the same configuration class where you use VaadinSecurityConfigurer, the method must be declared static, to prevent bootstrap errors because of circular dependencies in bean definitions.

You can also activate only the RoutePathAccessChecker, if you prefer to centralize the authorization configuration by only using the Spring Security URL-pattern-based security feature.

Source code
Activate Only URL-Pattern-Based Security
@Bean
NavigationAccessControlConfigurer navigationAccessControlConfigurer() {
    return new NavigationAccessControlConfigurer()
            .withRoutePathAccessChecker();
}

For more information about navigation access control consult the related documentation.

Vaadin strongly recommends not to mix Spring’s URL-pattern-based HTTP security and this view-based access control mechanism targeting the same views. Doing so might cause unwanted access configurations, and would be an unnecessary complication in the authorization of views.

Testing Access Control

Browserless tests support Spring Security, so you can test view access control without starting a browser or a server. Annotate a test with Spring Security’s @WithMockUser or @WithAnonymousUser to navigate as a user with given roles, or as an anonymous user, and assert which view is shown. See Test View Access Control for details.

To test services that are protected with Spring method security, see Testing Method Security.

Spring Concurrency Support with Vaadin

Spring Security provides built-in concurrency support to propagate security contexts across asynchronous operations. One of the key components for this is DelegatingSecurityContextExecutor, which wraps an Executor and ensures that the SecurityContext is properly propagated to background tasks.

Vaadin provides a SecurityContextHolderStrategy bean that is aware of the Vaadin session, and installs it in Spring Security’s SecurityContextHolder only after all singleton beans have been created. A DelegatingSecurityContextExecutor that is itself defined as a bean is created before that, so it would pick up Spring Security’s default strategy. Inject the strategy into the bean method and wire it into the executor explicitly:

Source code
Java
@Bean
public DelegatingSecurityContextAsyncTaskExecutor taskExecutor(
        SecurityContextHolderStrategy strategy) {
    var delegate = new ThreadPoolTaskExecutor();
    // Configure the executor
    delegate.initialize();

    var executor = new DelegatingSecurityContextAsyncTaskExecutor(delegate);
    executor.setSecurityContextHolderStrategy(strategy);
    return executor;
}

This ensures that the correct security context holder is used for asynchronous task execution in a Vaadin application.

Spring Impersonation with Vaadin

A common use case for secured applications is when a user has a problem and the admin or super user wants to log in as the troubled user to see things from their perspective to better assist them. It can be helpful for customer support analysis when the support person can access the system as the actual user.

An insecure solution — which is basically a security breach — would be to ask the customer to provide their password, or for the administrator to retrieve it from the system. Neither of these methods should be used. Instead, Spring Security has an impersonation feature.

By using impersonation, the system knows who has really logged in. Therefore, it can track an impersonator’s actions for an audit log, if there is one in place.

To add impersonation to a Vaadin application, add Spring Security’s SwitchUserFilter to the security filter chain, and secure the impersonation endpoints with the appropriate request matchers:

Source code
Java
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http,
        UserDetailsService userDetailsService,
        SecurityContextHolderStrategy strategy,
        ApplicationEventPublisher eventPublisher) throws Exception {
    var matchers = PathPatternRequestMatcher.withDefaults();

    var switchUserFilter = new SwitchUserFilter();
    switchUserFilter.setSecurityContextHolderStrategy(strategy);
    switchUserFilter.setUserDetailsService(userDetailsService);
    switchUserFilter.setSwitchUserMatcher(
            matchers.matcher(HttpMethod.GET, "/impersonate"));
    switchUserFilter.setExitUserMatcher(
            matchers.matcher(HttpMethod.GET, "/impersonate/exit"));
    switchUserFilter.setTargetUrl("/");
    switchUserFilter.setApplicationEventPublisher(eventPublisher);
    switchUserFilter.afterPropertiesSet();
    http.addFilterAfter(switchUserFilter, AuthorizationFilter.class);

    // Authorize impersonation for admin/impersonated admin
    http.authorizeHttpRequests(auth -> auth
            .requestMatchers("/impersonate").hasAnyRole("ADMIN", "PREVIOUS_ADMINISTRATOR")
            .requestMatchers("/impersonate/exit").hasRole("PREVIOUS_ADMINISTRATOR"));

    // Configure Vaadin's security using VaadinSecurityConfigurer
    http.with(VaadinSecurityConfigurer.vaadin(), configurer -> {
        configurer.loginView(LoginView.class);
    });

    return http.build();
}

The filter is added after the AuthorizationFilter, so that the authorization rules are checked before a user is switched. Don’t declare the filter as a bean of its own: Spring Boot would then also register it as a servlet filter outside the security filter chain. Since the filter isn’t a bean, call afterPropertiesSet() yourself — it creates the success and failure handlers — and pass it the ApplicationEventPublisher, so that it publishes an AuthenticationSwitchUserEvent for your audit log whenever a user is switched.

This allows users with the ADMIN role or when impersonating admin to call the impersonate endpoint.

To impersonate a user, a call to the impersonate URL needs to be done.

Source code
Java
Button impersonate = new Button("Impersonate user", e ->
  getUI().ifPresent(ui -> ui.getPage().setLocation("/impersonate?username=user"))
);

This has the admin impersonate the user account without having to know their password.

To exit impersonation, call the exit URL like this:

Source code
Java
Button exitImpersonation = new Button("Exit impersonation", e ->
  getUI().ifPresent(ui -> ui.getPage().setLocation("/impersonate/exit"))
);

4C8D835D-4E6E-4D81-BEA1-A865FEB17BAD