Vaadin 24 Spring Security

I am struggling to get my Spring Security stuff working properly. The behavior I’m going for is to have my app logoff after the session expires, but for some reason I’m having so many problems. I’m using SystemMessagesProvider to display a message to the user and afterwards, redirect to a logout view.

When a session expires, a new one pops up right away, so the user is always logged on, which is not what I want at all. To compensate, I have my logout view log off and redirect to my “/”.

If someone has an elegant Vaadin 24 Spring Security solution that you wouldn’t mind sharing with me, I would so very much appreciate it. I’ve spent days spinning wheels with this. I’m open to try anything to get passed this.

Cheers! Thank you very much,
Clint

When the session times out, the messsage I intended is displayed: “Your session has expired. Click here to sign back in”

But, when you click, a message of “Internal error, Please notify the administrator” pops up, along with a null pointer exception in the logs with the message:

Cannot invoke “com.vaadin.flow.component.UI.getInternals()” because the return value of “com.vaadin.flow.component.UI.getCurrent()” is null

I’m pretty sure the the LogoutView’s call in onAttach to attachEvent.getUI().navigate(“/”) is the culprit.

Some of my code:

/**
 * Customizes Vaadin system messages, specifically handling session expiration
 * by redirecting the user to the home page.
 */
@Component
public class ExpiredAppSystemMessagesProvider implements SystemMessagesProvider {

    @Override
    public SystemMessages getSystemMessages(SystemMessagesInfo systemMessagesInfo) {
        CustomizedSystemMessages messages = new CustomizedSystemMessages();

        // Session expiration settings
        messages.setSessionExpiredCaption("Your session has expired.");
        messages.setSessionExpiredMessage("Click here to sign back in.");
        messages.setSessionExpiredURL("/logout");
        messages.setSessionExpiredNotificationEnabled(true);

        return messages;
    }
}
/**
 * Configuration for Vaadin-specific settings.
 * Registers the ExpiredAppSystemMessagesProvider to handle session timeout redirects.
 */
@SpringComponent
public class VaadinConfig implements VaadinServiceInitListener {

    private final ExpiredAppSystemMessagesProvider systemMessagesProvider;

    @Autowired
    public VaadinConfig(ExpiredAppSystemMessagesProvider systemMessagesProvider) {
        this.systemMessagesProvider = systemMessagesProvider;
    }

    @Override
    public void serviceInit(ServiceInitEvent event) {
        // Register the system messages provider to handle session timeout redirects
        event.getSource().setSystemMessagesProvider(systemMessagesProvider);
    }
}
/**
 * View for handling user logout.
 * This view automatically logs out the current user and redirects to the home page.
 */
@PageTitle("Logout")
@Route(value = "logout")
@RolesAllowed("USER")
public class LogoutView extends VerticalLayout {
    private static final Logger logger = LoggerFactory.getLogger(LogoutView.class);

    private final LogoutService logoutService;

    public LogoutView(LogoutService logoutService) {
        this.logoutService = logoutService;
    }

    @Override
    protected void onAttach(AttachEvent attachEvent) {
        super.onAttach(attachEvent);

        // Call logout method
        logoutService.logout();

        attachEvent.getUI().navigate("/");
        
        logger.info("User logged out and redirected to home page");
    }
}
/**
 * View for handling user logout.
 * This view automatically logs out the current user and redirects to the home page.
 */
@PageTitle("Logout")
@Route(value = "logout")
@RolesAllowed("USER")
public class LogoutView extends VerticalLayout {
    private static final Logger logger = LoggerFactory.getLogger(LogoutView.class);

    private final LogoutService logoutService;

    public LogoutView(LogoutService logoutService) {
        this.logoutService = logoutService;
    }

    @Override
    protected void onAttach(AttachEvent attachEvent) {
        super.onAttach(attachEvent);

        // Call logout method
        logoutService.logout();

        attachEvent.getUI().navigate("/");

        logger.info("User logged out and redirected to home page");
    }
}

Two things:

  • your security configuration is missing
  • I doubt that you need a Route for logout, let spring handle it

Here’s my Security Config:

/**
 * Security configuration for the application.
 * Extends VaadinWebSecurity to integrate with Vaadin's security features.
 */
@EnableWebSecurity
@Configuration
public class SecurityConfig extends VaadinWebSecurity {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * Session registry to track active sessions
     */
    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }

    /**
     * Required for session management
     */
    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }

    /**
     * Exposes the AuthenticationManager as a bean to be used by the AuthenticationService
     */
    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class).build();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // Allow access to static resources
        http.authorizeHttpRequests(
                authorize -> authorize.requestMatchers(new AntPathRequestMatcher("/images/*.png")).permitAll());

        http.authorizeHttpRequests(
                authorize -> authorize.requestMatchers(new AntPathRequestMatcher("/icons/*")).permitAll());

        http.authorizeHttpRequests(
                authorize -> authorize.requestMatchers(new AntPathRequestMatcher("/logo/*")).permitAll());

        http.authorizeHttpRequests(
                authorize -> authorize.requestMatchers(new AntPathRequestMatcher("/frontend/images/**")).permitAll());

        // Icons from the line-awesome addon
        http.authorizeHttpRequests(authorize -> authorize
                .requestMatchers(new AntPathRequestMatcher("/line-awesome/**/*.svg")).permitAll());

        // Configure session management to allow only one session per user
        http.sessionManagement(sessionManagement -> sessionManagement
                .maximumSessions(1)
                .sessionRegistry(sessionRegistry())
                .expiredUrl("/logout") // Redirect to home page when session expires
        );

        // This needs to come BEFORE the super.configure(http) call
        setLoginView(http, LoginView.class, "/logout");

        super.configure(http); // Vaadin's default security configurations

        // Configure form login - make sure this comes AFTER super.configure
        http.formLogin(formLogin ->
                formLogin
                        .defaultSuccessUrl("/", true) // Force redirect to root on successful login
        );

        // Configure logout to redirect to home page
        http.logout(logout -> 
                logout
                        .logoutSuccessUrl("/logout") // Redirect to home page after logout
        );
    }
}

Your security configuration looks weird to me. Like said previously, remove your logout-Route and change your security configuration to have / or /login as your success destination. Currently you have a weird endless loop