Windows SSO в Jmix-приложении

Доброго дня!

Вопрос: как реализовать sso в jmix-приложение для windows-пользователей, залогиненных в AD?

В cuba-platform можно было это сделать через библиотеку jespa, а какие здесь есть варианты?

Добрый день!
Нам в текущем проекте сильно необходима данная возможность (вход в приложение без ввода логина/пароля под доменными учётными данными, SSO), это требование заказчика.
Были бы очень признательны, если вы подскажете какие есть варианты реализации данной возможности в Jmix.
Перепробовали уже много разных вариантов, но ни один не заработал.
Заранее спасибо.

Здравствуйте,
В Jmix в данный момент не прорабатывался механизм интеграции с Jespa.
Судя по документации в CUBA это реализовывалось посредством реализации собственных фильтров и т.д. Including the Library - CUBA Platform. Developer’s Manual

Jmix это Spring Boot приложение. Для Security используется Spring Security.
В случае Spring Boot есть интеграция с https://github.com/Waffle/waffle
Стартер: https://mvnrepository.com/artifact/com.github.waffle/waffle-spring-boot-starter2
Пример Spring Boot проекта в https://github.com/mgoldgeier/waffle-spring-boot-demo

Попробуйте проверить на чистом Spring Boot приложение и затем перенести похожие конфигурации в Jmix приложение.

Спасибо за ответ!

Попробовал проект отсюда: https://github.com/mgoldgeier/waffle-spring-boot-demo: развернул в среде Windows 10, и да, при авторизации в домене, сходу открывается страница без всяких дополнительных окон, где видно, что я залогинился в приложение как доменный пользователь.

Однако при открытии этого же приложения, но развернутого на CentOS 7, сначала появляется окно авторизации, а при попытке ввода логина/пароля выдает ошибку:

java.lang.UnsatisfiedLinkError: Unable to load library 'Secur32': Native library (linux-x86-64/libSecur32.so)...

Ну и, в документации проекта https://github.com/Waffle/waffle написано следующее:

Waffle also includes libraries that enable drop-in Windows Single Sign On for popular Java web servers, when running on Windows. While Waffle makes it ridiculously easy to do Windows Authentication in Java, on Windows, Waffle does not work on *nix(UNIX-like).

Судя по всему, этот вариант не подходит, так как предстоит разворачивать приложение в *nix среде.

Может, есть еще какие-нибудь варианты?

Пробую интегрировать waffle в приложение jmix, столкнулся с тем, что никак не могу понять, как правильно сообщить приложению jmix о том, что пользователь аутентифицирован и пробросить его на главный экран приложения…

Добавил конфигурацию Waffle:

WaffleConfiguration
@Configuration
public class WaffleConfiguration {

    @Bean
    public WindowsAuthProviderImpl waffleWindowsAuthProvider() {
        return new WindowsAuthProviderImpl();
    }

    @Bean
    public NegotiateSecurityFilterProvider negotiateSecurityFilterProvider(
            WindowsAuthProviderImpl windowsAuthProvider) {
        return new NegotiateSecurityFilterProvider(windowsAuthProvider);
    }

    @Bean
    public BasicSecurityFilterProvider basicSecurityFilterProvider(WindowsAuthProviderImpl windowsAuthProvider) {
        return new BasicSecurityFilterProvider(windowsAuthProvider);
    }

    @Bean
    public SecurityFilterProviderCollection waffleSecurityFilterProviderCollection(
            NegotiateSecurityFilterProvider negotiateSecurityFilterProvider,
            BasicSecurityFilterProvider basicSecurityFilterProvider) {
        SecurityFilterProvider[] securityFilterProviders = {
                negotiateSecurityFilterProvider,
                basicSecurityFilterProvider};
        return new SecurityFilterProviderCollection(securityFilterProviders);
    }

    @Bean
    public NegotiateSecurityFilterEntryPoint negotiateSecurityFilterEntryPoint(
            SecurityFilterProviderCollection securityFilterProviderCollection) {
        NegotiateSecurityFilterEntryPoint negotiateSecurityFilterEntryPoint = new NegotiateSecurityFilterEntryPoint();
        negotiateSecurityFilterEntryPoint.setProvider(securityFilterProviderCollection);
        return negotiateSecurityFilterEntryPoint;
    }

    @Bean
    public NegotiateSecurityFilter waffleNegotiateSecurityFilter(SecurityFilterProviderCollection securityFilterProviderCollection) {
        NegotiateSecurityFilter negotiateSecurityFilter = new NegotiateSecurityFilter();
        negotiateSecurityFilter.setProvider(securityFilterProviderCollection);
        return negotiateSecurityFilter;
    }

