Jmix version: 1.5.5
Jmix Studio plugin version: 2.2.1-232
IntelliJ version: IntelliJ IDEA 2023.2.6 (Community Edition)
Используется аутентификация через Active Directory.
Необходимо отключить использование параметра ldap jmix.ldap.standard-authentication-users
Проверка этого параметра выполняется в этом обработчике LdapActiveDirectorySecurityConfiguration.onPreAuthenticationCheckEvent
@EventListener
@Order(JmixOrder.LOWEST_PRECEDENCE - 10)
public void onPreAuthenticationCheckEvent(PreAuthenticationCheckEvent event) {
if (!ldapProperties.getStandardAuthenticationUsers().contains(event.getUser().getUsername())) {
throw new BadCredentialsException("Current user cannot be authenticated via standard authentication");
}
}
Не могу сообразить, как мне исключить выполнение этого кода в автоконфигурации ldap.
Класс LdapActiveDirectorySecurityConfiguration инициализируется в автоконфигурации LdapAutoConfiguration. Решил заменить штатную автоконфигурацию своей.
Сделал свой полный клон класса LdapAutoConfiguration, а исходный отключил в конфиге: spring.autoconfigure.exclude=io.jmix.autoconfigure.ldap.LdapAutoConfiguration
Моя автоконфигурация отрабатывает:
SpmLdapAutoConfiguration.DefaultLdapActiveDirectorySecurityConfiguration:
Did not match:
- @ConditionalOnMissingBean (types: io.jmix.security.StandardSecurityConfiguration,io.jmix.ldap.LdapActiveDirectorySecurityConfiguration; SearchStrategy: all) found beans of type 'io.jmix.ldap.LdapActiveDirectorySecurityConfiguration' spmLdapAutoConfiguration.DefaultLdapActiveDirectorySecurityConfiguration (OnBeanCondition)
Matched:
- AllNestedConditions 2 matched 0 did not; NestedCondition on SpmLdapAutoConfiguration.OnActiveDirectoryLdapConfigurationCondition.useActiveDirectoryConfiguration @ConditionalOnProperty (jmix.ldap.use-active-directory-configuration=true) matched; NestedCondition on SpmLdapAutoConfiguration.OnActiveDirectoryLdapConfigurationCondition.ldapEnabled @ConditionalOnProperty (jmix.ldap.enabled=true) matched (SpmLdapAutoConfiguration.OnActiveDirectoryLdapConfigurationCondition)
SpmLdapAutoConfiguration.DefaultLdapSecurityConfiguration:
Did not match:
- AllNestedConditions 1 matched 1 did not; NestedCondition on SpmLdapAutoConfiguration.OnDefaultLdapConfigurationCondition.useActiveDirectoryConfiguration @ConditionalOnProperty (jmix.ldap.use-active-directory-configuration=false) found different value in property 'use-active-directory-configuration'; NestedCondition on SpmLdapAutoConfiguration.OnDefaultLdapConfigurationCondition.ldapEnabled @ConditionalOnProperty (jmix.ldap.enabled=true) matched (SpmLdapAutoConfiguration.OnDefaultLdapConfigurationCondition)
Но я теперь не могу инжектить LdapProperties в свои классы:
required a bean of type 'io.jmix.ldap.LdapProperties' that could not be found.
Action:
Consider defining a bean of type 'io.jmix.ldap.LdapProperties' in your configuration.
В коде LdapAutoConfiguration я вижу, что LdapActiveDirectorySecurityConfiguration создаётся только если бин с таким типом не объявлен:
@EnableWebSecurity
@Conditional(OnActiveDirectoryLdapConfigurationCondition.class)
@ConditionalOnMissingBean({StandardSecurityConfiguration.class, LdapActiveDirectorySecurityConfiguration.class})
public static class DefaultLdapActiveDirectorySecurityConfiguration extends LdapActiveDirectorySecurityConfiguration {
}
Попробуйте у себя в проекте определить бин, который наследует LdapActiveDirectorySecurityConfiguration и в нём переопределить метод с листенером на событие.
@Component("SpmLdapActiveDirectorySecurityConfiguration")
public class SpmLdapActiveDirectorySecurityConfiguration extends LdapActiveDirectorySecurityConfiguration {
@EventListener
@Order(JmixOrder.LOWEST_PRECEDENCE - 10)
@Override
public void onPreAuthenticationCheckEvent(PreAuthenticationCheckEvent event) {
// Disable checking parameter 'jmix.ldap.standard-authentication-users'
}
}
Отладчиком убедился, что программа заходит в метод моего бина.
Локальные пользователи входят без проблем, а доменные пользователи вызывают ошибку:
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:289) ~[spring-security-crypto-5.7.11.jar:5.7.11]
at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:237) ~[spring-security-crypto-5.7.11.jar:5.7.11]
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.additionalAuthenticationChecks(DaoAuthenticationProvider.java:77) ~[spring-security-core-5.7.11.jar:5.7.11]
at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:147) ~[spring-security-core-5.7.11.jar:5.7.11]
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182) ~[spring-security-core-5.7.11.jar:5.7.11]
at io.jmix.securityui.authentication.LoginScreenSupport.authenticate(LoginScreenSupport.java:163) ~[jmix-security-ui-1.5.5.jar:na]
at ru.company.spm.screen.login.LoginScreen.login(LoginScreen.java:143) ~[main/:na]
Я взял пустой проект, где у меня был настроен аддон LDAP c Active Directory и добавил туда бин, который вы описали выше - всё работает без проблем: и доменные пользователи, и обычные заходят без ошибок. Попробуйте воспроизвести ошибку на новом проекте. Возможно, что-то в вашем текущем проекте ломает поведение.
Создал пустой проект.
Добавил параметры ldap/AD в application.properties
Добавил бин синхронизации пользователей копированием из документации
Получаю ту же ошибку при входе доменного пользователя:
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:289) ~[spring-security-crypto-5.7.11.jar:5.7.11]
at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:237) ~[spring-security-crypto-5.7.11.jar:5.7.11]
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.additionalAuthenticationChecks(DaoAuthenticationProvider.java:77) ~[spring-security-core-5.7.11.jar:5.7.11]
at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:147) ~[spring-security-core-5.7.11.jar:5.7.11]
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182) ~[spring-security-core-5.7.11.jar:5.7.11]
at io.jmix.securityui.authentication.LoginScreenSupport.authenticate(LoginScreenSupport.java:163) ~[jmix-security-ui-1.5.5.jar:na]
at com.company.ldap.screen.login.LoginScreen.login(LoginScreen.java:116) ~[main/:na]
Странно, что ошибку выдает DaoAuthenticationProvider и до ActiveDirectoryLdapAuthenticationProvider не доходит.
При первом входе доменного пользователя (если его в БД еще нет) все отрабатывает - пользователь заходит, создается запись в БД.
А если пользователь уже существует в БД, то при входе DaoAuthenticationProvider выдает ошибку There is no PasswordEncoder mapped for the id "null"
Уточню: тем, что вы отключаете вот эту логику, вы хотите добиться того, чтобы в приложение логинились люди как из домена, так и все, явно заведённые руками в экране пользователей Jmix?
@EventListener
@Order(JmixOrder.LOWEST_PRECEDENCE - 10)
public void onPreAuthenticationCheckEvent(PreAuthenticationCheckEvent event) {
if (!ldapProperties.getStandardAuthenticationUsers().contains(event.getUser().getUsername())) {
throw new BadCredentialsException("Current user cannot be authenticated via standard authentication");
}
}
Раньше просто проверка выше не пускала пользователей, которые созданы с помощью синхронизации, в DaoAuthenticationProvider, и соответственно их аутентификация уходила в ActiveDirectoryLdapAuthenticationProvider. Когда вы отключили эту проверку, пользователи, которые были созданы синхронизацией, полноценно проходят через DaoAuthenticationProvider, но т.к. пароль у них пустой, то соответственно этот эксепшн и появляется.
Как вариант можно проверку не отключить, а слегка изменить. Можно попробовать кидать эксепшн, если у юзера нет пароля (т.е. он создан синхронизацией). Либо какой-то признак в сущность пользователя ввести, что данного юзера надо аутентифицировать через AD и заполнять этот признак при синхронизации.
@EventListener
@Order(JmixOrder.LOWEST_PRECEDENCE - 10)
@Override
public void onPreAuthenticationCheckEvent(PreAuthenticationCheckEvent event) {
UserDetails user = event.getUser();
if (Strings.isNullOrEmpty(user.getPassword())) {
throw new BadCredentialsException("Current user cannot be authenticated via standard authentication");
}
}
Предистория:
У меня в приложение на данный момент могут входить как доменные, так и локальные пользователи.
Локальные пользователи могут входить, только если они указаны в параметре jmix.ldap.standard-authentication-users.
Получается, если я создаю локального пользователя, то необходимо добавлять его в параметр приложения и перезапускать приложение. Как-то не удобно получается.
Поэтому я решил избавиться от использования этого параметра.
Спасибо, Глеб за предложение: придется сделать у сущности User доп. атрибут.