vaadin / flow

Vaadin Flow is a Java framework binding Vaadin web components to Java. This is part of Vaadin 10+.
Apache License 2.0
609 stars 167 forks source link

NullPointerException in AuthenticationContext.logout() because VaadinServletResponse is null #20017

Open sephiroth-j opened 1 week ago

sephiroth-j commented 1 week ago

Description of the bug

I followed the example of enabling security using Spring Security. I added a logout button and used AuthenticationContext.logout() as described in the example.

Instead of logging out, nothing happens because a NullPointerException is thrown here

https://github.com/vaadin/flow/blob/12633cdf89710bd13b7f1b70fca83f4d65773f21/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/AuthenticationContext.java#L132-L133

The example when not using AuthenticationContext clearly does not try to use VaadinServletResponse because it is a) null and b) not used by the logout handler.

example without AuthenticationContext

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

stacktrace

java.lang.NullPointerException: Cannot invoke "com.vaadin.flow.server.VaadinServletResponse.getHttpServletResponse()" because the return value of "com.vaadin.flow.server.VaadinServletResponse.getCurrent()" is null
    at com.vaadin.flow.spring.security.AuthenticationContext.logout(AuthenticationContext.java:133) ~[vaadin-spring-24.4.8.jar:na]
    at foo.app.service.SecurityService.logout(SecurityService.java:47) ~[classes/:na]
    at foo.app.views.MainLayout.lambda$createHeader$7a549d6b$1(MainLayout.java:88) ~[classes/:na]
    at com.vaadin.flow.component.ComponentEventBus.fireEventForListener(ComponentEventBus.java:239) ~[flow-server-24.4.8.jar:24.4.8]
    at com.vaadin.flow.component.ComponentEventBus.handleDomEvent(ComponentEventBus.java:488) ~[flow-server-24.4.8.jar:24.4.8]
    at com.vaadin.flow.component.ComponentEventBus.lambda$addDomTrigger$dd1b7957$1(ComponentEventBus.java:298) ~[flow-server-24.4.8.jar:24.4.8]
    at com.vaadin.flow.internal.nodefeature.ElementListenerMap.lambda$fireEvent$2(ElementListenerMap.java:473) ~[flow-server-24.4.8.jar:24.4.8]
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) ~[na:na]
    at com.vaadin.flow.internal.nodefeature.ElementListenerMap.fireEvent(ElementListenerMap.java:473) ~[flow-server-24.4.8.jar:24.4.8]
    at com.vaadin.flow.server.communication.rpc.EventRpcHandler.handleNode(EventRpcHandler.java:62) ~[flow-server-24.4.8.jar:24.4.8]
    at com.vaadin.flow.server.communication.rpc.AbstractRpcInvocationHandler.handle(AbstractRpcInvocationHandler.java:73) ~[flow-server-24.4.8.jar:24.4.8]
    at com.vaadin.flow.server.communication.ServerRpcHandler.handleInvocationData(ServerRpcHandler.java:475) ~[flow-server-24.4.8.jar:24.4.8]
    at com.vaadin.flow.server.communication.ServerRpcHandler.lambda$handleInvocations$5(ServerRpcHandler.java:456) ~[flow-server-24.4.8.jar:24.4.8]
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) ~[na:na]
    at com.vaadin.flow.server.communication.ServerRpcHandler.handleInvocations(ServerRpcHandler.java:456) ~[flow-server-24.4.8.jar:24.4.8]
    at com.vaadin.flow.server.communication.ServerRpcHandler.handleRpc(ServerRpcHandler.java:324) ~[flow-server-24.4.8.jar:24.4.8]
    at com.vaadin.flow.server.communication.PushHandler.lambda$new$1(PushHandler.java:165) ~[flow-server-24.4.8.jar:24.4.8]
    at com.vaadin.flow.server.communication.PushHandler.callWithUi(PushHandler.java:277) ~[flow-server-24.4.8.jar:24.4.8]
    at com.vaadin.flow.server.communication.PushHandler.onMessage(PushHandler.java:628) ~[flow-server-24.4.8.jar:24.4.8]
    at com.vaadin.flow.server.communication.PushAtmosphereHandler.onMessage(PushAtmosphereHandler.java:90) ~[flow-server-24.4.8.jar:24.4.8]
    at com.vaadin.flow.server.communication.PushAtmosphereHandler.onRequest(PushAtmosphereHandler.java:79) ~[flow-server-24.4.8.jar:24.4.8]
    at org.atmosphere.cpr.AsynchronousProcessor.action(AsynchronousProcessor.java:217) ~[atmosphere-runtime-3.0.5.slf4jvaadin1.jar:3.0.5.slf4jvaadin1]
    at org.atmosphere.cpr.AsynchronousProcessor.suspended(AsynchronousProcessor.java:103) ~[atmosphere-runtime-3.0.5.slf4jvaadin1.jar:3.0.5.slf4jvaadin1]
    at org.atmosphere.container.Servlet30CometSupport.service(Servlet30CometSupport.java:67) ~[atmosphere-runtime-3.0.5.slf4jvaadin1.jar:3.0.5.slf4jvaadin1]
    at org.atmosphere.cpr.AtmosphereFramework.doCometSupport(AtmosphereFramework.java:2284) ~[atmosphere-runtime-3.0.5.slf4jvaadin1.jar:3.0.5.slf4jvaadin1]
    at org.atmosphere.websocket.DefaultWebSocketProcessor.dispatch(DefaultWebSocketProcessor.java:574) ~[atmosphere-runtime-3.0.5.slf4jvaadin1.jar:3.0.5.slf4jvaadin1]
    at org.atmosphere.websocket.DefaultWebSocketProcessor.lambda$dispatch$2(DefaultWebSocketProcessor.java:326) ~[atmosphere-runtime-3.0.5.slf4jvaadin1.jar:3.0.5.slf4jvaadin1]
    at org.atmosphere.util.VoidExecutorService.execute(VoidExecutorService.java:101) ~[atmosphere-runtime-3.0.5.slf4jvaadin1.jar:3.0.5.slf4jvaadin1]
    at org.atmosphere.websocket.DefaultWebSocketProcessor.dispatch(DefaultWebSocketProcessor.java:323) ~[atmosphere-runtime-3.0.5.slf4jvaadin1.jar:3.0.5.slf4jvaadin1]
    at org.atmosphere.websocket.DefaultWebSocketProcessor.invokeWebSocketProtocol(DefaultWebSocketProcessor.java:428) ~[atmosphere-runtime-3.0.5.slf4jvaadin1.jar:3.0.5.slf4jvaadin1]
    at org.atmosphere.container.JSR356Endpoint.lambda$onOpen$2(JSR356Endpoint.java:261) ~[atmosphere-runtime-3.0.5.slf4jvaadin1.jar:3.0.5.slf4jvaadin1]
    at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:390) ~[tomcat-embed-websocket-10.1.28.jar:10.1.28]
    at org.apache.tomcat.websocket.server.WsFrameServer.sendMessageText(WsFrameServer.java:130) ~[tomcat-embed-websocket-10.1.28.jar:10.1.28]
    at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:484) ~[tomcat-embed-websocket-10.1.28.jar:10.1.28]
    at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:284) ~[tomcat-embed-websocket-10.1.28.jar:10.1.28]
    at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:130) ~[tomcat-embed-websocket-10.1.28.jar:10.1.28]
    at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:85) ~[tomcat-embed-websocket-10.1.28.jar:10.1.28]
    at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:184) ~[tomcat-embed-websocket-10.1.28.jar:10.1.28]
    at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:164) ~[tomcat-embed-websocket-10.1.28.jar:10.1.28]
    at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:152) ~[tomcat-embed-websocket-10.1.28.jar:10.1.28]
    at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:60) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:57) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:904) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.28.jar:10.1.28]
    at java.base/java.lang.VirtualThread.run(VirtualThread.java:309) ~[na:na]

