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 КБ)