Зарегистрировать сеанс для пользователя - PullRequest
1 голос
/ 28 октября 2019

Я создаю прототип, используя Vaadin8, начиная с одномодульного шаблона. Я пытаюсь назначить уникальный экземпляр пользовательского интерфейса (сеанс) каждому аутентифицированному пользователю, чтобы каждый пользователь представлял контент определенного типа в соответствии со своими собственными настройками в приложении. Вот моя конфигурация:

@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);
    }

После ввода одного из двух имен пользователей, зарегистрированных в базе данных, я бы хотел, чтобы объект Label отображал имя аутентифицированного пользователя вместо «Гость». Еще один эффект, которого я пытаюсь достичь, - это если пользователь вошел в систему и еще один запрос к серверу, он должен сгенерировать новый пользовательский интерфейс с неустановленным атрибутом us3r.

1 Ответ

3 голосов
/ 29 октября 2019

Предостережения: я использую Vaadin Flow в последнее время, а не Vaadin 8. Так что моя память туманная, и мой код может быть неправильным. И я сохранил все примеры слишком простыми, не готовыми к производству. Наконец, я уверен, что другие выберут другой подход, поэтому вы можете заняться поиском в Интернете, чтобы увидеть альтернативы.


UI податлив

UI изВаадин более пластичен и податлив, чем вы можете себе представить. Вы можете полностью заменить начальный VerticalLayout на другой вид, содержащий виджет.

Способ обработки входов в систему с помощью Vaadin состоит в том, что мой подкласс UI по умолчанию проверяет объект моего собственного User класса в веб-сеансе. Основываясь на технологии Джакартский сервлет , каждое веб-приложение Vaadin автоматически получает преимущества от обработки сеанса на основе сервлетов, предоставляемой контейнером сервлетов. Кроме того, Vaadin переносит их как VaadinSession.

Если обнаружено, что объект User существует как «атрибут» (пара ключ-значение) в сеансе, тогдаЯ знаю, что пользователь уже успешно вошел в систему. Поэтому я отображаю основное содержимое в этом начальном объекте подкласса UI. Под «основным содержанием» я подразумеваю экземпляр определенного класса, который я написал, который расширяет VertialLayout, или HoriontalLayout, или что-то подобное.

Если объект User не найден, то мой начальный UI объект подкласса отображает вид входа в систему. Под «представлением входа в систему» ​​я имею в виду экземпляр некоторого другого конкретного класса, который я написал, который расширяет VertialLayout, или HoriontalLayout, или что-то подобное.

Когда вы переключаете или изменяете содержимое в экземпляре подкласса UI, Vaadin берет на себя все обновления клиента. Изменения в состоянии вашего объекта UI на сервере, сделанные вашим кодом Java, автоматически передаются в библиотеку JavaScript Vaadin, которая была изначально установлена ​​в веб-браузере. Эта библиотека Vaadin JS автоматически отображает измененный пользовательский интерфейс, генерируя необходимые HTML, CSS, JavaScript и т. Д. вам не нужно перезагружать страницу , как вы, похоже, делаете в своем примере кода. Как одностраничное веб-приложение , веб-страница загружается только один раз. В Vaadin мы в значительной степени забываем о цикле HTTP запросов / ответов.

Пример приложения

Сначала нам понадобится простой User класс для демонстрационных целей.

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 );
    }
}

Начальная точка нашего приложения, наш подкласс UI провероксеанс и переключает контент. Обратите внимание, как мы разделили код проверки и переключения на именованный метод ShowLoginOrContent. Это позволяет нам снова вызывать этот код после входа в систему и снова после выхода из системы.

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
    {
    }
}

screenshot of login view with

Вот что LoginView, VerticalLayout. У нас есть имя пользователя и пароль с кнопкой «Войти». Обратите внимание, что при успешной аутентификации мы:

  • Создаем User и добавляем в автоматически созданный сеанс в виде пары ключ-значение «атрибут». Ключом является класс User, а значением является экземпляр User. В качестве альтернативы, вы можете использовать String в качестве ключа.
  • Вызовите метод showLoginOrContent для MyUI для замены нашего представления входа в систему с представлением основного содержимого.

В реальной работе я бы нашел механизм аутентификации пользователя длясвой собственный класс, не связанный с пользовательским интерфейсом. Но здесь мы игнорируем процесс аутентификации для этой демонстрации.

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 );
    }
}

main content view, with

Наконец, нам нужен наш основной просмотр содержимого. Здесь мы используем «список клиентов», который еще не создан. Вместо этого мы размещаем пару фрагментов текста, чтобы вы знали, что макет появляется. Обратите внимание, как в этом коде мы ищем имя пользователя из нашего User объекта в атрибуте сеанса.