Expected behavior

Successful logout, no NPE.

Minimal reproducible example

Add a logout button and use AuthenticationContext.logout() as described here.

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);
    }
}

Versions

Hilla: 24.4.8 Flow: 24.4.8 Vaadin: 24.4.12 Copilot: 24.4.13 Copilot IDE Plugin: false Java: Eclipse Adoptium 21.0.3 Java Hotswap: false Frontend Hotswap: Disabled, using pre-built bundle OS: amd64 Windows 11 10.0 Browser: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0 Spring Boot: 3.3.4 Spring Framework: 6.1.13 Spring Security: 6.3.3

mcollovati commented 5 days ago

I can reproduce the issue, but only with @Push enabled with WEBSOCKET transport. @sephiroth-j can you please confirm if you have the same setup?

mcollovati commented 5 days ago

When @Push transport is WEBSOCKET, all client to server request are sent through the websocket channel, and the Vaadin PushHandler does not provide a VaadinResponse instance to VaadinService.requestStart() nor sets the VaadinResponse thread local.

sephiroth-j commented 5 days ago

Hello @mcollovati, yes, I had set @Push(transport = Transport.WEBSOCKET). Later, however, I switched back to the standard WEBSOCKET_XHR because there were further problems with the login page and @RolesAllowed annotations were not recognized.

mcollovati commented 5 days ago

Thanks for the confirmation. For anyone else stepping here, the current workaround is to switch the PUSH transport to WEBSOCKET_XHR (that is the default setting)