    // This is required for Spring Boot so it does not register the same filter twice
    @Bean
    public FilterRegistrationBean waffleNegotiateSecurityFilterRegistration(NegotiateSecurityFilter waffleNegotiateSecurityFilter) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(waffleNegotiateSecurityFilter);
        registrationBean.setEnabled(false);
        return registrationBean;
    }
}

Добавил Security-конфигурацию:

WaffleSecurityConfiguration
@Configuration
//@Order(JmixOrder.HIGHEST_PRECEDENCE + 300)
public class WaffleSecurityConfiguration extends StandardSecurityConfiguration {

    @Autowired
    private NegotiateSecurityFilter negotiateSecurityFilter;

    @Autowired
    private NegotiateSecurityFilterEntryPoint entryPoint;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        http
//                .authorizeRequests()
//                .anyRequest()
//                .authenticated()
//                .and()
//                .httpBasic()
//                .authenticationEntryPoint(entryPoint)
//                .and()
//                .addFilterBefore(negotiateSecurityFilter, BasicAuthenticationFilter.class)
//                .addFilterAfter(customFilter, NegotiateSecurityFilter.class);
        http
                .authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                    .httpBasic()
                    .authenticationEntryPoint(entryPoint)
                .and()
                    .addFilterBefore(negotiateSecurityFilter, BasicAuthenticationFilter.class);
//                .apply(uiSecurity())
////                .and().exceptionHandling().authenticationEntryPoint(entryPoint)
//                .and()
//                .logout().logoutUrl("/logout").logoutSuccessUrl("/");
    }


//    @Override
//    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        super.configure(auth);
//        WindowsAuthenticationProvider windowsAuthenticationProvider = new WindowsAuthenticationProvider();
//        auth.authenticationProvider(windowsAuthenticationProvider);
//    }
}

При такой настройке, судя по логам, аутентификация происходит:

Логи
2021-10-26 14:29:39.855 DEBUG 13796 --- [nio-8080-exec-4] waffle.spring.NegotiateSecurityFilter    : GET /, contentlength: -1
2021-10-26 14:29:39.896 DEBUG 13796 --- [nio-8080-exec-4] w.s.NegotiateSecurityFilterEntryPoint    : [waffle.spring.NegotiateEntryPoint] commence
2021-10-26 14:29:39.906 DEBUG 13796 --- [nio-8080-exec-4] waffle.spring.NegotiateSecurityFilter    : GET /, contentlength: -1
2021-10-26 14:29:39.907 DEBUG 13796 --- [nio-8080-exec-4] w.s.spi.NegotiateSecurityFilterProvider  : security package: Negotiate, connection id: 0:0:0:0:0:0:0:1:2319
2021-10-26 14:29:39.907 DEBUG 13796 --- [nio-8080-exec-4] w.s.spi.NegotiateSecurityFilterProvider  : token buffer: 133 byte(s)
2021-10-26 14:29:39.971 DEBUG 13796 --- [nio-8080-exec-4] w.s.spi.NegotiateSecurityFilterProvider  : continue token: oYIBNzCCATOgAwoBAaEMBgorBgEEAYI3AgIKooIBHASCARhOVExNU1NQAAIAAAASABIAOAAAABXCieL7XbYVOPSbf0IAAQAAAAAAzgDOAEoAAAAKAGFKAAAAD0MAUgBPAFMAUwBUAEUAQwBIAAIAEgBDAFIATwBTAFMAVABFAEMASAABAB4ARABFAFMASwBUAE8AUAAtAEkAVABFADIANgAyAEIABAAeAGMAcgBvAHMAcwB0AGUAYwBoAC4AbABvAGMAYQBsAAMAPgBEAEUAUwBLAFQATwBQAC0ASQBUAEUAMgA2ADIAQgAuAGMAcgBvAHMAcwB0AGUAYwBoAC4AbABvAGMAYQBsAAUAHgBjAHIAbwBzAHMAdABlAGMAaAAuAGwAbwBjAGEAbAAHAAgACkKxw1zK1wEAAAAA
2021-10-26 14:29:39.971 DEBUG 13796 --- [nio-8080-exec-4] w.s.spi.NegotiateSecurityFilterProvider  : continue required: true
2021-10-26 14:29:39.974 DEBUG 13796 --- [nio-8080-exec-6] waffle.spring.NegotiateSecurityFilter    : GET /, contentlength: -1
2021-10-26 14:29:39.974 DEBUG 13796 --- [nio-8080-exec-6] w.s.spi.NegotiateSecurityFilterProvider  : security package: Negotiate, connection id: 0:0:0:0:0:0:0:1:2319
2021-10-26 14:29:39.974 DEBUG 13796 --- [nio-8080-exec-6] w.s.spi.NegotiateSecurityFilterProvider  : token buffer: 121 byte(s)
2021-10-26 14:29:39.979 DEBUG 13796 --- [nio-8080-exec-6] w.s.spi.NegotiateSecurityFilterProvider  : continue token: oRswGaADCgEAoxIEEAEAAADhNSVbomfpXQAAAAA=
2021-10-26 14:29:39.979 DEBUG 13796 --- [nio-8080-exec-6] w.s.spi.NegotiateSecurityFilterProvider  : continue required: false
2021-10-26 14:29:40.079 DEBUG 13796 --- [nio-8080-exec-6] waffle.spring.NegotiateSecurityFilter    : logged in user: TESTDOMAIN\pupkin.i (XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX)
2021-10-26 14:29:40.275 DEBUG 13796 --- [nio-8080-exec-6] waffle.spring.NegotiateSecurityFilter    : roles: TESTDOMAIN\pupkin.i, TESTDOMAIN\....., BUILTIN\...., ....
2021-10-26 14:29:40.275  INFO 13796 --- [nio-8080-exec-6] waffle.spring.NegotiateSecurityFilter    : successfully logged in user: TESTDOMAIN\pupkin.i

