Skip to content

Commit 6a94f63

Browse files
fix: handle logout from a background thread (#20688) (#20706)
Allows AuthenticationContext.logout feature to be used from a background thread, handling the missing request by forcing the client to make an additional request. Applies the same logic used to support logout when using PUSH with websocket transport. References #11026 Co-authored-by: Marco Collovati <marco@vaadin.com>
1 parent 5804162 commit 6a94f63

File tree

4 files changed

+50
-10
lines changed

4 files changed

+50
-10
lines changed

flow-tests/vaadin-spring-tests/test-spring-security-flow/src/main/java/com/vaadin/flow/spring/flowsecurity/views/MainView.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package com.vaadin.flow.spring.flowsecurity.views;
22

33
import java.util.Optional;
4+
import java.util.concurrent.CompletableFuture;
5+
import java.util.concurrent.TimeUnit;
6+
7+
import org.springframework.security.concurrent.DelegatingSecurityContextExecutor;
48

59
import com.vaadin.flow.component.Component;
610
import com.vaadin.flow.component.ComponentUtil;
11+
import com.vaadin.flow.component.UI;
712
import com.vaadin.flow.component.applayout.AppLayout;
813
import com.vaadin.flow.component.applayout.DrawerToggle;
914
import com.vaadin.flow.component.avatar.Avatar;
@@ -97,6 +102,18 @@ private Component createDrawerContent(Tabs menu) {
97102
});
98103
layout.add(logout);
99104

105+
Button logoutFromServer = new Button("Logout from server");
106+
logoutFromServer.setId("logout-server");
107+
logoutFromServer.addClickListener(e -> {
108+
UI ui = UI.getCurrent();
109+
Runnable action = ui.accessLater(() -> securityUtils.logout(),
110+
null);
111+
CompletableFuture.runAsync(action,
112+
new DelegatingSecurityContextExecutor(CompletableFuture
113+
.delayedExecutor(1, TimeUnit.SECONDS)));
114+
});
115+
layout.add(logoutFromServer);
116+
100117
Anchor logoutWithUrl = new Anchor("doLogout", "Logout with URL");
101118
logoutWithUrl.getElement().setAttribute("router-ignore", true);
102119
logoutWithUrl.setId("logout-anchor");

flow-tests/vaadin-spring-tests/test-spring-security-flow/src/test/java/com/vaadin/flow/spring/flowsecurity/AppViewIT.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,16 @@ public void logout_via_doLogoutURL_redirects_to_logout() {
308308
assertLogoutViewShown();
309309
}
310310

311+
@Test
312+
public void logout_server_initiated_redirects_to_logout() {
313+
open(LOGIN_PATH);
314+
loginAdmin();
315+
navigateTo("admin");
316+
assertAdminPageShown(ADMIN_FULLNAME);
317+
getMainView().$(ButtonElement.class).id("logout-server").click();
318+
assertRootPageShown();
319+
}
320+
311321
@Test
312322
public void client_menu_routes_correct_for_anonymous() {
313323
navigateToClientMenuList();

flow-tests/vaadin-spring-tests/test-spring-security-flow/src/test/java/com/vaadin/flow/spring/flowsecurity/UIAccessContextIT.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,16 @@
1515
*/
1616
package com.vaadin.flow.spring.flowsecurity;
1717

18+
import org.junit.Assert;
19+
import org.junit.Test;
20+
import org.openqa.selenium.WebDriver;
21+
1822
import com.vaadin.flow.component.button.testbench.ButtonElement;
1923
import com.vaadin.flow.component.login.testbench.LoginFormElement;
2024
import com.vaadin.flow.component.login.testbench.LoginOverlayElement;
2125
import com.vaadin.testbench.HasElementQuery;
2226
import com.vaadin.testbench.TestBenchElement;
2327

24-
import org.junit.Assert;
25-
import org.junit.Test;
26-
import org.openqa.selenium.WebDriver;
27-
2828
public class UIAccessContextIT extends AbstractIT {
2929

3030
@Test
@@ -37,14 +37,15 @@ public void securityContextSetForUIAccess() throws Exception {
3737
super.setup();
3838
open("private");
3939
loginUser();
40-
TestBenchElement balance = $("span").id("balanceText");
40+
TestBenchElement balance = waitUntil(
41+
d -> $("span").id("balanceText"));
4142
Assert.assertEquals(expectedUserBalance, balance.getText());
4243

4344
open("private", adminBrowser);
4445
HasElementQuery adminContext = () -> adminBrowser;
4546
loginAdmin(adminContext);
46-
TestBenchElement adminBalance = adminContext.$("span")
47-
.id("balanceText");
47+
TestBenchElement adminBalance = waitUntil(
48+
d -> adminContext.$("span").id("balanceText"));
4849
Assert.assertEquals(expectedAdminBalance, adminBalance.getText());
4950

5051
ButtonElement sendRefresh = $(ButtonElement.class)
@@ -70,6 +71,10 @@ private void loginAdmin(HasElementQuery adminContext) {
7071
form.getUsernameField().setValue("emma");
7172
form.getPasswordField().setValue("emma");
7273
form.submit();
74+
waitUntilNot(driver -> ((WebDriver) adminContext.getContext())
75+
.getCurrentUrl().contains("my/login/page"));
76+
waitUntilNot(
77+
driver -> adminContext.$(LoginOverlayElement.class).exists());
7378
}
7479

7580
}

vaadin-spring/src/main/java/com/vaadin/flow/spring/security/AuthenticationContext.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import org.springframework.util.Assert;
4646

4747
import com.vaadin.flow.component.UI;
48+
import com.vaadin.flow.server.VaadinRequest;
4849
import com.vaadin.flow.server.VaadinServletRequest;
4950
import com.vaadin.flow.server.VaadinServletResponse;
5051
import com.vaadin.flow.shared.ui.Transport;
@@ -131,8 +132,10 @@ public boolean isAuthenticated() {
131132
*/
132133
public void logout() {
133134
final UI ui = UI.getCurrent();
134-
if (ui.getPushConfiguration().getTransport() == Transport.WEBSOCKET
135-
&& ui.getInternals().getPushConnection().isConnected()) {
135+
boolean pushWebsocketConnected = ui.getPushConfiguration()
136+
.getTransport() == Transport.WEBSOCKET
137+
&& ui.getInternals().getPushConnection().isConnected();
138+
if (pushWebsocketConnected) {
136139
// WEBSOCKET transport mode would not log out properly after session
137140
// invalidation. Switching to WEBSOCKET_XHR for a single request
138141
// to do the logout.
@@ -151,6 +154,11 @@ public void logout() {
151154
ui.getPushConfiguration().setTransport(Transport.WEBSOCKET);
152155
doLogout(ui);
153156
});
157+
} else if (VaadinRequest.getCurrent() == null) {
158+
// Logout started from a background thread, force client to send
159+
// a request
160+
ui.getPage().executeJs("return true").then(ignored -> doLogout(ui),
161+
error -> doLogout(ui));
154162
} else {
155163
doLogout(ui);
156164
}
@@ -496,7 +504,7 @@ public void logout(HttpServletRequest request,
496504
private boolean isContinueToNextHandler(HttpServletRequest request,
497505
LogoutHandler handler) {
498506
return handler instanceof SecurityContextLogoutHandler
499-
&& (request.getSession() == null
507+
&& (request.getSession(false) == null
500508
|| !request.isRequestedSessionIdValid());
501509
}
502510
}

0 commit comments

Comments
 (0)