Vaadin LoginForm-在用户通过或失败身份验证时发出信号 [英] Vaadin LoginForm - signaling when user passed or failed authentication

查看:118
本文介绍了Vaadin LoginForm-在用户通过或失败身份验证时发出信号的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我了解使用Vaadin 14的 Login 组件时,我必须呼叫 addLoginListener 来注册我自己的实现ComponentEventListener<AbstractLogin.LoginEvent>的侦听器.在我的实施代码中,我可以调用 LoginEvent::getUsername

I understand that in using the Login component of Vaadin 14, I must call addLoginListener to register a listener of my own that implements ComponentEventListener<AbstractLogin.LoginEvent>. In my implementing code, I can call the LoginEvent::getUsername and LoginEvent::getPassword methods to obtain the text values entered by the user. My listener code then determines if these credentials are correct.

➥侦听器代码通过什么机制与我的 LoginForm 身份验证检查的结果?

➥ By what mechanism does my listener code communicate back to my LoginForm the results of the authentication check?

如果身份验证检查成功,则需要关闭我的LoginForm,并让导航继续到预期的路线.如果身份验证失败,我需要LoginForm通知用户有关失败的信息,并要求用户重新输入用户名和密码.我的侦听器代码如何告诉LoginForm下一步做什么?

If the authentication check succeeded, I need my LoginForm to close and let navigation continue on to the intended route. If the authentication failed, I need the LoginForm to inform the user about the failure, and ask user to re-enter username and password. How can my listener code tell the LoginForm what to do next?

推荐答案

LoginForm仅支持其按钮的事件

LoginForm可以为两种事件注册侦听器:

LoginForm supports events only for its buttons

The LoginForm can register listeners for two kinds of events:

  • 用户单击登录"按钮
  • 用户单击忘记密码"按钮
    (可能会显示或可能不会显示)

对于我们检测用户是否成功完成登录尝试而言,这两个都不对我们有用.注册登录"按钮单击的任何方法都将决定用户的成功或失败.正是那个方法需要发出成功登录尝试的信号.

Neither of those is useful for us in detecting if the user successfully completed their login attempt. Whatever method that does register for the "Log in" button click will be deciding the user's success or failure. It is that method which needs to signal the successful login attempt.

作为 Component LoginForm 事件监听器支持不能扩展到我们的网络应用程序类,因为它们不属于Vaadin软件包的数量(超出范围).

As a Component, the LoginForm has built-in support for registering listeners for other types of events. Unfortunately, that support has its scope restricted to protected. So that event-listener support cannot extend to our web-app classes, as they are not part of the Vaadin packages (out of scope).

我尝试并无法使用Vaadin Flow的路由功能. Vaadin 14.0.2中的路由行为似乎存在一些严重的问题.加上我对路由"的无知,这对我来说是不行的.相反,我使用单个URL(根")从MainView内部管理所有内容.

I tried and failed to use the Routing feature of Vaadin Flow. There seem to be some serious issues with routing behavior in Vaadin 14.0.2. Add on my ignorance of Routing, and it became a no-go for me. Instead, I am managing all the content from within my MainView using a single URL (root "").

我不知道这是否是最好的方法,但是我选择制作LoginForm的子类,称为AuthenticateView.

I do not know if this is the best approach, but I chose to make a subclass of LoginForm called AuthenticateView.

➥在该子类上,我添加了一个定义单个方法authenticationPassed的嵌套接口AuthenticationPassedObserver.这是我的回调尝试,目的是将用户的登录成功通知我MainView.这是关于如何在用户通过身份验证时发出信号的问题的特定解决方案:使调用布局实现在LoginForm子类上定义的接口,并在用户成功登录后调用一个方法尝试.

➥ On that subclass I added a nested interface AuthenticationPassedObserver defining a single method authenticationPassed. This is my attempt at a callback, to inform my MainView of the success of the user's attempt to login. This is the specific solution to the Question of how to signal when the user passed authentication: Make the calling layout implement an interface defined on the LoginForm subclass, with a single method to be called after the user succeeds in their login attempt.

请注意,我们不关心失败.我们只需将LoginForm子类显示为我们的MainView内容,直到用户成功或关闭Web浏览器窗口/选项卡,从而终止会话为止.如果您担心黑客无休止地尝试登录,则可能希望LoginForm的子类跟踪重复的尝试并做出相应的反应.但是Vaadin Web应用程序的性质使这种攻击的可能性较小.

Notice that we do not care about a failure. We simply leave the LoginForm subclass displayed as our MainView content for as long as it takes until the user either succeeds or closes the web browser window/tab, thereby terminating the session. If you are worried about hackers trying endlessly to login, you may want your subclass of LoginForm to keep track of repeated attempts and react accordingly. But the nature of a Vaadin web app makes such an attack less likely.

