Spring Boot Azure Несколько HttpSecurity

Можно ли смешивать два режима аутентификации?

  • Внутренний пользователь: объявление Azure
  • Внешний пользователь: проверка подлинности с помощью формы

Пока у меня это:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {

    @Configuration
    @Order(1)
    public static class MfaAuthentication extends AadWebSecurityConfigurerAdapter {

        private final UserService userService;

        @Autowired
        public MfaAuthentication(UserService userService) {
            this.userService = userService;
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            super.configure(http);
            http
                .antMatcher("/internal/**")
                .authorizeHttpRequests()
                    .anyRequest().authenticated()
                    .and()
                .oauth2Login()
                    .userInfoEndpoint(userInfoEndpointConfig -> {
                        userInfoEndpointConfig.oidcUserService(this.oidcUserService());
                    });
        }

        private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
            final OidcUserService delegate = new OidcUserService();
            return (userRequest) -> {
                // Delegate to the default implementation for loading a user
                OidcUser oidcUser = delegate.loadUser(userRequest);

                OAuth2AccessToken accessToken = userRequest.getAccessToken();
                Set<GrantedAuthority> mappedAuthorities = new HashSet<>();

                // TODO
                // 1) Fetch the authority information from the protected resource using accessToken
                // 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities

                // 3) Create a copy of oidcUser but use the mappedAuthorities instead

                List<String> dummy = userService.fetchUserRoles("dummy");
                dummy.forEach(user -> mappedAuthorities.add((GrantedAuthority) () -> user));
                oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());

                return oidcUser;
            };
        }
    }

    @Configuration
    public static class ExternalAuthentication extends WebSecurityConfigurerAdapter {

        private final ThdAuthenticationProvider thdAuthenticationProvider;

        @Autowired
        public ExternalAuthentication(ThdAuthenticationProvider thdAuthenticationProvider) {
            this.thdAuthenticationProvider = thdAuthenticationProvider;
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .antMatcher("/external/**")
                .authorizeRequests()
                    .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
                    .anyRequest().fullyAuthenticated()
                    .and()
                .formLogin()
                    .loginPage("/external/login").permitAll()
                    .defaultSuccessUrl("/external/index", true)
                    .failureUrl("/external/denied")
                    .and()
                .logout()
                    .invalidateHttpSession(true)
                    .and()
                    .authenticationProvider(thdAuthenticationProvider);
        }
    }
}

У нас есть смешанные учетные записи (внешние пользователи/внутренние пользователи), поэтому нам нужно проверить, какой тип учетной записи хочет иметь доступ в первую очередь.

