向用户注册会话 [英] Register session to User

查看:40
本文介绍了向用户注册会话的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 Vaadin8 从单模块模板开始构建原型.我正在尝试为每个经过身份验证的用户分配一个唯一的 UI 实例(一个会话),以便根据他们自己在应用程序中的设置向每个用户显示特定类型的内容.这是我的配置:

@WebListener公共类市场实现 ServletContextListener {公共静态 ArrayListusers;公共无效上下文初始化(ServletContextEvent sce){用户=新的ArrayList<用户>();User hau=new User("hau");users.add(hau);User moc=new User("moc");用户添加(moc);}public void contextDestroyed(ServletContextEvent sce){}}公共类用户{公共字符串用户名;公共用户(字符串用户名){this.username=username;}}公共类 MyUI 扩展了 UI {用户 us3r;@覆盖受保护的无效初始化(VaadinRequest vaadinRequest){最终的 VerticalLayout 布局 = new VerticalLayout();字符串用户名;if (this.us3r==null) {username="Guest";}else {username=us3r.username;}标签 who=new Label(username);TextField userfield=new TextField();按钮登录=新按钮(登录");login.addClickListener(new ClickListener() {@覆盖public void buttonClick(ClickEvent 事件) {对于(用户用户:Market.users){if (userfield.getValue().equals(user.username)) {us3r=user;Page.getCurrent().reload();return;}}Notification.show("没有用户"+userfield.getValue());}});按钮注销=新按钮(注销");logout.addClickListener(new ClickListener() {public void buttonClick(ClickEvent 事件) {if(us3r!=null) {us3r=null;Page.getCurrent().reload();}}});layout.addComponent(userfield);layout.addComponent(登录);layout.addComponent(who);layout.addComponent(注销);设置内容(布局);}

输入在数据库中注册的两个用户名之一后,我希望 Label 对象显示经过身份验证的用户的名称,而不是Guest".我试图实现的另一个效果是,如果用户登录并且有另一个对服务器的请求,它应该生成一个带有未实例化的 us3r 属性的新 UI.

解决方案

警告:我最近一直在使用 Vaadin Flow 而不是 Vaadin 8.所以我的记忆很模糊,我的代码可能是错误的.而且我让所有的例子都过于简单,没有准备好投入生产.最后,我相信其他人会采取不同的方法,因此您可能需要进行一些互联网搜索以查看替代方法.

<小时>

UI 具有延展性

Vaadin 的UI 比您想象的更具可塑性和可塑性.您可以使用其他一些包含小部件的视图来完全替换初始的 VerticalLayout.

我使用 Vaadin 处理登录的方式是,我的默认 UI 子类在网络会话中检查我自己的 User 类的对象.基于

这里是 LoginView,一个 VerticalLayout.我们有我们的用户名 &密码,带有登录"按钮.请注意我们如何成功进行身份验证:

  • 实例化一个 User 并作为属性"键值对添加到自动创建的会话中.键是 User 类,值是 User 实例.或者,您可以选择使用 String 作为键.
  • MyUI 上调用 showLoginOrContent 方法,将我们的登录视图换成主内容视图.

在实际工作中,我会将用户身份验证机制定位到与用户界面无关的自己的类中.但是这里我们忽略了这个演示的认证过程.

package work.basil.example;导入 com.vaadin.server.VaadinSession;导入 com.vaadin.ui.*;公共类 LoginView 扩展了 VerticalLayout{私有文本字段用户名字段;私人密码字段密码字段;私人按钮身份验证按钮;公共登录视图 ( ){//小部件this.userNameField = new TextField();this.userNameField.setCaption("用户账户名:");this.passwordField = new PasswordField();this.passwordField.setCaption("密码:");this.authenticateButton = new Button("登录");this.authenticateButton.addClickListener( ( Button.ClickListener ) clickEvent -> {//验证用户输入,不为空,不为空,不为空.//执行对用户进行身份验证的工作.User user = new User( this.userNameField.getValue() );VaadinSession.getCurrent().setAttribute( User.class , user );( ( MyUI ) UI.getCurrent() ).showLoginOrContent();//在我们的`UI` 子类实例中切换出内容.});//安排this.addComponents( this.userNameField , this.passwordField , this.authenticateButton );}}

最后,我们需要我们的主要内容视图.在这里,我们使用尚未实际构建的客户列表".相反,我们放置了几段文本,以便您知道布局正在出现.请注意,在这段代码中,我们如何从会话属性中的 User 对象中查找用户名.

我们包含一个退出"按钮,以显示我们如何通过清除我们的 User 实例作为会话中属性"的值来简单地反转身份验证.或者,您可以通过调用 VaadinSession::close.哪个合适取决于您的具体应用.

package work.basil.example;导入 com.vaadin.server.VaadinSession;导入 com.vaadin.ui.Button;导入 com.vaadin.ui.Label;导入 com.vaadin.ui.UI;导入 com.vaadin.ui.VerticalLayout;导入 java.time.Duration;导入 java.time.Instant;公共类 CustomerListingView 扩展了 VerticalLayout{按钮注销按钮;公共 CustomerListingView ( ){//小部件this.logoutButton = new Button("退出");this.logoutButton.addClickListener( ( Button.ClickListener ) clickEvent -> {VaadinSession.getCurrent().setAttribute( User.class , null );//传递 null 以清除值.( ( MyUI ) UI.getCurrent() ).showLoginOrContent();});用户用户 = VaadinSession.getCurrent().getAttribute( User.class );Duration duration = Duration.between( user.getWhenAuthenticated() , Instant.now() );Label Welcome = new Label( "Bonjour, " + user.getName() + ".您已登录:" + duration.toString() + "." );Label placeholder = new Label("这个视图正在建设中.这里会出现一个客户表.\"");//安排this.addComponents( this.logoutButton , 欢迎 , 占位符 );}}

退出"按钮的作用是移除主要内容,并将用户带回登录视图.

关注点分离

登录方法的目标之一是关注点分离.构建交互式用户界面(Vaadin 小部件和代码)的问题应该与我们如何确定用户是否是他们声称的身份(身份验证代码)的业务逻辑在很大程度上分开.

我们的 UI 子类对用户认证几乎一无所知.我们将所有登录机制移至其他非 Vaadin 特定的类.Vaadin 相关代码只有两个连接点到身份验证:(a) 传递收集到的凭据(用户名、密码等),以及 (b) 检查会话中是否存在 User 对象键值存储.

多窗口网络应用

顺便说一下,您应该知道 Vaadin 8 对多窗口 Web 应用程序有惊人的支持.您可以编写链接或按钮以在浏览器中打开其他窗口/选项卡,所有这些都在同一个 Web 应用程序和同一个用户会话中工作.每个选项卡/窗口都有自己编写的 UI 子类实例.所有这些 UI 子类实例共享相同的 VaadinSession 对象.

因此使用上述逻辑适用于所有此类选项卡/窗口:多个窗口都属于一个会话,一次登录.

假对话框不安全

您可能想将登录视图放在显示在主要内容上方的对话框中.不要不要这样做.Web 对话框是假的",因为它不是由操作系统创建和操作的窗口.Web 应用程序对话框窗口只是一些用于创建第二个窗口错觉的图形.假装对话框和底层内容实际上都是一个网页.

黑客可能会获得对页面内容的访问权限,并可能破解您的登录对话框.这在 Vaadin 手册中提到,在页面 子窗口.

在我上面的例子中,我们没有这样的安全问题.敏感的主要内容仅在 身份验证完成后才到达用户的 Web 浏览器.

网络应用生命周期挂钩

顺便说一下,您对 ServletContextListener 的使用 是正确的.这是 Web 应用程序启动生命周期的标准挂钩.该侦听器保证在第一个用户的请求到达之前运行,并在最后一个用户的响应发送后再次运行.这是一个合适的地方来配置你的应用程序所需的资源,跨不同的用户.

但是,在 Vaadin 中,您有另一种选择.Vaadin 提供了 VaadinServiceInitListener 供你执行.这可能比标准方法更方便,但您需要通过创建一个文件来配置它,以使您的实现通过 Java 服务实现接口 (SPI) 设施.您的 VaadinServiceInitListener 作为另一个为整个 Web 应用程序设置资源的地方.您还可以为关闭的服务(网络应用)以及用户会话的启动或停止注册更多侦听器.

浏览器重新加载按钮

最后一个提示:您可能想要使用 @PreserveOnRefresh 注释.

Vaadin 流

在 Vaadin Flow(版本 10+)中,我采用相同的登录方法.

嗯,基本一样.在 Vaadin Flow 中,UI 类的用途得到了显着改进.实际上,考虑到它的行为有多么不同,该类应该被重命名.在启动 Vaadin 应用程序时,我们不再常规地编写 UI 的子类.UI 实例在用户会话期间不再稳定.Vaadin 运行时会将 UI 对象替换为另一个新实例(或重新初始化它),有时会很快,原因我还不明白.因此,对于我们这些编写 Vaadin 应用程序的人来说,UI 并没有太多实际用途.

现在在 Flow 中,我从一个空布局开始,而不是一个 UI 子类.在该布局中,我交换了嵌套布局.首先是登录视图.身份验证后,我将登录视图切换为主要内容视图.在注销时,相反(或关闭 VaadinSession 对象).

I'm building a prototype using Vaadin8 starting from a single-module template. I'm trying to assign a unique UI instance (a session) to each authenticated user, so that each user is presented with a particular type of content according to their own settings within the app. Here's my configuration:

@WebListener
public class Market implements ServletContextListener {

    public static ArrayList<User>users;

    public void contextInitialized(ServletContextEvent sce) {   

    users=new ArrayList<User>();
    User hau=new User("hau");
    users.add(hau);
    User moc=new User("moc");
    users.add(moc);
    }

    public void contextDestroyed(ServletContextEvent sce){}
}

public class User {
    public String username;
    public user(String username){this.username=username;}
}


public class MyUI extends UI {
    User us3r;
    @Override
    protected void init(VaadinRequest vaadinRequest) {
      final VerticalLayout layout = new VerticalLayout();
      String username;
      if (this.us3r==null) {username="Guest";}else {username=us3r.username;}
      Label who=new Label(username);
      TextField userfield=new TextField();
      Button login=new Button("login");
      login.addClickListener(new ClickListener() {
        @Override
        public void buttonClick(ClickEvent event) {
            for (User user:Market.users) {
            if (userfield.getValue().equals(user.username)) {
                us3r=user;Page.getCurrent().reload();return;
                }
            }Notification.show("No user "+userfield.getValue());
        }
    });
      Button logout=new Button("logout");
      logout.addClickListener(new ClickListener() {
        public void buttonClick(ClickEvent event) {
            if(us3r!=null) {us3r=null; Page.getCurrent().reload();}
        }
    });
       layout.addComponent(userfield);
       layout.addComponent(login);
       layout.addComponent(who);
       layout.addComponent(logout);
       setContent(layout);
    }

After inputting one of the two usernames registered in the Database, I'd like the Label object to display the name of the authenticated user, instead of "Guest". Another effect I'm trying to achieve is if a user is logged in and there is another request to the server, it should generate a fresh UI with the uninstantiated us3r attribute.

解决方案

Caveats: I have been using Vaadin Flow lately rather than Vaadin 8. So my memory is hazy, and my code may be wrong. And I have kept all the examples overly simple, not ready for production. Lastly, I am sure others would take a different approach, so you may want to do some internet searching to see alternatives.


UI is malleable

The UI of Vaadin is more plastic and malleable than you may realize. You can entirely replace the initial VerticalLayout with some other widget-containing-view.

The way I have handled logins with Vaadin is that my default UI subclass checks for an object of my own User class in the web session. Being based on Jakarta Servlet technology, every Vaadin web app automatically benefits from the Servlet-based session handling provided by the Servlet container. Furthermore, Vaadin wraps those as a VaadinSession.

If the User object is found to be existing as an "attribute" (key-value pair) in the session, then I know the user has already logged-in successfully. So I display the main content in that initial UI subclass object. By "main content", I mean an instance of a particular class I wrote that extends VertialLayout, or HoriontalLayout or some such.

If no User object is found, then my initial UI subclass object displays a login view. By "login view" I mean an instance of some other particular class I wrote that extends VertialLayout, or HoriontalLayout or some such.

When you switch or morph the content within a UI subclass instance, Vaadin takes care of all the updating of the client. The change in state of your UI object on the server made by your Java code is automatically communicated to the Vaadin JavaScript library that was initially installed in the web browser. That Vaadin JS library automatically renders your changed user-interface by generating the needed HTML, CSS, JavaScript, and so on. There is no need for you to be reloading the page as you seem to be doing in your example code. As a single-page web app, the web page only loads once. In Vaadin, we largely forget about the HTTP Request/Response cycle.

Example app

First we need a simple User class for demonstration purposes.

package work.basil.example;

import java.time.Instant;
import java.util.Objects;

public class User
{
    private String name;
    private Instant whenAuthenticated;

    public User ( String name )
    {
        Objects.requireNonNull( name );
        if ( name.isEmpty() || name.isBlank() ) { throw new IllegalArgumentException( "The user name is empty or blank. Message # b2ec1529-47aa-47c1-9702-c2b2689753cd." ); }
        this.name = name;
        this.whenAuthenticated = Instant.now();
    }

    @Override
    public boolean equals ( Object o )
    {
        if ( this == o ) return true;
        if ( o == null || getClass() != o.getClass() ) return false;
        User user = ( User ) o;
        return name.equals( user.name );
    }

    @Override
    public int hashCode ( )
    {
        return Objects.hash( name );
    }
}

The starting point of our app, our subclass of UI checks the session and switches content. Notice how we segregated the check-and-switch code to a named method, ShowLoginOrContent. This allows us to invoke that code again after login, and again after logout.

package work.basil.example;

import com.vaadin.annotations.Theme;
import com.vaadin.annotations.VaadinServletConfiguration;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinServlet;
import com.vaadin.server.VaadinSession;
import com.vaadin.ui.UI;

import javax.servlet.annotation.WebServlet;
import java.util.Objects;

/**
 * This UI is the application entry point. A UI may either represent a browser window
 * (or tab) or some part of an HTML page where a Vaadin application is embedded.
 * <p>
 * The UI is initialized using {@link #init(VaadinRequest)}. This method is intended to be
 * overridden to add component to the user interface and initialize non-component functionality.
 */
@Theme ( "mytheme" )
public class MyUI extends UI
{

    @Override
    protected void init ( VaadinRequest vaadinRequest )
    {
        this.showLoginOrContent();
    }

    void showLoginOrContent ( )
    {
        // Check for User object in session, indicating the user is currently logged-in.
        User user = VaadinSession.getCurrent().getAttribute( User.class );
        if ( Objects.isNull( user ) )
        {
            LoginView loginView = new LoginView();
            this.setContent( loginView );
        } else
        {
            CustomerListingView customerListingView = new CustomerListingView();
            this.setContent( customerListingView );
        }
    }

    @WebServlet ( urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true )
    @VaadinServletConfiguration ( ui = MyUI.class, productionMode = false )
    public static class MyUIServlet extends VaadinServlet
    {
    }
}

Here is that LoginView, a VerticalLayout. We have our username & password, with a "Sign in" button. Notice how on successful authentication we:

  • Instantiate a User and add to the automatically-created session as an "attribute" key-value pair. The key is the class User, and the value is the User instance. Alternatively, you can choose to use a String as the key.
  • Invoke that showLoginOrContent method on MyUI to swap out our login view with a main content view.

In real work, I would locate the user-authentication mechanism to its own class unrelated to the user-interface. But here we ignore the process of authentication for this demonstration.

package work.basil.example;

import com.vaadin.server.VaadinSession;
import com.vaadin.ui.*;

public class LoginView extends VerticalLayout
{
    private TextField userNameField;
    private PasswordField passwordField;
    private Button authenticateButton;

    public LoginView ( )
    {
        // Widgets
        this.userNameField = new TextField();
        this.userNameField.setCaption( "User-account name:" );

        this.passwordField = new PasswordField();
        this.passwordField.setCaption( "Passphrase:" );
        this.authenticateButton = new Button( "Sign in" );
        this.authenticateButton.addClickListener( ( Button.ClickListener ) clickEvent -> {
                    // Verify user inputs, not null, not empty, not blank.
                    // Do the work to authenticate the user.
                    User user = new User( this.userNameField.getValue() );
                    VaadinSession.getCurrent().setAttribute( User.class , user );
                    ( ( MyUI ) UI.getCurrent() ).showLoginOrContent(); // Switch out the content in our `UI` subclass instance.
                }
        );

        // Arrange
        this.addComponents( this.userNameField , this.passwordField , this.authenticateButton );
    }
}

Lastly, we need our main content view. Here we use a "customer listing" that is not yet actually built. Instead, we place a couple pieces of text so you know the layout is appearing. Notice how in this code we look up the user's name from our User object in the session attribute.

We include a "Sign out" button to show how we reverse the authentication simply by clearing our User instance as the value of our "attribute" on the session. Alternatively, you could kill the entire session by calling VaadinSession::close. Which is appropriate depends on your specific app.

package work.basil.example;

import com.vaadin.server.VaadinSession;
import com.vaadin.ui.Button;
import com.vaadin.ui.Label;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;

import java.time.Duration;
import java.time.Instant;

public class CustomerListingView extends VerticalLayout
{
    Button logoutButton;

    public CustomerListingView ( )
    {
        // Widgets
        this.logoutButton = new Button( "Sign out" );
        this.logoutButton.addClickListener( ( Button.ClickListener ) clickEvent -> {
                    VaadinSession.getCurrent().setAttribute( User.class , null ); // Pass null to clear the value.
                    ( ( MyUI ) UI.getCurrent() ).showLoginOrContent();
                }
        );
        User user = VaadinSession.getCurrent().getAttribute( User.class );
        Duration duration = Duration.between( user.getWhenAuthenticated() , Instant.now() );
        Label welcome = new Label( "Bonjour, " + user.getName() + ". You’ve been signed in for: " + duration.toString() + "." );
        Label placeholder = new Label( "This view is under construction. A table of customers will appear here.\"" );

        // Arrange
        this.addComponents( this.logoutButton , welcome , placeholder );
    }
}

The effect of the "Sign out" button is to remove the main content, and take the user back to the login view.

Separation of concerns

One of the aims of the approach to logins is separation of concerns. The concern of building an interactive user-interface (Vaadin widgets and code) should be kept largely separate from the business logic of how we determine if a user is who they claim to be (authentication code).

Our UI subclass knows almost nothing about user-authentication. We moved all the mechanics of logging-in to other non-Vaadin-specific classes. The Vaadin-related code only has two connection points to authentication: (a) Passing collected credentials (username, password, or such), and (b) Checking for the presence of a User object in the session’s key-value store.

Multi-window web apps

By the way, you should know that Vaadin 8 has amazing support for multi-window web apps. You can write links or buttons to open additional windows/tabs in the browser, all working within the same web app and the same user session. Each tab/window has its own instance of a UI subclass you wrote. All of these UI subclass instances share the same VaadinSession object.

So using the logic seen above applies to all such tab/windows: Multiple windows all belonging to one session with one login.

Fake dialog boxes are not secure

You might be tempted to put your login view inside a dialog box appearing over your main content. Do not do this. A web dialog box is "fake", in that it is not a window created and operated by the operating-system. A web app dialog window is just some graphics to create the illusion of a second window. The pretend dialog and the underlying content are actually all one web page.

A hacker might gain access to the content on the page, and might be able to defeat your login dialog. This is mentioned in the Vaadin manual, on the page Sub-Windows.

In my example above, we have no such security problem. The sensitive main content arrives on the user’s web browser only after authentication completes.

Web app lifecycle hooks

By the way, your use of ServletContextListener is correct. That is the standard hook for the lifecycle of your web app launching. That listener is guaranteed to run before the first user’s request arrives, and again after the last user’s response is sent. This is an appropriate place to configure resources needed by your app in general, across various users.

However, in Vaadin, you have an alternative. Vaadin provides the VaadinServiceInitListener for you to implement. This may be more convenient than the standard approach, though you need to configure it by creating a file to make your implementation available via the Java Service Implementation Interface (SPI) facility. Your VaadinServiceInitListener as another place to setup resources for your entire web app. You can also register further listeners for the service (web app) shutting down, and for user-session starting or stopping.

Browser Reload button

One last tip: You may want to use the @PreserveOnRefresh annotation.

Vaadin Flow

In Vaadin Flow (versions 10+), I take the same approach to logins.

Well, basically the same. In Vaadin Flow, the purpose of the UI class was dramatically revamped. Actually, that class should have been renamed given how differently it behaves. We no longer routinely write a subclass of UI when starting a Vaadin app. A UI instance is no longer stable during the user's session. The Vaadin runtime will replace the UI object by another new instance (or re-initialize it), sometimes quite rapidly, for reasons I do not yet understand. So I do not see much practical use for UI for those of us writing Vaadin apps.

Now in Flow I start with an empty layout, instead of a UI subclass. Inside that layout I swap nested layouts. First the login view. After authentication, I swap the login view for the main content view. On logout, the opposite (or close the VaadinSession object).

这篇关于向用户注册会话的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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