Аутентификация
Аутентификация – это процесс проверки личности пользователя или процесса, который взаимодействует с системой. Например, система может аутентифицировать пользователей по их имени и паролю. Для аутентифицированных пользователей система может выполнить авторизацию, которая представляет собой проверку разрешений для определенного ресурса.
Jmix напрямую использует Servlet аутентификацию Spring Security, поэтому если вы знакомы с этой платформой, то вы можете легко расширить или переопределить стандартный механизм аутентификации, предоставляемый Jmix "из коробки".
Текущий пользователь
Чтобы определить, кто в данный момент аутентифицирован, используйте бин CurrentAuthentication
. Он имеет следующие методы:
-
getUser()
возвращает текущего аутентифицированного пользователя как UserDetails. Его можно привести к классу пользователя, определенному в проекте. -
getAuthentication()
возвращает объект Authentication, связанный с текущим потоком выполнения. ОбъектAuthentication
хранит имена ролей пользователя.Jmix использует класс Spring Security SimpleGrantedAuthority для представления ролей пользователей. Этот класс фактически хранит одну строку, представляющую роль. Формат этой строки:
-
Для ресурсных ролей:
ROLE_<role-code>
, например,ROLE_system-full-access
. -
Для ролей уровня строк:
ROW_LEVEL_ROLE_<role-code>
, например,ROW_LEVEL_ROLE_my-role
.
Granted authorities нужного Java-класса и содержания могут быть созданы из кодов ролей с использованием класса
RoleGrantedAuthorityUtils
.Вы можете настроить префикс для полномочий ресурсных ролей, используя стандартный механизм Spring, путем конфигурирования бина
org.springframework.security.config.core.GrantedAuthorityDefaults
.Аналогично, вы можете скорректировать префикс для полномочий ролей уровня строк, используя свойство приложения
jmix.security.default-row-level-role-prefix
. -
-
getLocale()
иgetTimeZone()
возвращают локаль и часовой пояс текущего пользователя. -
isSet()
возвращает значениеtrue
, если текущий поток выполнения аутентифицирован, то есть содержит информацию о пользователе. Если это не так, методыgetUser()
,getLocale()
иgetTimeZone()
, описанные выше, выбросят исключениеIllegalStateException
.
Ниже приведен пример получения информации о текущем пользователе:
@Autowired
private CurrentAuthentication currentAuthentication;
private void printAuthenticationInfo() {
UserDetails user = currentAuthentication.getUser();
Authentication authentication = currentAuthentication.getAuthentication();
Locale locale = currentAuthentication.getLocale();
TimeZone timeZone = currentAuthentication.getTimeZone();
System.out.println(
"User: " + user.getUsername() + "\n" +
"Authentication: " + authentication + "\n" +
"Roles: " + getRoleNames(authentication) + "\n" +
"Locale: " + locale.getDisplayName() + "\n" +
"TimeZone: " + timeZone.getDisplayName()
);
}
private String getRoleNames(Authentication authentication) {
return authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
}
Например, вы можете использовать |
Аутентификация клиента
У бэкенда приложения Jmix могут быть разные клиенты, например, Jmix UI или REST API. Каждый клиент имеет свой собственный стандартный механизм аутентификации, такой как окно логина UI или токен доступа REST.
Собственная валидация паролей
Чтобы реализовать собственную валидацию паролей в приложении, достаточно создать бин (или несколько бинов), реализующий интерфейс PasswordValidator
. Например:
package com.company.demo.security;
import com.company.demo.entity.User;
import io.jmix.securityflowui.password.PasswordValidationContext;
import io.jmix.securityflowui.password.PasswordValidationException;
import io.jmix.securityflowui.password.PasswordValidator;
import org.springframework.stereotype.Component;
@Component
public class MyPasswordValidator implements PasswordValidator<User> {
@Override
public void validate(PasswordValidationContext<User> context) throws PasswordValidationException {
if (context.getPassword().length() < 3)
throw new PasswordValidationException("Password is too short, must be >= 3 characters");
}
}
Все валидаторы будут автоматически использованы в диалоге действия ChangePassword.
Для добавления кастомной валидации в экран деталей сущности User, используйте бин-помощник PasswordValidation
:
@Autowired
private PasswordValidation passwordValidation;
@Subscribe
public void onValidation(final ValidationEvent event) {
// ...
if (entityStates.isNew(getEditedEntity())) {
List<String> validationErrors = passwordValidation.validate(getEditedEntity(), passwordField.getValue());
if (!validationErrors.isEmpty()) {
event.getErrors().add(String.join("\n", validationErrors));
}
}
Защита от взлома методом перебора
В фреймворке есть механизм защиты от взлома паролей методом перебора, которая обеспечивается свойством приложения jmix.security.bruteforceprotection.enabled. Если она включена, комбинация логина пользователя и IP-адреса блокируется на определенный промежуток времени в случае нескольких неудачных попыток входа в систему. Максимальное количество попыток определяется свойством приложения jmix.security.bruteforceprotection.max-login-attempts-number. Интервал блокировки в секундах определяется свойством приложения jmix.security.bruteforceprotection.block-interval.
-
jmix.security.bruteforceprotection.enabled
Включает механизм защиты от взлома пароля методом перебора. Значение по умолчанию:
false
.
-
jmix.security.bruteforceprotection.block-interval
Определяет интервал блокировки в секундах после превышения максимального количества неудачных попыток входа в систему, если включено свойство jmix.security.bruteforceprotection.enabled. Значение по умолчанию:
60 seconds
.
-
jmix.security.bruteforceprotection.max-login-attempts-number
Определяет максимальное количество неудачных попыток входа в систему для комбинации логина пользователя и IP-адреса, если включено свойство jmix.security.bruteforceprotection.enabled. Значение по умолчанию:
5
.
Атрибуты сессии
Если вам необходимо использовать некоторые значения в нескольких запросах от одного и того же подключенного пользователя, используйте бин SessionData
. В нем есть методы для чтения и записи именованных значений, хранящихся в текущей сессии пользователя.
Бин SessionData
можно инжектировать напрямую в экраны UI:
public class CustomerListView extends StandardListView<Customer> {
@Autowired
private SessionData sessionData;
В singleton-бине используйте SessionData
через org.springframework.beans.factory.ObjectProvider
:
@Component
public class CustomerService {
@Autowired
private ObjectProvider<SessionData> sessionDataProvider;
public void saveSessionValue(String value) {
sessionDataProvider.getObject().setAttribute("my-attribute", value);
}
Атрибуты сессии также можно использовать в запросах JPQL. |
При обработке запросов UI общие значения сохраняются в сессии HTTP.
Системная аутентификация
Поток выполнения может быть не аутентифицирован, если был запущен внутренним планировщиком или обрабатывает запрос из JMX-интерфейса. Однако обычно бизнес-логике или коду доступа к данным для журналирования или авторизации требуется информация о том, кто в данный момент работает с системой.
Чтобы временно связать текущий поток выполнения с пользователем, используйте бин SystemAuthenticator
. Он имеет следующие методы:
-
withSystem()
- принимает lambda-выражение и выполняет его от имени системного пользователя. -
withUser()
- принимает имя обычного пользователя приложения и lambda-выражение и выполняет его от имени данного пользователя с соответствующими разрешениями.
Ниже приведен пример аутентификации операции MBean:
@Autowired
private SystemAuthenticator systemAuthenticator;
@Autowired
private CurrentAuthentication currentAuthentication;
@ManagedOperation
public String doSomething() {
return systemAuthenticator.withSystem(() -> {
UserDetails user = currentAuthentication.getUser();
System.out.println("User: " + user.getUsername()); // system
// ...
return "Done";
});
}
@ManagedOperation
public String doSomething2() {
return systemAuthenticator.withUser("admin", () -> {
UserDetails user = currentAuthentication.getUser();
System.out.println("User: " + user.getUsername()); // admin
// ...
return "Done";
});
}
Также можно использовать аннотацию @Authenticated
для аутентификации всего метода бина, как выполняемого пользователем system
. Например:
@Autowired
private CurrentAuthentication currentAuthentication;
@Authenticated // authenticates the entire method
@ManagedOperation
public String doSomething3() {
UserDetails user = currentAuthentication.getUser();
System.out.println("User: " + user.getUsername()); // system
// ...
return "Done";
}
События аутентификации
Фреймворк Spring посылает определенные события приложения, связанные с аутентификацией.
Studio может помочь вам создать слушателей событий аутентификации. Нажмите New (+) → Event Listener в окне инструментов Jmix и выберите Authentication Event в диалоговом окне. |
Ниже приведен пример обработки событий аутентификации.
@Component
public class AuthenticationEventListener {
private static final Logger log =
LoggerFactory.getLogger(AuthenticationEventListener.class);
@EventListener
public void onInteractiveAuthenticationSuccess(
InteractiveAuthenticationSuccessEvent event) { (1)
User user = (User) event.getAuthentication().getPrincipal(); (2)
log.info("User logged in: " + user.getUsername());
}
@EventListener
public void onAuthenticationSuccess(
AuthenticationSuccessEvent event) { (3)
User user = (User) event.getAuthentication().getPrincipal(); (4)
log.info("User authenticated " + user.getUsername());
}
@EventListener
public void onAuthenticationFailure(
AbstractAuthenticationFailureEvent event) { (5)
String username = (String) event.getAuthentication().getPrincipal(); (6)
log.info("User login attempt failed: " + username);
}
@EventListener
public void onLogoutSuccess(LogoutSuccessEvent event) { (7)
User user = (User) event.getAuthentication().getPrincipal(); (8)
log.info("User logged out: " + user.getUsername());
}
}
1 | InteractiveAuthenticationSuccessEvent посылается, когда пользователь входит в систему через UI или REST API. |
2 | InteractiveAuthenticationSuccessEvent содержит сущность пользователя. |
3 | AuthenticationSuccessEvent отправляется при любой успешной аутентификации, включая системную. |
4 | AuthenticationSuccessEvent содержит сущность пользователя. |
5 | AbstractAuthenticationFailureEvent посылается, если попытка аутентификации не удалась, например, из-за ввода неверных учетных данных. |
6 | AbstractAuthenticationFailureEvent содержит только имя пользователя, указанное при аутентификации. |
7 | LogoutSuccessEvent посылается при выходе пользователя из системы. |
8 | LogoutSuccessEvent содержит сущность пользователя. |