Моя идея состоит в том, чтобы предоставить специальную форму входа для внутреннего/внешнего пользователя, где маршрутизация выполняется так, как /internal/** идет к нашему входу в Azure, а /external/** идет к пользовательскому поставщику аутентификации.

Когда я еду в http://localhost:8080/internal, меня перенаправляют на http://localhost:8080/oauth2/authorization/azure, говоря, что карты нет. Я хочу, чтобы меня перенаправили на нашу учетную запись Azure.

Это можно сделать?

РЕДАКТИРОВАТЬ

application.properties

# Enable related features.
spring.cloud.azure.active-directory.enabled=true
# Specifies your Active Directory ID:
spring.cloud.azure.active-directory.profile.tenant-id=some-id
# Specifies your App Registration's Application ID:
spring.cloud.azure.active-directory.credential.client-id=some-client-id
# Specifies your App Registration's secret key:
spring.cloud.azure.active-directory.credential.client-secret=some-secret

Сообщение об ошибке:

Whitelabel Error Page

This application has no explicit mapping for /error, so you are seeing this as a fallback.
Fri May 06 12:41:41 CEST 2022
There was an unexpected error (type=Not Found, status=404).

РЕДАКТИРОВАТЬ 2

Благодаря комментариям я понял правильную конфигурацию - по крайней мере, для маршрутизации. У меня на данный момент такая конфигурация:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {

    @Configuration

    public static class MfaAuthentication extends AadWebSecurityConfigurerAdapter {

        private final UserService userService;

        @Autowired
        public MfaAuthentication(UserService userService) {
            this.userService = userService;
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            super.configure(http);
            http
                    .authorizeRequests()
                    .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
                    .antMatchers("/index").permitAll()
                    .antMatchers("/public/**").permitAll()
                    .antMatchers("/internal/**").hasAnyAuthority("Administrator")
                    .anyRequest()
                    .authenticated()
                    .and()
                    .oauth2Login()
                    .userInfoEndpoint(userInfoEndpointConfig -> {
                        userInfoEndpointConfig.oidcUserService(this.oidcUserService());
                    })
                    .defaultSuccessUrl("/internal/index", true);

        }

        private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
            final OidcUserService delegate = new OidcUserService();
            return (userRequest) -> {
                // Delegate to the default implementation for loading a user
                OidcUser oidcUser = delegate.loadUser(userRequest);

                OAuth2AccessToken accessToken = userRequest.getAccessToken();
                Set<GrantedAuthority> mappedAuthorities = new HashSet<>();

                // TODO
                // 1) Fetch the authority information from the protected resource using accessToken
                // 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities

                // 3) Create a copy of oidcUser but use the mappedAuthorities instead

                List<String> dummy = userService.fetchUserRoles("dummy");
                dummy.forEach(user -> mappedAuthorities.add((GrantedAuthority) () -> user));
                oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());

                return oidcUser;
            };
        }
    }

    @Configuration
    @Order(1)
    public static class ExternalAuthentication extends WebSecurityConfigurerAdapter {

        private final ThdAuthenticationProvider thdAuthenticationProvider;

        @Autowired
        public ExternalAuthentication(ThdAuthenticationProvider thdAuthenticationProvider) {
            this.thdAuthenticationProvider = thdAuthenticationProvider;
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .antMatcher("/external/**")
                    .authorizeRequests()
                    .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
                    .antMatchers("/login").permitAll()

                    .anyRequest()
                    .fullyAuthenticated()
                    .and()
                    .formLogin()
                    .loginPage("/external/login").permitAll()
                    .loginProcessingUrl("/external/login").permitAll()
                    .defaultSuccessUrl("/external/index", true)
                    .failureUrl("/external/denied")
                    .and()
                    .logout()
                    .invalidateHttpSession(true)
                    .and()
                    .authenticationProvider(thdAuthenticationProvider);
        }
    }


}

Проблема сейчас:

Когда я еду в /external/index, меня перенаправляют на мою пользовательскую страницу входа. Когда я хочу войти в систему (перенаправляется через POST в /login), меня перенаправляют на страницу, где я могу выбрать один из логинов oauth2, который сам нацелен на http://localhost:8080/oauth2/authorization/azure

Вот выдержка из моей формы (thymeleaf):

 <form action = "#" th:action = "@{/login}" method = "post" class = "form-signin"
          accept-charset = "utf-8">
</form> 

Я знаю, что /login — это фиксированный маршрут для весенней безопасности и аутентификации на основе форм. Так это предназначено для работы с лазурью в смешанной среде?

Эта установка как-то противоречит друг другу?

Благодарю вас!

Вы не настроили клиент OAuth2. Путь: spring.security.oauth2.client.

dur 06.05.2022 12:52

Короткий ответ: да, у вас может быть два разных режима аутентификации. Однако вы можете подумать о том, чтобы заставить работать первый (Azure), прежде чем внедрять второй в свой код.

jzheaux 06.05.2022 20:31

Спасибо вам всем. Пожалуйста, смотрите мой Редактировать 2. У меня есть новая проблема по этому поводу :) Может быть, вы можете помочь?

Thomas Lang 10.05.2022 14:57
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
4
82
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Благодаря вкладу комментаторов и тщательному гуглению я получил эту рабочую версию:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {

    @Configuration

    public static class MfaAuthentication extends AadWebSecurityConfigurerAdapter {

        private final UserService userService;

        @Autowired
        public MfaAuthentication(UserService userService) {
            this.userService = userService;
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            super.configure(http);
            http
                    .csrf()
                    .and()
                    .authorizeRequests(authorize -> authorize.antMatchers("/").permitAll()
                            .antMatchers("/index").permitAll()
                            .antMatchers("/public/**").permitAll()
                            .antMatchers("/internal/**").hasAnyAuthority("Administrator")
                            .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
                            .anyRequest().authenticated())
                    .oauth2Login()
                    .userInfoEndpoint(userInfoEndpointConfig -> userInfoEndpointConfig.oidcUserService(this.oidcUserService()))
                    .defaultSuccessUrl("/internal/index", true);

        }

        private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
            final OidcUserService delegate = new OidcUserService();
            return (userRequest) -> {
                // Delegate to the default implementation for loading a user
                OidcUser oidcUser = delegate.loadUser(userRequest);

                Set<GrantedAuthority> mappedAuthorities = new HashSet<>();

                List<String> dummy = userService.fetchUserRoles("dummy");
                dummy.forEach(user -> mappedAuthorities.add((GrantedAuthority) () -> user));
                oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());

                return oidcUser;
            };
        }
    }

    @Configuration
    @Order(1)
    public static class ExternalAuthentication extends WebSecurityConfigurerAdapter {

        private final ThdAuthenticationProvider thdAuthenticationProvider;

        @Autowired
        public ExternalAuthentication(ThdAuthenticationProvider thdAuthenticationProvider) {
            this.thdAuthenticationProvider = thdAuthenticationProvider;
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .antMatcher("/external/**")
                    .authorizeRequests(authorize -> authorize.antMatchers("/").permitAll()
                            .antMatchers("/index").permitAll()
                            .antMatchers("/public/**").permitAll()
                            .antMatchers("/external/**").hasAnyAuthority("External")
                            .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
                            .anyRequest().authenticated())
                    .formLogin()
                    .loginPage("/external/login").permitAll()
                    .loginProcessingUrl("/external/login").permitAll()
                    .defaultSuccessUrl("/external/index", true)
                    .failureUrl("/external/denied")
                    .and()
                    .logout()
                    .invalidateHttpSession(true)
                    .and()
                    .authenticationProvider(thdAuthenticationProvider);
        }
    }
}

Вот моя пользовательская внешняя форма входа в систему - по крайней мере, ее выдержка:

<form accept-charset = "utf-8" action = "#" class = "form-signin" method = "post"
          th:action = "@{/external/login}">
</form>

Все /internal/** маршруты идут к нашему входу в Azure AD.
Обратите внимание, что существует пользовательская служба oidc для загрузки дополнительных ролей для данного пользователя.

Все /external/** маршруты идут в наш кастом AuthenticationProvider

Я не знаю, будем ли мы реализовывать это в готовом для производства коде.
Лично у меня плохое предчувствие по поводу этого смешения различных сценариев аутентификации.

Я думаю, что лучше разделить оба (при наличии внешнего/внутреннего пользователя) на отдельные приложения с отдельными SecurityConfiguration

Любая помощь/комментарии/советы по смешиванию внешних/внутренних пользователей приветствуется!

Другие вопросы по теме