5.5.9.4. Процесс входа в 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.
Обычно, логин выполняется из экрана AppLoginWindow, который поддерживает вход при помощи логина/пароля и токена "запомнить меня".
Реализация Connection по умолчанию - ConnectionImpl, который делегирует логин цепочке объектов LoginProvider. Интерфейс LoginProvider предназначен для реализации модулей входа, которые могут обрабатывать специфичные реализации интерфейса Credentials, также этот интерфейс включает метод supports(), позволяющий проверить поддерживает ли модули определённый тип Credentials.
Стандартный процесс входа:
-
Пользователь вводит свой логин и пароль.
-
Web Client создаёт объект
LoginPasswordCredentials, передав логи и пароль в его конструктор, и вызывает методConnection.login()с этими данными для входа. -
Connectionиспользует цепочку объектовLoginProvider. Существует стандартный модуль входаLoginPasswordLoginProvider, который работает с аутентификационными данными типаLoginPasswordCredentials. Этот модуль хэширует пароль при помощи методаgetPlainHash()бинаPasswordEncryptionи вызываетAuthenticationService.login(Credentials). -
Если вход выполнен успешно, то объект
AuthenticationDetailsс активной сессией UserSession возвращается вConnection. -
Connectionсоздаёт специальный класс-обёрткуClientUserSessionи устанавливает его в атрибутVaadinSession. -
Connectionсоздаёт экземплярSecurityContextи устанавливает его вAppContext. -
Connectionпубликует событиеStateChangeEvent, стандартный обработчик которого обновляет UI и инициализируетAppMainWindow.
Все реализации 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- выполняет аутентификацию при помощи LDAP и выполняет вход, передаваяExternalUserCredentialsсервисуAuthenticationService. -
ExternalUserLoginProvider- может использоваться для выполнения входа из обработчиков событий приложения, позволяет выполнить вход от имени любого пользователя по его логину.
Все реализации создают активную сессию при помощи
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, даже если пользователь не аутентифицирован. -
- Точки расширения
-
- Вы можете расширить механизм входа, используя следующие точки расширения
-
-
Connection- заменить существующийConnectionImpl. -
HttpRequestFilter- реализовать дополнительныйHttpRequestFilter. -
LoginProviderimplementations - реализовать новый или заменить существующий бинLoginProvider. -
Events - реализовать обработчик одного из доступных событий.
-
Вы можете заменить существующие бины, используя механизмы Spring Framework, например, зарегистрировав новый бин в конфигурационном файле Spring XML модуля web.
<bean id="cuba_LoginPasswordLoginProvider" class="com.company.demo.web.CustomLoginProvider"/>
- Устаревшие механизмы
-
При необходимости можно создать собственный класс имплементации
CubaAuthProviderи использовать его, установив следующие свойства приложения:cuba.web.externalAuthentication = true cuba.web.externalAuthenticationProviderClass = com.company.sample.web.MyAuthProviderСледующие компоненты считаются устаревшими:
-
Интерфейс
CubaAuthProviderи его реализации доступны в режиме совместимости. Используйте вместо него события, интерфейсыLoginProviderиHttpRequestFilter. -
LdapAuthProviderзаменён наLdapLoginProvider, который может быть включен как описано здесь: Интеграция с LDAP -
IdpAuthProviderзаменён наIdpLoginProvider, который может быть включен как описано здесь: IDP SSO
Не используйте эти компоненты. Они будут удалены в следующей major версии платформы.
Используйте вместо этих механизмов точки расширения Web Client.
-