这里是同步回调的嵌套接口.

Here is that nested interface for the synchronous callback.

    interface AuthenticationPassedObserver
    {
        void authenticationPassed ( );
    }

此外,我定义了一个接口Authenticator,该接口负责确定用户名&密码凭证有效.

In addition, I defined an interface Authenticator that does the work of deciding if the username & password credentials are valid.

package work.basil.ticktock.backend.auth;

import java.util.Optional;

public interface Authenticator
{
    public Optional <User> authenticate( String username , String password ) ;

    public void rememberUser ( User user );  // Collecting.
    public void forgetUser (  );  // Dropping user, if any, terminating their status as  authenticated.
    public boolean userIsAuthenticated () ;  // Retrieving.
    public Optional<User> fetchUser () ;  // Retrieving.

}

现在,我已经对该接口进行了抽象实现,以处理琐碎的工作,该工作存储了我定义的User类的对象,该对象代表每个人的登录名.

For now I have an abstract implementation of that interface to handle the chore of storing an object of the User class I defined to represent each individual's login.

package work.basil.ticktock.backend.auth;

import com.vaadin.flow.server.VaadinSession;

import java.util.Objects;
import java.util.Optional;

public abstract class AuthenticatorAbstract implements Authenticator
{
    @Override
    public void rememberUser ( User user ) {
        Objects.requireNonNull( user );
        VaadinSession.getCurrent().setAttribute( User.class, user  ) ;
    }

    @Override
    public void forgetUser() {
        VaadinSession.getCurrent().setAttribute( User.class, null  ) ; // Passing NULL clears the stored value.
    }

    @Override
    public boolean userIsAuthenticated ( )
    {
        Optional<User> optionalUser = this.fetchUser();
        return optionalUser.isPresent() ;
    }

    @Override
    public Optional <User> fetchUser ()
    {

        Object value = VaadinSession.getCurrent().getAttribute( User.class ); // Lookup into key-value store.
        return Optional.ofNullable( ( User ) value );
    }

}

也许我会将这些方法移到界面上的default上.但目前已经足够好了.

Perhaps I will move those methods to be default on the interface. But good enough for now.

我写了一些实现.有两个用于初始设计:一个总是不接受检查就接受证书,而另一个总是不检查就拒绝证书.另一个实现是真正的实现,它在用户数据库中进行查询.

I wrote some implementations. A couple were for initial design: one that always accepts the credentials without examination, and another that always rejects the credentials without examination. Another implementation is the real one, doing a look up in a database of users.

身份验证器返回Optional< User >,这意味着如果凭据失败,则可选字段为空,并且如果凭据通过则包含User对象.

The authenticator returns an Optional< User >, meaning the optional is empty if the credentials failed, and containing a User object if the credentials passed.

这里总是侧翼:

package work.basil.ticktock.backend.auth;

import work.basil.ticktock.ui.AuthenticateView;

import java.util.Optional;

final public class AuthenticatorAlwaysFlunks  extends AuthenticatorAbstract
{
    public Optional <User> authenticate( String username , String password ) {
        User user = null ;
        return Optional.ofNullable ( user  );
    }
}

...并且总是通过:

…and always passes:

package work.basil.ticktock.backend.auth;

import java.util.Optional;
import java.util.UUID;

public class AuthenticatorAlwaysPasses extends AuthenticatorAbstract
{
    public Optional <User> authenticate( String username , String password ) {
        User user = new User( UUID.randomUUID() , "username", "Bo" , "Gus");
        this.rememberUser( user );
        return Optional.ofNullable ( user  );
    }
}

顺便说一句,我不想​​在这里使用回调函数.几个压力点将我带到了这个设计.

By the way, I am not trying to be fancy here with my use of a callback. A couple pressure-points led me to this design.

  • did 想让LoginView子类不知道谁在调用它.一方面,我可能有一天会开始路由工作,或者实现某些导航器框架,然后显示谁的更大上下文可能会改变LoginForm子类.因此,我想要在产生该LoginForm子类对象的MainView对象上硬编码对方法的调用.
  • 我更喜欢将方法引用传递给我的LoginForm子类的构造函数的简单方法.然后,我可以忽略整个定义嵌套接口的内容.像这样:AuthenticateView authView = new AuthenticateView( this::authenticationPassed , authenticator );.但是我对 lambdas 不够了解,无法知道如何定义自己的方法引用参数.
  • I did want to keep the LoginView subclass ignorant of who is calling it. For one thing, I might some day get routing working, or implement some navigator framework, and then the larger context of who is display the LoginForm subclass may change. So I did not want to hardcode a call to method back on the MainView object that spawned this LoginForm subclass object.
  • I would have preferred to the simpler approach of passing a method reference to the constructor of my LoginForm subclass. Then I could omit the entire define-a-nested-interface thing. Something like this: AuthenticateView authView = new AuthenticateView( this::authenticationPassed , authenticator );. But I am not savvy enough with lambdas to know how to define my own method-reference argument.