Однако, в браузере открывается страница со следующим содержимым:

{“timestamp”:“2021-10-26T11:29:40.502+00:00”,“status”:403,“error”:“Forbidden”,“path”:"/"}

Пробовал также добавлять кастомный фильтр (.addFilterAfter(customFilter, NegotiateSecurityFilter.class)), ожидая, что смогу получить логин пользователя, добавив его в заголовки запросов, а в контроллере LoginScreen попытаться его вытащить через VaadinServletRequest request = VaadinServletRequest.getCurrent();, но и это не помогло. Подозреваю, что это как-то слишком сложно, и всё должно быть проще.

Как правильно сконфигурировать SecurityConfiguration, чтобы у аутентифицированного через waffle пользователя сразу открывался главный экран приложения, а у других открывался стандартный LoginScreen Jmix?

Прикладываю тестовый проект
test.zip (77.0 КБ)

На нашем проекте вопрос также по-прежнему очень актуален. Заказчику необходим вход в приложение без ввода пароля, пробуем прикрутить Waffle к Jmix. Были бы очень признательны, если бы вы подсказали как это правильно сделать.
Заранее спасибо.

Добрый день!
Мы понимаем важность вопроса, но готового решения на данный момент у нас нет.
Завели тикет, займемся им как только появится возможность.

С уважением,
Константин

Поддержу коллег.
Простая и прозрачная интеграция с AD “маст хэв” для энтерпрайза и копоративных приложений (в России точно), так что мы вынуждены все время сдвигать сроки начала тестирования миграции на jmix ранее разработанных приложений.

1 симпатия

Всем привет.
Беглый поиск в гугле рассказал, что Keycloak IDP умеет в SSO, в связке с AD.
По ссылке ниже пример интеграции Jmix c Keycloak, от команды Jmix. На основании этого примера мы интегрировали приложении с IDP, который у нас уже был настроен.
До SSO руки дойдут на следующей неделе.

Может кому-то будет полезно.

Доброго дня всем! Да, действительно, когда вынесли аутентификацию в keycloak, удалось настроить SSO через Kerberos. Также в проекте есть пример получения данных по rest (access_token при этом тоже даёт keycloak, а не Jmix-приложение), это тоже работает (UserController).

Но сразу всплыла другая проблема: как только к этому же приложению подключить аддон Jmix REST-API, получение данных по rest api не работает.

С одной стороны: нельзя получить jmix access-токен по /auth/token - идет перенаправление на страницу логина keycloak.
С другой стороны: не открывается /rest/userInfo - так как Jmix-приложение пытается искать выданный токен у себя в бд в таблице oauth_access_token и не находит его (т.к. его выдал keycloak).

Т.е. без аддона REST-API всё ок, но нужна функциональность, которая уже реализована в аддоне REST-API.

Можно ли как-то быстро сконфигурировать приложение jmix так, чтобы “подменить” стандартную аутентификацию rest-api аутентификацией через keycloak и как это правильно сделать? Понятно, что наверно можно было бы не подключать аддон REST-api вообще и реализовать свои контроллеры по аналогии с аддоном REST-API, но неужели это единственный способ?

1 симпатия

Если быстро то можно сделать второй инстанс jmix (полностью повторяющий первый инстанс) но со встроенной авторизацией, и к REST подключаться через него.
Если нет возможности поднимать еще один сабдомен - то деплойнуть его в новый контекст на том же апп-сервере.
Multitenancy – Jmix

Это если ну вот совсем быстро =)

З.Ы.: в целом, по моему скромному мнению - такое разделение вполне жизнеспособно.