Vaadin 14.2:使用 BeforeLeaveListener 拦截注销(使会话无效)

Vaadin 14.2: Intercept logout (invalidate session) with BeforeLeaveListener

在我的 Vaadin 14.2.0 应用程序中,有一个 BeforeLeaveListener 可以在输入脏时显示确认对话框(=输入字段中有未保存的更改)以取消(或有意继续)导航:

BeforeLeaveListener listener = new BeforeLeaveListener() {
    @Override
    public void beforeLeave(BeforeLeaveEvent event) {
        if (dirtyFlag.isDirty()) {
            ContinueNavigationAction postponeAction = event.postpone();
            // show confirmation dialog and maybe execute a proceed
            [...] () -> postponeAction.proceed();
        }
    }
};
UI.getCurrent().addBeforeLeaveListener(listener);

除注销外,这对所有情况都适用。 这是我的注销按钮在开始时的样子(只要我不想 postpone/cancel 注销,它就可以工作):

Anchor link = new Anchor();
link.getElement().addEventListener("click", e -> {
    VaadinSession.getCurrent().getSession().invalidate();
    UI.getCurrent().navigate(LoginView.class);
});

现在我想推迟注销,直到用户确认应丢弃未保存的更改。 (在 EventListener 中切换两行似乎是个好主意,但由于以下原因没有奏效。) 在 navigate-call 中调用了 BeforeLeaveListener(好),但是注销还是完成了,因为执行 navigate()-call 之后的代码(当然,因为它不是阻塞调用),会话无效并且虽然刚刚弹出确认对话框,但用户已注销(但用户没有机会confirm/deny)。

我试图将会话失效移动到 LoginView 中(移动到 BeforeEnterObserver 中),但结果是登录视图在无限循环中重新加载。

问题:有没有类似"navigate to LoginView and if navigation is not postponed and not cancelled, then invalidate session"的东西?

一个解决方法是导航到一个截取的 LogoutView,它只是使会话无效,redirects/forwards 到 LoginView:

@Route(value = "logout")
public class LogoutView implements BeforeEnterObserver {
    @Override
    public void beforeEnter(BeforeEnterEvent event) {
        VaadinSession.getCurrent().getSession().invalidate();
        event.forwardTo(LoginView.class);
    }
}

但在我看来,这只是一个糟糕的解决方法,需要一些开销(创建一个视图只是为了转发到另一个视图)...

我知道,你没有提到Spring。但这实际上是我所相信的 Spring 安全部门正在做的事情。

我的注销按钮(使用 Spring 安全)看起来像这样:

new Button("Logout", click -> {
    UI.getCurrent().getPage().executeJs("location.assign('logout')");
});

单击它后,您将注销并重定向到登录视图 (more details from Baeldung)。我认为如果您执行 LogoutView 重定向绕行就没问题了。

您甚至可以使用 Navigator 转到您的 LogoutView 的事实在这里更好,因为这会被 BeforeLeaveObserver 捕获(与通过分配新位置或刷新页面的全新请求形成对比。参见 BeforeLeaveObserver vs. beforeunload 了解更多详情)


我仍然想为您的用例提出另一个想法。我认为它会简单得多,但要求注销 link 知道 dirtyFlag:

Anchor link = new Anchor();
link.getElement().addEventListener("click", e -> {
    if(dirtyFlag.isDirty()){
        // show Dialog with 
        // - a confirm btn that calls doLogout() on click
        // - a cancel btn that closes the Dialog
    } else {
       doLogout();
    }
});

...

private void doLogout(){
    VaadinSession.getCurrent().getSession().invalidate();
    UI.getCurrent().navigate(LoginView.class);
}