3.2.11.2. Вход в систему
Платформа предоставляет встроенные механизмы аутентификации, функциональность которых может быть расширена в приложениях. Они включают в себя различные схемы аутентификации, такие как вход по паролю, функциональность "Запомнить меня", доверенный и анонимный вход в систему.
Данный раздел преимущественно описывает механизмы аутентификации среднего слоя. Для информации об аутентификации веб-клиента см. Процесс входа в Web Client.
Платформа включает следующие механизмы среднего слоя:
-
AuthenticationManager
, реализованный классомAuthenticationManagerBean
-
Реализации интерфейса
AuthenticationProvider
-
AuthenticationService
, реализованный классомAuthenticationServiceBean
-
UserSessionLog
- см. журналирование пользовательских сессий.
Также платформа включает следующие дополнительные компоненты:
-
TrustedClientService
, реализованный классомTrustedClientServiceBean
- предоставляет анонимную/системную сессию для доверенных приложений-клиентов. -
AnonymousSessionHolder
- создаёт и хранит анонимную сессию для доверенных приложений-клиентов. -
UserCredentialsChecker
- проверяет, могут ли быть использованы переданныеCredentials
, например, для защиты от атак типа brute-force. -
UserAccessChecker
- проверяет, может ли пользователь выполнять вход из данного контекста, например, в REST API или с указанного IP адреса.
Основной интерфейс аутентификации - AuthenticationManager
, включающий 4 метода:
public interface AuthenticationManager {
AuthenticationDetails authenticate(Credentials credentials) throws LoginException;
AuthenticationDetails login(Credentials credentials) throws LoginException;
UserSession substituteUser(User substitutedUser);
void logout();
}
Здесь есть два метода с похожей ответственностью: authenticate()
и login()
. Оба метода проверяют, являются ли переданные аутентификационные данные валидными и соответствуют ли они активному пользователю системы, затем возвращают объект AuthenticationDetails
. Основное отличие в работе этих методов в том, что метод login()
активирует сессию пользователя, так что она может использоваться в дальнейшем для вызова сервисов.
Объект Credentials
представляет собой набор аутентификационных данных. Платформа поставляет несколько типов аутентификационных данных:
Доступные на всех слоях:
-
LoginPasswordCredentials
-
RememberMeCredentials
-
TrustedClientCredentials
Доступные только на среднем слое:
-
SystemUserCredentials
-
AnonymousUserCredentials
Методы login / authenticate AuthenticationManager
возвращают объект AuthenticationDetails
, который содержит объект UserSession. Этот объект может быть использован для проверки дополнительных разрешений, чтения свойств объекта User и атрибутов сессии. В платформе есть встроенная реализация интерфейса AuthenticationDetails
- SimpleAuthenticationDetails, который хранит только объект сессии пользователя, приложения могут предоставлять свою реализацию AuthenticationDetails
с дополнительной инфомацией для приложений-клиентов.
AuthenticationManager может выполнить метод authenticate() с одним из следующих результатов:
-
вернуть объект
AuthenticationDetails
, если он подтверждает, что переданные аутентификационные данные верны и соответствуют активному пользователю системы. -
выбросить
LoginException
, если невозможно аутентифицировать пользователя, неверны аутентификационные данные или если пользователю запрещён доступ к системе. -
выбросить
UnsupportedCredentialsException
, если переданный тип аутентификационных данных не поддерживается системой.
Реализация AuthenticationManager
по умолчанию - AuthenticationManagerBean
, который делегирует аутентификацию цепочке экземпляров AuthenticationProvider
. AuthenticationProvider
- это модуль аутентификации, который может обрабатывать объекты Credentials
определённого типа. AuthenticationProvider
также имеет специальный метод supports()
, позволяющий узнать, поддерживается ли переданный тип аутентификационных данных.
Стандартный процесс входа пользователя:
-
пользователь вводит свой логин и пароль
-
клиентский блок приложения вызывает метод
Connection.login()
, передавая ему логин пользователя и пароль. -
Connection
создаёт объектCredentials
и вызывает методlogin()
сервисаAuthenticationService
. -
AuthenticationService
делегирует выполнение бинуAuthenticationManager
, который использует цепочку объектовAuthenticationProvider
. В этой цепочке имеется бинLoginPasswordAuthenticationProvider
, поддерживающий аутентификационные данные типаLoginPasswordCredentials
. Он загружает объектUser
по полученному логину, хэширует полученный хэш пароля повторно, используя в качестве соли идентификатор пользователя, и сравнивает полученный хэш с сохраненным в БД хэшем пароля. В случае несовпадения выбрасывается исключениеLoginException
. -
После успешной аутентификации в созданный экземпляр UserSession загружаются все параметры доступа данного пользователя: список ролей, права, ограничения и атрибуты сессии.
-
Если журналирование пользовательских сессий активировано, в базу данных сохраняется запись с информацией о текущей сессии.
См. также Специфика процесса входа в Web Client.
Алгоритм хэширования паролей реализуется бином типа EncryptionModule
и задается в свойстве приложения cuba.passwordEncryptionModule. По умолчанию - BCrypt.
- Встроенные провайдеры аутентификации
-
Платформа включает следующие реализации интерфейса
AuthenticationProvider
:-
LoginPasswordAuthenticationProvider
-
RememberMeAuthenticationProvider
-
TrustedClientAuthenticationProvider
-
SystemAuthenticationProvider
-
AnonymousAuthenticationProvider
Все реализации загружают пользователя из базы данных, проверяют переданные аутентификационные данные и создают неактивный экземпляр пользовательской сессии при помощи
UserSessionManager
. Этот экземпляр может стать активным позднее, если вызывается методAuthenticationManager.login()
.Бины
LoginPasswordAuthenticationProvider
,RememberMeAuthenticationProvider
иTrustedClientAuthenticationProvider
используют дополнительные подключаемые проверки: бины, реализующие интерфейсUserAccessChecker
. Если по крайней мере одна из таких проверок выбрасывает исключениеLoginException
, то аутентификация не выполняется и исключениеLoginException
передаётся вызывающему коду.Кроме того,
LoginPasswordAuthenticationProvider
иRememberMeAuthenticationProvider
проверяют аутентификационные данные при помощи бинов UserCredentialsChecker. Имеется одна встроенная реализация этого интерфейса - BruteForceUserCredentialsChecker, который проверяет, пытается ли пользователь подобрать верные аутентификационные данные при помощи атаки brute-force. -
- Исключения
-
AuthenticationManager
иAuthenticationProvider
могут выбрасывать исключение LoginException или одного из его наследников из методовauthenticate()
иlogin()
. Исключение UnsupportedCredentialsException выбрасывается, если переданный объект аутентификационных данныхCredentials
не может быть обработан имеющимисяAuthenticationProvider
.См. следующие классы исключений:
-
UnsupportedCredentialsException
-
LoginException
-
AccountLockedException
-
UserIpRestrictedException
-
RestApiAccessDeniedException
-
- События
-
Стандартная реализация
AuthenticationManager
-AuthenticationManagerBean
публикует следующие события во время аутентификации / входа пользователей:-
BeforeAuthenticationEvent
/AfterAuthenticationEvent
-
BeforeLoginEvent
/AfterLoginEvent
-
AuthenticationSuccessEvent
/AuthenticationFailureEvent
-
UserLoggedInEvent
/UserLoggedOutEvent
-
UserSubstitutedEvent
Бины Spring на среднем слое приложения могут обрабатывать эти события при помощи механизма подписок
@EventListener
:@Component public class LoginEventListener { @Inject private Logger log; @EventListener protected void onUserLoggedIn(UserLoggedInEvent event) { User user = event.getSource().getUser(); log.info("Logged in user {}", user.getInstanceName()); } }
Обработчики всех событий, перечисленных выше (кроме
AfterLoginEvent
,UserSubstitutedEvent
иUserLoggedInEvent
), могут выброситьLoginException
, чтобы прервать процесс аутентификации / входа пользователя.Например, мы можем реализовать механизм режима обслуживания системы, который позволит запретить вход в систему на время её обслуживания.
@Component public class MaintenanceModeValve { private volatile boolean maintenance = true; public boolean isMaintenance() { return maintenance; } public void setMaintenance(boolean maintenance) { this.maintenance = maintenance; } @EventListener protected void onBeforeLogin(BeforeLoginEvent event) throws LoginException { if (maintenance && event.getCredentials() instanceof AbstractClientCredentials) { throw new LoginException("Sorry, system is unavailable"); } } }
-
- Точки расширения
-
Вы можете расширить механизм аутентификации, используя следующие точки расширения:
-
AuthenticationService
- заменить существующийAuthenticationServiceBean
. -
AuthenticationManager
- заменить существующийAuthenticationManagerBean
. -
AuthenticationProvider
- реализовать новый или заменить существующий бинAuthenticationProvider
. -
Events - реализовать обработчик одного из доступных событий.
Вы можете заменить существующие бины, задействуя механизмы Spring Framework, например, зарегистрировав новую реализацию в XML конфигурации Spring модуля core:
<bean id="cuba_LoginPasswordAuthenticationProvider" class="com.company.authext.core.CustomLoginPasswordAuthenticationProvider"/>
public class CustomLoginPasswordAuthenticationProvider extends LoginPasswordAuthenticationProvider { @Inject public CustomLoginPasswordAuthenticationProvider(Persistence persistence, Messages messages) { super(persistence, messages); } @Override public AuthenticationDetails authenticate(Credentials credentials) throws LoginException { LoginPasswordCredentials loginPassword = (LoginPasswordCredentials) credentials; // for instance, add new check before login if ("demo".equals(loginPassword.getLogin())) { throw new LoginException("Demo account is disabled"); } return super.authenticate(credentials); } }
Обработчики событий могут быть упорядочены при помощи аннотации
@Order
. Все бины и обработчики событий платформы используют значениеorder
из диапазона 100 и 1000, что позволяет добавлять обработчики на уровне проект как до, так и после кода платформы. Если вы хотите добавить свой обработчик до обработчиков/бинов платформы - используйте значение меньше 100.Задание
order
для обработчика события:@Component public class DemoEventListener { @Inject private Logger log; @Order(10) @EventListener protected void onUserLoggedIn(UserLoggedInEvent event) { log.info("Demo"); } }
Бины
AuthenticationProvider
могут реализовать интерфейс Ordered и методgetOrder()
для определения очерёдности исполнения.@Component public class DemoAuthenticationProvider extends AbstractAuthenticationProvider implements AuthenticationProvider, Ordered { @Inject private UserSessionManager userSessionManager; @Inject public DemoAuthenticationProvider(Persistence persistence, Messages messages) { super(persistence, messages); } @Nullable @Override public AuthenticationDetails authenticate(Credentials credentials) throws LoginException { // ... } @Override public boolean supports(Class<?> credentialsClass) { return LoginPasswordCredentials.class.isAssignableFrom(credentialsClass); } @Override public int getOrder() { return 10; } }
-
- Дополнительные возможности
-
-
В платформе имеется механизм защиты от взлома пароля методом перебора. Для его включения необходимо установить свойство приложения cuba.bruteForceProtection.enabled для блока Middleware. В этом случае после определенного количества неуспешных попыток входа для определенного имени пользователя с определенного IP-адреса вход для пары логин + IP-адрес блокируется на некоторое время. Допустимое количество попыток входа для пары логин + IP-адрес определяется свойством приложения cuba.bruteForceProtection.maxLoginAttemptsNumber (по умолчанию 5). Интервал блокировки пользователя в секундах задается свойством cuba.bruteForceProtection.blockIntervalSec (по умолчанию 60).
-
Возможен вариант, когда пароль пользователя (точнее, хэш пароля) не хранится в базе данных, а проверяется внешними средствами, например, путем интеграции с LDAP. В этом случае фактически аутентификацию выполняет клиентский блок, а Middleware "доверяет" клиенту, создавая сессию по одному только логину пользователя без пароля методом
AuthenticationService.login()
, передаваяTrustedClientCredentials
. Этот метод требует выполнения следующих условий:-
клиентский блок должен передать так называемый доверенный пароль, задаваемый на Middleware и на клиентском блоке свойством приложения cuba.trustedClientPassword
-
IP-адрес клиентского блока должен быть в списке, задаваемом свойством приложения cuba.trustedClientPermittedIpList
-
-
Вход в систему требуется также для автоматических процессов, запускаемых по расписанию, а также при подключении к бинам Middleware через JMX-интерфейс. Строго говоря, такие действия считаются административными и не требуют аутентификации до тех пор, пока не выполняется каких-либо изменений сущностей в базе данных. При записи сущностей в БД требуется проставить логин пользователя, который выполнил изменения, поэтому для работы таких процессов должен быть указан пользователь, от лица которого выполняются изменения.
Дополнительным плюсом входа в систему для автоматического процесса и для JMX-вызова является то, что вывод в журнал сообщений от логгеров сопровождается указанием логина текущего пользователя, если пользовательская сессия установлена в потоке выполнения. Это упрощает поиск сообщений от конкретного процесса при разборе журнала.
Вход в систему для процессов внутри Middleware выполняется вызовом
AuthenticationManager.login()
с передачей объектаSystemUserCredentials
, содержащего логин пользователя (без пароля), от имени которого будет работать данный процесс. В результате создается объект UserSession, который будет закэширован в данном блоке Middleware и не будет реплицироваться в кластере.
Более подробно аутентификация процессов внутри Middleware рассмотрена в разделе Системная аутентификация.
-