最后,这是我早期的尝试,旨在修改Vaadin Starter项目提供给我的MainView类.

Lastly, here is my early experimental attempt at modifying the MainView class given to me by the Vaadin Starter project.

注意MainView如何实现嵌套的回调接口AuthenticateView.AuthenticationPassedObserver.用户成功完成登录后,将调用MainView此处的authenticationPassed方法.该方法从MainView的内容显示中清除LoginForm子类,并安装已授权当前用户查看的常规应用程序内容.

Notice how MainView implements the nested callback interface AuthenticateView.AuthenticationPassedObserver. When the user successfully completes a login, the authenticationPassed method here on MainView is invoked. That method clears the LoginForm subclass from content display of MainView, and installs the regular app content for which the current user is authorized to view.

还请注意注释:

  • 版本14中Vaadin Flow的新增功能@PreserveOnRefresh可使内容保持活动状态,即使用户单击浏览器的刷新"按钮也是如此.以为我没有仔细考虑过,这可能对登录过程有所帮助.
  • 此外,请注意@PWA.我目前不需要渐进式Web应用程序功能.但是在Vaadin 14.0.2中删除该注释似乎会导致对包含我们的Web浏览器窗口/选项卡内容的UI对象的虚假替换,因此我将其保留在原处.
  • 虽然我没有在Flow中使用路由"功能,但我不知道如何禁用该功能.因此,我将@Route ( "" )保留在原位.
  • The @PreserveOnRefresh new to Vaadin Flow in version 14 keeps content alive despite the user clicking the browser Refresh button. That may help with the login process, thought I've not thought it through.
  • Also, note the @PWA. I do not currently need the progressive web app features. But removing that annotation in a Vaadin 14.0.2 seems to cause even more spurious replacements of the UI object that contains our web browser window/tab contents, so I am leaving that line in place.
  • While I am not making use of the Routing feature in Flow, I do not know how to disable that feature. So I leave the @Route ( "" ) in place.
package work.basil.ticktock.ui;

import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.PreserveOnRefresh;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.PWA;
import work.basil.ticktock.backend.auth.Authenticator;
import work.basil.ticktock.backend.auth.AuthenticatorAlwaysPasses;

import java.time.Instant;

/**
 * The main view of the web app.
 */
@PageTitle ( "TickTock" )
@PreserveOnRefresh
@Route ( "" )
@PWA ( name = "Project Base for Vaadin", shortName = "Project Base" )
public class MainView extends VerticalLayout implements AuthenticateView.AuthenticationPassedObserver
{

    // Constructor
    public MainView ( )
    {
        System.out.println( "BASIL - MainView constructor. " + Instant.now() );
        this.display();
    }

    protected void display ( )
    {
        System.out.println( "BASIL - MainView::display. " + Instant.now() );
        this.removeAll();

        // If user is authenticated already, display initial view.
        Authenticator authenticator = new AuthenticatorAlwaysPasses();
        if ( authenticator.userIsAuthenticated() )
        {
            this.displayContentPanel();
        } else
        { // Else user is not yet authenticated, so prompt user for login.
            this.displayAuthenticatePanel(authenticator);
        }
    }

    private void displayContentPanel ( )
    {
        System.out.println( "BASIL - MainView::displayContentPanel. " + Instant.now() );
        // Widgets.
        ChronListingView view = new ChronListingView();

        // Arrange.
        this.removeAll();
        this.add( view );
    }

    private void displayAuthenticatePanel ( Authenticator authenticator )
    {
        System.out.println( "BASIL - MainView::displayAuthenticatePanel. " + Instant.now() );
        // Widgets
        AuthenticateView authView = new AuthenticateView(this, authenticator);

        // Arrange.
//        this.getStyle().set( "border" , "6px dotted DarkOrange" );  // DEBUG - Visually display the  bounds of this layout.
        this.getStyle().set( "background-color" , "LightSteelBlue" );
        this.setSizeFull();
        this.setJustifyContentMode( FlexComponent.JustifyContentMode.CENTER ); // Put content in the middle horizontally.
        this.setDefaultHorizontalComponentAlignment( FlexComponent.Alignment.CENTER ); // Put content in the middle vertically.

        this.removeAll();
        this.add( authView );
    }

    // Implements AuthenticateView.AuthenticationPassedObserver
    @Override
    public void authenticationPassed ( )
    {
        System.out.println( "BASIL - MainView::authenticationPassed. " + Instant.now() );
        this.display();
    }
}

这篇关于Vaadin LoginForm-在用户通过或失败身份验证时发出信号的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