Мы включаем кнопку «Выйти», чтобы показать, как мы отменяем аутентификацию, просто очистив наш Userэкземпляр как значение нашего «атрибута» в сеансе. Кроме того, вы можете убить весь сеанс, вызвав VaadinSession::close. Что подходит, зависит от вашего конкретного приложения.

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 );
    }
}

Эффект кнопки «Выйти» состоит в том, чтобы удалить основной контент и вернуть пользователя обратно в представление входа в систему.

Разделение задач

Один изЦелью подхода к логинам является разделение интересов . Забота о создании интерактивного пользовательского интерфейса (виджеты и код Vaadin) должна быть в значительной степени отделена от бизнес-логики того, как мы определяем, является ли пользователь тем, кем он себя считает (код аутентификации).

Наш подкласс UI почти ничего не знает об аутентификации пользователя. Мы перенесли всю механику входа в другие классы, не относящиеся к Ваадину. Код, связанный с Vaadin, имеет только две точки подключения к аутентификации: (a) Передача собранных учетных данных (имя пользователя, пароль и т. Д.) И (b) Проверка на наличие объекта User в хранилище значений ключей сеанса.

Многооконные веб-приложения

Кстати, вы должны знать, что Vaadin 8 имеет потрясающую поддержку многооконных веб-приложений. Вы можете написать ссылки или кнопки, чтобы открыть дополнительные окна / вкладки в браузере, все они работают в одном веб-приложении и в одном сеансе пользователя. Каждая вкладка / окно имеет свой собственный экземпляр подкласса UI, который вы написали. Все эти UI экземпляры подкласса используют один и тот же объект VaadinSession.

Таким образом, использование логики, описанной выше, применимо ко всем таким вкладкам / окнам: все окна, принадлежащие одному сеансу, с одним логином.

Поддельные диалоговые окна не защищены

Выможет возникнуть соблазн поместить ваш вид входа в систему в диалоговое окно, отображаемое поверх основного содержимого. не сделать это. Веб-диалоговое окно «поддельное» в том смысле, что оно не является окном, созданным и управляемым операционной системой. Диалоговое окно веб-приложения - это просто графика, создающая иллюзию второго окна. Диалоговое окно с изображением притязаний и лежащий в их основе контент фактически являются одной веб-страницей

Хакер может получить доступ к содержимому на странице и может победить ваш диалог входа в систему. Это упоминается в руководстве Vaadin на странице Подокна .

В моем примере выше, у нас нет такой проблемы безопасности. Чувствительный основной контент поступает в веб-браузер пользователя только после завершения аутентификации.

Хуки жизненного цикла веб-приложения

Кстати, вы правильно используете ServletContextListener. Это стандартная ловушка для жизненного цикла запуска вашего веб-приложения. Этот слушатель гарантированно будет работать до того, как поступит запрос первого пользователя, и снова после отправки ответа последнего пользователя. Это подходящее место для настройки ресурсов, необходимых вашему приложению в целом, для разных пользователей.

Однако у Ваадина есть альтернатива. Ваадин предоставляет вам VaadinServiceInitListener для реализации. Это может быть более удобным, чем стандартный подход, хотя вам необходимо настроить его, создав файл, чтобы сделать вашу реализацию доступной через Интерфейс реализации службы Java (SPI) . Ваш VaadinServiceInitListener как другое место для настройки ресурсов для всего вашего веб-приложения. Вы также можете зарегистрировать дополнительных прослушивателей для закрытия службы (веб-приложения) и для запуска или остановки сеанса пользователя.

Браузер Перезагрузка Кнопка

Последний совет:Возможно, вы захотите использовать аннотацию @PreserveOnRefresh.

Vaadin Flow

В Vaadin Flow (версии 10+) я использую тот же подход для входа в систему.

Ну,в основном то же самое. В Vaadin Flow цель класса UI была значительно обновлена. На самом деле, этот класс должен был быть переименован с учетом того, как он себя ведет. Мы больше не пишем подкласс UI при запуске приложения Vaadin. Экземпляр UI больше не стабилен во время сеанса пользователя. Среда выполнения Vaadin заменит объект UI другим новым экземпляром (или повторно инициализирует его), иногда довольно быстро, по причинам, которые я пока не понимаю. Так что я не вижу большого практического использования для UI для тех из нас, кто пишет приложения для Vaadin.

Теперь в Flow я начинаю с пустого макета вместо UI подкласса. Внутри этого макета я меняю вложенные макеты. Сначала просмотр входа в систему. После аутентификации я меняю вид входа в систему для основного вида контента. При выходе из системы наоборот (или закройте объект VaadinSession).

...