3.5.19. Процесс входа в Web Client
В данном разделе описывается, как работает аутентификация на веб-клиент и как расширить ее в проекте. Для информации об аутентификации на среднем слое см. Вход в систему.
Реализация логина в Web Client включает следующие механизмы:
-
Connection
реализованный классомConnectionImpl
. -
Реализации интерфейса
LoginProvider
. -
Реализации интерфейса
HttpRequestFilter
.
Основной интерфейс подсистемы входа в Web Client - Connection
, включающий следующие основные методы:
-
login() - аутентифицирует пользователя, создаёт пользовательскую сессию и изменяет состояние соединения.
-
logout() - выполняет выход из системы.
-
substituteUser() - замещает пользователя в текущей сессии. Этот метод создаёт новый объект UserSession, но с тем же ID.
-
getSession() - возвращает текущую сессию.
После успешного входа Connection устанавливает объект UserSession в атрибут VaadinSession
и устанавливает SecurityContext
. Объект Connection связан с VaadinSession
, поэтому вы не можете использовать его из фоновых потоков, при попытке вызова login/logout
из фонового потока выбрасывается исключение IllegalConcurrentAccessException.
Обычно, логин выполняется из экрана LoginScreen
, который поддерживает вход при помощи логина/пароля и токена "запомнить меня".
Реализация Connection
по умолчанию - ConnectionImpl
, который делегирует логин цепочке объектов LoginProvider
. Интерфейс LoginProvider
предназначен для реализации модулей входа, которые могут обрабатывать специфичные реализации интерфейса Credentials
, также этот интерфейс включает метод supports()
, позволяющий проверить поддерживает ли модули определённый тип Credentials
.
Стандартный процесс входа:
-
Пользователь вводит свой логин и пароль.
-
Web Client создаёт объект
LoginPasswordCredentials
, передав логи и пароль в его конструктор, и вызывает методConnection.login()
с этими данными для входа. -
Connection
использует цепочку объектовLoginProvider
. Существует стандартный модуль входаLoginPasswordLoginProvider
, который работает с аутентификационными данными типаLoginPasswordCredentials
. В зависимости от значения свойства cuba.checkPasswordOnClient, он либо вызываетAuthenticationService.login(Credentials)
передавая логин и пароль пользователя, либо загружает сущностьUser
по логину, проверяет пароль на соответствие загруженному хэшу, и выполняет вход как доверенный клиент используяTrustedClientCredentials
и cuba.trustedClientPassword. -
Если вход выполнен успешно, то объект
AuthenticationDetails
с активной сессией UserSession возвращается вConnection
. -
Connection
создаёт специальный класс-обёрткуClientUserSession
и устанавливает его в атрибутVaadinSession
. -
Connection
создаёт экземплярSecurityContext
и устанавливает его вAppContext
. -
Connection
публикует событиеStateChangeEvent
, стандартный обработчик которого обновляет UI и инициализируетMainScreen
.
Все реализации LoginProvider
должны:
-
Аутентифицировать пользователя при помощи переданного объекта
Credentials
. -
Создать и запустить новую пользовательскую сессию при помощи
AuthenticationService
или вернуть существующий объект активной сессии (например, для пользователя anonymous). -
Вернуть данные аутентификации или null если объект
Credentials
не может быть обработан, например, если модуль входа отключен или не сконфигурирован. -
Выбросить исключение
LoginException
в случае некорректных данныхCredentials
или пробросить вызывающему коду исключениеLoginException
, полученное от среднего слоя,.
HttpRequestFilter
- маркерный интерфейс для бинов, которые будут автоматически добавлены в цепочку фильтров приложения в качестве HTTP фильтра: https://docs.oracle.com/javaee/6/api/javax/servlet/Filter.html. Вы можете использовать такой фильтр для реализации дополнительной аутентификации, пре- и пост-обработки запроса и ответа.
Вы можете реализовать такой Filter
если создадите компонент Spring Framework и реализуете интерфейс HttpRequestFilter
:
@Component
public class CustomHttpFilter implements HttpRequestFilter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
// delegate to the next filter/servlet
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
Обратите внимание, что минимальная реализация должна делегировать исполнение объекту FilterChain
, в противном случае ваше приложение будет неработоспособно. По умолчанию фильтры добавленные как бины HttpRequestFilter
не будут получать запросы к каталогу VAADIN
и другим путям, указанным в свойстве приложения cuba.web.cubaHttpFilterBypassUrls
.
- Встроенные провайдеры входа
-
Платформа включает следующие реализации интерфейса
LoginProvider
:-
AnonymousLoginProvider
- предоставляет анонимный вход для пользователей, не выполнивших вход в систему. -
LoginPasswordLoginProvider
- делегирует логин сервисуAuthenticationService
, передаваяLoginPasswordCredentials
. -
RememberMeLoginProvider
- делегирует логин сервисуAuthenticationService
, передаваяRememberMeCredentials
. -
LdapLoginProvider
- принимаетLoginPasswordCredentials
, выполняет аутентификацию при помощи LDAP и делегирует логин сервисуAuthenticationService
, передаваяTrustedClientCredentials
. -
ExternalUserLoginProvider
- принимаетExternalUserCredentials
и делегирует логин сервисуAuthenticationService
, передаваяTrustedClientCredentials
. Тем самым позволяет выполнить вход от имени любого пользователя по его логину.
Все реализации создают активную сессию при помощи
AuthenticationService.login()
.Вы можете переопределить любой из провайдеров входа при помощи механизмов Spring Framework.
-
- События
-
Стандартная реализация
Connection
-ConnectionImpl
публикует следующие события во время процедуры входа:-
BeforeLoginEvent
/AfterLoginEvent
-
LoginFailureEvent
-
UserConnectedEvent
/UserDisconnectedEvent
-
UserSessionStartedEvent
/UserSessionFinishedEvent
-
UserSessionSubstitutedEvent
Обработчики событий
BeforeLoginEvent
иLoginFailureEvent
могут выбросить исключениеLoginException
чтобы прервать процесс входа или переопределить оригинальную причину ошибки входа.Например, при помощи обработчика
BeforeLoginEvent
вы можете разрешить вход в Web Client только для пользователей, логин которых включает домен компании.@Component public class BeforeLoginEventListener { @Order(10) @EventListener protected void onBeforeLogin(BeforeLoginEvent event) throws LoginException { if (event.getCredentials() instanceof LoginPasswordCredentials) { LoginPasswordCredentials loginPassword = (LoginPasswordCredentials) event.getCredentials(); if (loginPassword.getLogin() != null && !loginPassword.getLogin().contains("@company")) { throw new LoginException( "Only users from @company are allowed to login"); } } } }
Дополнительно, стандартный класс приложения -
DefaultApp
публикует следующие события:-
AppInitializedEvent
- публикуется после инициализации объектаApp
, выполняется один раз для одной HTTP сессии. -
AppStartedEvent
- публикуется во время обработки первого HTTP запроса кApp
перед инициализацией анонимного входа. Обработчики события могут выполнить вход при помощиConnection
, связанного сApp
. -
AppLoggedInEvent
- публикуется после инициализации UIApp
сразу после того, как выполнен вход в приложение. -
AppLoggedOutEvent
- публикуется после инициализации UIApp
сразу после того, как выполнен выход из приложения. -
SessionHeartbeatEvent
- публикуется во время запросовheartbeat
от веб-браузера пользователя.
Событие
AppStartedEvent
может использоваться для реализации прозрачного входа и SSO со сторонними системами, такими как Jasig CAS. Обычно, используется вместе с дополнительной реализациейHttpRequestFilter
, которая должна собрать и предоставить дополнительные аутентификационные данные из HTTP запроса.Допустим, что система должна автоматически выполнять вход для пользователей, у которых есть специальный файл cookie -
PROMO_USER
.@Order(10) @Component public class AppStartedEventListener implements ApplicationListener<AppStartedEvent> { private static final String PROMO_USER_COOKIE = "PROMO_USER"; @Inject private Logger log; @Override public void onApplicationEvent(AppStartedEvent event) { String promoUserLogin = event.getApp().getCookieValue(PROMO_USER_COOKIE); if (promoUserLogin != null) { Connection connection = event.getApp().getConnection(); if (!connection.isAuthenticated()) { try { connection.login(new ExternalUserCredentials(promoUserLogin)); } catch (LoginException e) { log.warn("Unable to login promo user {}: {}", promoUserLogin, e.getMessage()); } finally { event.getApp().removeCookie(PROMO_USER_COOKIE); } } } } }
Так, если веб-браузер хранит файл cookie
PROMO_USER
, и пользователь откроет приложение, будет выполнен вход от имени пользователя, указанного вpromoUserLogin
.Если вы хотите выполнить дополнительные действия после входа в приложение и инициализации UI вы можете реализовать обработчик события
AppLoggedInEvent
. Обратите внимание, что вы должны проверить, аутентифицирован ли пользователь или нет в обработчиках события, поскольку все события публикуются и для пользователяanonymous
, даже если пользователь не аутентифицирован. -
- События жизненного цикла веб-сессии
-
В зависимости от состояния веб-сессии могут публиковаться два события:
-
WebSessionInitializedEvent
- публикуется после инициализации HTTP сессии. -
WebSessionDestroyedEvent
- публикуется после завершения HTTP сессии.
Эти события могут использоваться для выполнения некоторой системной логики. Обратите внимание, что в потоке, обрабатывающем событие отсутствует
SecurityContext
. -
- Точки расширения
-
Вы можете расширить механизм входа, используя следующие точки расширения:
-
Connection
- заменить существующийConnectionImpl
. -
HttpRequestFilter
- реализовать дополнительныйHttpRequestFilter
. -
LoginProvider
implementations - реализовать новый или заменить существующий бинLoginProvider
. -
Events - реализовать обработчик одного из доступных событий.
Вы можете заменить существующие бины, используя механизмы Spring Framework, например, зарегистрировав новый бин в конфигурационном файле Spring XML модуля web.
<bean id="cuba_LoginPasswordLoginProvider" class="com.company.demo.web.CustomLoginProvider"/>
-