Аутентификация
Аутентификация – это процесс проверки личности пользователя или процесса, который взаимодействует с системой. Например, система может аутентифицировать пользователей по их имени и паролю. Для аутентифицированных пользователей система может выполнить авторизацию, которая представляет собой проверку разрешений для определенного ресурса.
Jmix напрямую использует Servlet аутентификацию Spring Security, поэтому если вы знакомы с этой платформой, то вы можете легко расширить или переопределить стандартный механизм аутентификации, предоставляемый Jmix "из коробки".
Текущий пользователь
Чтобы определить, кто в данный момент аутентифицирован, используйте бин CurrentAuthentication
. Он имеет следующие методы:
-
getUser()
возвращает текущего аутентифицированного пользователя как UserDetails. Его можно привести к классу пользователя, определенному в проекте. -
getAuthentication()
возвращает объект Authentication, установленный в текущем потоке выполнения. Его можно использовать, чтобы получить коллекцию полномочий текущего пользователя. В стандартной реализации безопасности Jmix эта коллекция содержит объекты полномочий для каждой ресурсной и row-level роли, назначенной пользователю. -
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 могут быть разные клиенты, например, UI, GraphQL, или REST API. Каждый клиент имеет свой собственный стандартный механизм аутентификации, например:
-
Окно входа UI
-
Аутентификация REST
Собственная валидация паролей
Чтобы реализовать собственную валидацию паролей в приложении, достаточно создать бин (или несколько бинов), реализующий интерфейс PasswordValidator
. Например:
package security.ex1.security;
import io.jmix.securityui.password.PasswordValidationContext;
import io.jmix.securityui.password.PasswordValidationException;
import io.jmix.securityui.password.PasswordValidator;
import org.springframework.stereotype.Component;
import security.ex1.entity.User;
@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
protected void onBeforeCommit(BeforeCommitChangesEvent event) {
if (entityStates.isNew(getEditedEntity())) {
// ...
List<String> validationErrors = passwordValidation.validate(getEditedEntity(), passwordField.getValue());
if (!validationErrors.isEmpty()) {
notifications.create(Notifications.NotificationType.WARNING)
.withCaption(String.join("\n", validationErrors))
.show();
event.preventCommit();
}
getEditedEntity().setPassword(passwordEncoder.encode(passwordField.getValue()));
}
}
Защита от взлома методом перебора
В фреймворке есть механизм защиты от взлома паролей методом перебора, которая обеспечивается свойством приложения 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 CustomerBrowse extends StandardLookup<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. |
Системная аутентификация
Поток выполнения может быть не аутентифицирован, если был запущен внутренним планировщиком или обрабатывает запрос из 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 содержит сущность пользователя. |