Using custom NavigationAccessChecker breaks servlet

Hi,
I’m currently testing Vaadin in combination with Spring Boot and Spring Security. I’ve been following the docs so far, but stumbled upon an issue with configuring the NavigationAccessControlConfigurer:

@Configuration
@EnableWebSecurity
@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class)
public class WebSecurityConfiguration {

    @Bean
    static NavigationAccessControlConfigurer navigationAccessControlConfigurer() {
        return new NavigationAccessControlConfigurer()
                .withRoutePathAccessChecker()
                .withNavigationAccessChecker(new MyCustomNavigationAccessChecker());
    }

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http, RouteUtil routeUtil) throws Exception {
        return http.authorizeHttpRequests(registry -> {
            registry.requestMatchers(routeUtil::isRouteAllowed).permitAll();
        }).with(VaadinSecurityConfigurer.vaadin(), vaadin -> {

        }).build();
    }

    @Bean
    public UserDetailsManager userDetailsManager() {
        return new InMemoryUserDetailsManager(...);
    }

}

The MyCustomNavigationAccessChecker does not contain any logic whatsoever:

@Override
public AccessCheckResult check(NavigationContext context) {
    LOGGER.info("check access for {}", context);
    if (context.isErrorHandling()) {
        return AccessCheckResult.neutral();
    }
    return AccessCheckResult.allow();
}

Using the custom checker, accessing the webserver fails with an exception:

2025-09-17T21:18:05.589+02:00 ERROR 12220 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception

java.lang.UnsupportedOperationException: public abstract java.security.Principal jakarta.servlet.http.HttpServletRequest.getUserPrincipal() is not supported
	at org.springframework.security.web.FilterInvocation$UnsupportedOperationExceptionInvocationHandler.invoke(FilterInvocation.java:331) ~[spring-security-web-6.5.3.jar:6.5.3]
	at jdk.proxy2/jdk.proxy2.$Proxy177.getUserPrincipal(Unknown Source) ~[na:na]
	at jakarta.servlet.http.HttpServletRequestWrapper.getUserPrincipal(HttpServletRequestWrapper.java:181) ~[tomcat-embed-core-10.1.44.jar:6.0]
	at jakarta.servlet.http.HttpServletRequestWrapper.getUserPrincipal(HttpServletRequestWrapper.java:181) ~[tomcat-embed-core-10.1.44.jar:6.0]
	at com.vaadin.hilla.route.RouteUtil.filterClientViews(RouteUtil.java:79) ~[hilla-endpoint-24.9.0.jar:na]
	at com.vaadin.hilla.route.RouteUtil.getRouteData(RouteUtil.java:145) ~[hilla-endpoint-24.9.0.jar:na]
	at com.vaadin.hilla.route.RouteUtil.isRouteAllowed(RouteUtil.java:71) ~[hilla-endpoint-24.9.0.jar:na]
	at org.springframework.security.web.util.matcher.RequestMatcher.matcher(RequestMatcher.java:48) ~[spring-security-web-6.5.3.jar:6.5.3]

As soon as I remove the .withNavigationAccessChecker(...) I can load pages again.

Is this something I did wrong and just couldn’t find in the docs?

The issue seems not to be with the custom access checker but with the RoutePathAccessChecker that in turn uses Spring WebInvocationPrivilegeEvaluator to check if a Flow or Hilla route path is accessible or not. For some reason, the privilege evaluator implementation does not support the getUserPrincipal() method.

This issue is probably not caught in the Vaadin Flow repository (where RoutePathAccessChecker is defined). This is because it is not aware of Hilla; thus the routeUtil.isRouteAllowed (or better RequestUtil.isAllowedHillaView, that is automatically set by VaadinSecurityConfigurer, so no need to add routeUtil::isRouteAllowed matcher manually) is never called.

Do you mind opening an issue on the Vaadin Flow repository?

1 Like

A potential solution in Flow could be to wrap the request and override getUserPrincipal to return AuthenticationUtil.getSecurityHolderAuthentication() if the original method throws UnsupportedOperationException.

1 Like

Oh yeah, right - seems like the RoutePathAccessChecker is actually at fault. I did test several combinations yesterday and it somehow worked with the RPAC, but not with the custom checker. Might have been an issue with the hot reloading, as that’s no longer the case. Sounds more sensible either way tbf.

so no need to add routeUtil::isRouteAllowed matcher manually

I thought so too, but added it as another test as it was mentioned in the docs - just to rule that out. But good to know that it can be removed.

Just for my understanding - Hilla is implicitly used by Flow, right? Just as many other seem to run just fine with Spring Security, without having that specific issue. But I’ve just created the issue: Vaadin with Spring Boot & Spring Security breaks RoutePathAccessChecker · Issue #22284 · vaadin/flow · GitHub

1 Like

Hilla gets automatically imported when you use the Vaadin Spring Boot starter, but you can opt-out by excluding com.vaadin:hilla and com.vaadin:hilla-dev dependencies.

In addition, other projects might not suffer from the reported issue because also RoutePathAccessChecker is an opt-in, not enabled by default.

Back to your example, using RoutePathAccessChecker does not make sense, since you seem not to use Spring Security request matchers to protect Vaadin routes (e.g. http.authorizeHttpRequests(authz -> authz.requestMatchers("/myroute").hasRole("ADMIN")))

1 Like