1.x: Отключить использование jmix.ldap.standard-authentication-users

Добрый день!

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 не доходит.

Настройки такие:

jmix.ldap.use-active-directory-configuration = true
jmix.ldap.active-directory-domain = company.ru
jmix.ldap.enabled = true
jmix.ldap.urls = ldaps://dcs003.sub.company.ru:636/
jmix.ldap.base-dn = DC=sub,DC=company,DC=ru
jmix.ldap.manager-dn = CN=ADReader,OU=TechAccounts,DC=sub,DC=company,DC=ru
jmix.ldap.manager-password = ************
jmix.ldap.user-search-filter = (&(objectClass=user)(sAMAccountName={1}))
jmix.ldap.user-search-base = DC=sub,DC=company,DC=ru
jmix.ldap.username-attribute = sAMAccountName
jmix.ldap.synchronize-role-assignments = false
jmix.ldap.synchronize-user-on-login = true
jmix.ldap.default-roles = ui-minimal, ui-filter

При первом входе доменного пользователя (если его в БД еще нет) все отрабатывает - пользователь заходит, создается запись в БД.
А если пользователь уже существует в БД, то при входе 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");
        }
    }
1 симпатия

Предистория:
У меня в приложение на данный момент могут входить как доменные, так и локальные пользователи.
Локальные пользователи могут входить, только если они указаны в параметре jmix.ldap.standard-authentication-users.
Получается, если я создаю локального пользователя, то необходимо добавлять его в параметр приложения и перезапускать приложение. Как-то не удобно получается. :slight_smile:

Поэтому я решил избавиться от использования этого параметра.

Спасибо, Глеб за предложение: придется сделать у сущности User доп. атрибут.