Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
sephiroth-j opened this issue Sep 20, 2024 · 7 comments · Fixed by #20057

Comments

@sephiroth-j
Copy link

sephiroth-j commented Sep 20, 2024

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

HttpServletResponse response = VaadinServletResponse.getCurrent()
.getHttpServletResponse();

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
Copy link
Collaborator

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
Copy link
Collaborator

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
Copy link
Author

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
Copy link
Collaborator

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)

@mshabarov mshabarov moved this from 🔖 Normal Priority (P2) to 🔖 High Priority (P1) in Vaadin Flow bugs & maintenance (Vaadin 10+) Sep 24, 2024
@tltv tltv self-assigned this Sep 24, 2024
@tltv tltv moved this to ⚒️ In progress in Vaadin Flow ongoing work (Vaadin 10+) Sep 24, 2024
tltv added a commit that referenced this issue Sep 25, 2024
Don't throw NullPointerException in case of null VaadinServletResponse in AuthenticationContext#logout.

Fixes: #20017
@tltv tltv closed this as completed in 99d8266 Oct 2, 2024
@github-project-automation github-project-automation bot moved this from 🔖 High Priority (P1) to ✅ Closed in Vaadin Flow bugs & maintenance (Vaadin 10+) Oct 2, 2024
@github-project-automation github-project-automation bot moved this from ⚒️ In progress to Done in Vaadin Flow ongoing work (Vaadin 10+) Oct 2, 2024
vaadin-bot pushed a commit that referenced this issue Oct 2, 2024
Don't throw NullPointerException in case of null VaadinServletResponse in AuthenticationContext#logout.
Tolerate null response better in case when running with @Push(transport = Transport.WEBSOCKET), or when response is null for some other reason.
Makes logout also work in WEBSOCKET mode by automatically switching to WEBSOCKET_XHR for one additional request that executes logout.

Fixes: #20017
vaadin-bot pushed a commit that referenced this issue Oct 2, 2024
Don't throw NullPointerException in case of null VaadinServletResponse in AuthenticationContext#logout.
Tolerate null response better in case when running with @Push(transport = Transport.WEBSOCKET), or when response is null for some other reason.
Makes logout also work in WEBSOCKET mode by automatically switching to WEBSOCKET_XHR for one additional request that executes logout.

Fixes: #20017
tltv added a commit that referenced this issue Oct 2, 2024
Don't throw NullPointerException in case of null VaadinServletResponse in AuthenticationContext#logout.
Tolerate null response better in case when running with @Push(transport = Transport.WEBSOCKET), or when response is null for some other reason.
Makes logout also work in WEBSOCKET mode by automatically switching to WEBSOCKET_XHR for one additional request that executes logout.

Fixes: #20017
vaadin-bot added a commit that referenced this issue Oct 2, 2024
Don't throw NullPointerException in case of null VaadinServletResponse in AuthenticationContext#logout.
Tolerate null response better in case when running with @Push(transport = Transport.WEBSOCKET), or when response is null for some other reason.
Makes logout also work in WEBSOCKET mode by automatically switching to WEBSOCKET_XHR for one additional request that executes logout.

Fixes: #20017

Co-authored-by: Tomi Virtanen <[email protected]>
vaadin-bot added a commit that referenced this issue Oct 2, 2024
Don't throw NullPointerException in case of null VaadinServletResponse in AuthenticationContext#logout.
Tolerate null response better in case when running with @Push(transport = Transport.WEBSOCKET), or when response is null for some other reason.
Makes logout also work in WEBSOCKET mode by automatically switching to WEBSOCKET_XHR for one additional request that executes logout.

Fixes: #20017

Co-authored-by: Tomi Virtanen <[email protected]>
mcollovati pushed a commit that referenced this issue Oct 2, 2024
Don't throw NullPointerException in case of null VaadinServletResponse in AuthenticationContext#logout.
Tolerate null response better in case when running with @Push(transport = Transport.WEBSOCKET), or when response is null for some other reason.
Makes logout also work in WEBSOCKET mode by automatically switching to WEBSOCKET_XHR for one additional request that executes logout.

Fixes: #20017
@vaadin-bot
Copy link
Collaborator

This ticket/PR has been released with Vaadin 24.5.0.beta5 and is also targeting the upcoming stable 24.5.0 version.

@vaadin-bot
Copy link
Collaborator

This ticket/PR has been released with Vaadin 24.3.19.

@vaadin-bot
Copy link
Collaborator

This ticket/PR has been released with Vaadin 24.4.14.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment