Vaadin LoginForm-在用户通过或失败身份验证时发出信号 [英] Vaadin LoginForm - signaling when user passed or failed authentication
问题描述
我了解使用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 theLoginForm
subclass may change. So I did not want to hardcode a call to method back on theMainView
object that spawned thisLoginForm
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 theUI
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屋!