The logout is successful, but the redirect to /login doesn’t work. I have tried to call “setLocation(”/login")" before “invalidate()” and “close()”, wrap it in ui.access(), etc., but the result is the same - nothing happens. There are no errors either.
I’m using Vaadin 24.3.13. Is the code I use some outdated way to logout and navigate to login, or what could be the issue?
I would suggest to updated to a recent version of Vaadin, e.g. 24.5.6 (if you have to stick with 24.3, upgrade at least to 24.3.20) and to use AuthenticationContext.logout() along with VaadinWebSecurity to configure Spring Security.
I use AuthenticationContext on a logout button and it works fine there. I remember trying to use it for the async call, but, unless I remember wrong, I got NPEs one some of the singleton calls in the class:
Hmm. AuthenticationContext still doesn’t work in my situation, but after updating Vaadin version to 24.5.6 the original code I posted works, though there is a second or a few delay until the redirecting happens.
Perhaps the way I do the async calls and error handling is wrong, I might be missing something. I tried to create a barebone version of what I’m trying to do async. Basically, two async calls are done and joined, and at some point during this there is an error. The error should cause a logout. This is the example code:
private void fetchData(UI ui) {
// Simulate async tasks with delays
CompletableFuture<String> async1 = getAsync("1");
CompletableFuture<String> async2 = getAsync("2");
// Combine futures and handle results
CompletableFuture<Void> allFutures = CompletableFuture.allOf(async1, async2);
allFutures.thenRun(() -> {
throw new IllegalStateException("Simulate error");
}).exceptionally(throwable -> {
try {
System.out.println("Error during data fetching");
// Logout with authenticationContext does not work:
ui.access(authenticationContext::logout);
// Logout less clean but works:
ui.access(() -> {
ui.getSession().getSession().invalidate();
ui.getSession().close();
SecurityContextHolder.clearContext();
ui.getPage().setLocation("/login");
});
} catch (Exception e) {
e.printStackTrace();
}
return null;
});
}
private CompletableFuture<String> getAsync(String data) {
return CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
return data;
} catch (InterruptedException e) {
throw new RuntimeException("Async operation interrupted", e);
}
});
}
Do AuthenticationContext.logout throw some exception? I wonder if the problem is that the Spring security context holder thread local is not correctly set, since the code is running in a thread that is not “Spring aware”
I get the stacktrace below when calling “ui.access(authenticationContext::logout);”. I’m new to spring so it could absolutely be that I haven’t set the spring security context correctly. I threw something together to make it work, this is basically how I do it:
java.util.concurrent.ExecutionException: java.lang.NullPointerException: Cannot invoke "com.vaadin.flow.server.VaadinServletRequest.getHttpServletRequest()" because the return value of "com.vaadin.flow.server.VaadinServletRequest.getCurrent()" is null
at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122) ~[na:na]
at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191) ~[na:na]
at com.vaadin.flow.server.FutureAccess.get(FutureAccess.java:62) ~[flow-server-24.5.7.jar:24.5.7]
at com.vaadin.flow.server.VaadinService.runPendingAccessTasks(VaadinService.java:2162) ~[flow-server-24.5.7.jar:24.5.7]
at com.vaadin.flow.server.VaadinSession.unlock(VaadinSession.java:755) ~[flow-server-24.5.7.jar:24.5.7]
at com.vaadin.flow.server.VaadinService.ensureAccessQueuePurged(VaadinService.java:2123) ~[flow-server-24.5.7.jar:24.5.7]
at com.vaadin.flow.server.VaadinService.accessSession(VaadinService.java:2090) ~[flow-server-24.5.7.jar:24.5.7]
at com.vaadin.flow.server.VaadinSession.access(VaadinSession.java:1059) ~[flow-server-24.5.7.jar:24.5.7]
at com.vaadin.flow.component.UI.access(UI.java:570) ~[flow-server-24.5.7.jar:24.5.7]
at com.vaadin.flow.component.UI.access(UI.java:553) ~[flow-server-24.5.7.jar:24.5.7]
at com.application.TestView.lambda$fetchData$5(TestView.java:270) ~[classes/:na]
at java.base/java.util.concurrent.CompletableFuture.uniExceptionally(CompletableFuture.java:990) ~[na:na]
at java.base/java.util.concurrent.CompletableFuture$UniExceptionally.tryFire(CompletableFuture.java:974) ~[na:na]
at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[na:na]
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773) ~[na:na]
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1760) ~[na:na]
at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:387) ~[na:na]
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1312) ~[na:na]
at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1843) ~[na:na]
at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1808) ~[na:na]
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:188) ~[na:na]
Caused by: java.lang.NullPointerException: Cannot invoke "com.vaadin.flow.server.VaadinServletRequest.getHttpServletRequest()" because the return value of "com.vaadin.flow.server.VaadinServletRequest.getCurrent()" is null
at com.vaadin.flow.spring.security.AuthenticationContext.doLogout(AuthenticationContext.java:161) ~[vaadin-spring-24.5.7.jar:na]
at com.vaadin.flow.spring.security.AuthenticationContext.logout(AuthenticationContext.java:155) ~[vaadin-spring-24.5.7.jar:na]
at com.vaadin.flow.component.UI.accessSynchronously(UI.java:498) ~[flow-server-24.5.7.jar:24.5.7]
at com.vaadin.flow.component.UI$2.execute(UI.java:573) ~[flow-server-24.5.7.jar:24.5.7]
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572) ~[na:na]
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) ~[na:na]
at com.vaadin.flow.server.VaadinService.runPendingAccessTasks(VaadinService.java:2159) ~[flow-server-24.5.7.jar:24.5.7]
... 17 common frames omitted
Ah, yeah. AuthContext.logout() is called in a UI.access() command from a background thread, so VaadinRequest thread local is not available, and the request is required to execute Spring logoutSuccessHandler.
So, this is effectively not an option if you want to logout from a background thread.
Yeah, that works. Perhaps I ask a bit stupidly now, but is this a safe way to ensure a logout? I don’t know exactly what happens here, I just want to make sure that the server logs out the user without it going through the client first → the execute js part concerns me
Yes, there’s a chance for this to happen. If you want to invalidate the session immediately, you have to go with the other solution. Just be aware that Spring logout success handler will not be invoked.
OK. Just curious, is it important to trigger the logout success handler(s)? Are there any default/under the hood handlers that should be triggered, or are the handlers just something that I can chose to add if I want to use the spring logout mechanism? I assume it’s handlers added in the config:
Can’t say. I think it depends on the application configuration. By default, it probably only redirects to the login page.
You should also make sure that executing SecurityContextHolder.clearContext() behaves as expected in a background thread, according to the documentation link I posted above.