Spring 启动 Azure 多 HttpSecurity

Spring Boot Azure Multiple HttpSecurity

是否可以混合使用两种身份验证模式?

到目前为止我有这个:

@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);
        }
    }
}

我们有混合帐户(外部 users/internal 用户),所以我们需要首先检查哪种帐户想要访问。

我的想法是为 internal/external 用户提供一个专用的登录表单,在其中完成路由,例如 /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 是 spring 安全和基于表单的身份验证的固定路径。那么这是为了在混合环境中使用 azure 吗?

此设置是否会以任何方式相互冲突?

谢谢!

多亏了评论员的意见和大量的谷歌搜索,我最终得到了这个工作版本:

@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

我不知道我们是否会在生产就绪代码中实现它。
就个人而言,我对这种混合各种身份验证方案的感觉很糟糕。

我认为最好将两者(当有 external/internal 用户时)分成单独的应用程序,单独 SecurityConfiguration

欢迎任何help/comments/tips混音external/internal用户!