Spring Security 5.8中的单个Sping Boot 应用程序中的多个OAuth2AuthorizedClientManager

9wbgstp7  于 2023-10-15  发布在  Spring
关注(0)|答案(1)|浏览(109)

我们正在升级到Spring Security 5.8,为6的更大更新做准备,并且我们需要在单个Sping Boot 应用程序中使用多个AuthorizedClientManager。我还没有找到关于这个主题的结论性指南,所以我希望一些社区的反馈会有所帮助。
我们正在使用Spring Cloud NetflixEureka 服务器进行服务发现(我知道有点旧),并在同一个应用程序中部署了Spring Boot Admin。我们使用Keycloak作为OAuth2授权服务器。
基本前提是:

  1. Sping Boot Admin有一个服务帐户,它可以用来向例如/actuator端点。
    1.对SBA UI的访问 * 在一个需要用户登录才能查看的登录掩码后面受到保护。
    在Spring Security 5.8之前,我们可以按照this指南设置安全的SBA访问。
    现在,我们有以下内容:
---
spring:
  application:
    name: eureka-server
  boot:
    admin:
      client:
        prefer-ip: true
      context-path: /admin
  security.oauth2.client:
    # UI client
    provider.external.issuer-uri: ${SBA_AUTH_SERVER_URL}/realms/${REALM}
    registration.external:
      provider: external
      token-uri: ${SBA_AUTH_SERVER_URL}/realms/${REALM}/protocol/openid-connect/token
      client-name: spring-boot-admin-ui
      client-id: spring-boot-admin
      scope: openid,offline_access,profile,email
      authorization-grant-type: authorization_code

    # SBA Service account client
    provider.sba.issuer-uri: ${SBA_AUTH_SERVER_URL}/realms/${REALM}
    registration.sba:
      provider: sba
      token-uri: ${SBA_AUTH_SERVER_URL}/realms/${REALM}/protocol/openid-connect/token
      client-name: spring-boot-admin
      client-id: spring-boot-admin
      client-secret: ${SBA_CLIENT_SECRET}
      authorization-grant-type: client_credentials

jwt:
  auth:
    converter:
      resource-id: spring-boot-admin
      principal-attribute: preferred_username

连同以下配置:

@Configuration
public class OAuth2ManagerConfig {

    @Bean
    public OAuth2AuthorizedClientService auth2AuthorizedClientService(ClientRegistrationRepository clientRegistrationRepository) {
        return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
    }

    @Bean
    public AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientServiceAndManager (
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientService authorizedClientService) {

        return new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository,
                authorizedClientService);
    }

    @Bean
    public DefaultOAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientRepository authorizedClientRepository) {

        DefaultOAuth2AuthorizedClientManager authorizedClientManager = 
            new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientRepository);

        authorizedClientManager.setContextAttributesMapper(new DefaultOAuth2AuthorizedClientManager.DefaultContextAttributesMapper());

        return authorizedClientManager;
    }
}
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    private final String apiToken;

    private final ClientRegistrationRepository clientRegistrationRepository;
    private final AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientServiceAndManager;

    @Autowired
    public WebSecurityConfig(@Value("${SBA_API_TOKEN}") final String apiToken,
                             final ClientRegistrationRepository clientRegistrationRepository,
                             final AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientServiceAndManager) {
        this.apiToken = apiToken;
        this.clientRegistrationRepository = clientRegistrationRepository;
        this.authorizedClientServiceAndManager = authorizedClientServiceAndManager;
    }
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .oauth2Client()
                .and()
                .oauth2Login()
                .tokenEndpoint()
                .and()
                .userInfoEndpoint().userAuthoritiesMapper(new MyAuthoritiesMapper());
        http
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
        http
                .csrf().disable()
                .authorizeHttpRequests()
                .requestMatchers("/*/*.css", "/admin/img/**", "/admin/third-party/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().permitAll();

        http.logout(httpSecurityLogoutConfigurer -> {
            httpSecurityLogoutConfigurer.clearAuthentication(true);
            httpSecurityLogoutConfigurer.invalidateHttpSession(true);
            httpSecurityLogoutConfigurer.logoutRequestMatcher(new AntPathRequestMatcher("/admin/logout"));
            httpSecurityLogoutConfigurer.logoutSuccessHandler(oidcLogoutSuccessHandler());
        });
        return http.build();
    }

    private LogoutSuccessHandler oidcLogoutSuccessHandler() {
        OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler =
                new OidcClientInitiatedLogoutSuccessHandler(this.clientRegistrationRepository);

        oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");

        return oidcLogoutSuccessHandler;
    }

    /**
     * {@link HttpHeadersProvider} used to populate the {@link HttpHeaders} for
     * accessing the state of the discovered clients.
     *
     * @return
     */
    @Bean
    public HttpHeadersProvider keycloakBearerAuthHeaderProvider() {
        return provider -> {
            HttpHeaders headers = new HttpHeaders();
            headers.add("X-Api-Token", apiToken);
            OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("sba")
                    .principal("spring-boot-admin")
                    .build();

            OAuth2AuthorizedClient authorizedClient = this.authorizedClientServiceAndManager.authorize(authorizeRequest);
            OAuth2AccessToken accessToken = Objects.requireNonNull(authorizedClient).getAccessToken();
            headers.add("X-Authorization-Token", "keycloak-bearer "
                    + authorizedClient.getAccessToken().getTokenValue());

            return headers;

        };
    }
}

这适用于内部Sping Boot Admin应用程序用于执行各种/actuator/**调用的服务帐户。
然而,我在设置一个 * 单独的 * 客户端和AuthorizedClientManager以在导航时显示登录掩码时遇到了麻烦。具体来说,添加这部分配置:

.authorizeHttpRequests()
               .requestMatchers("/*/*.css", "/admin/img/**", "/admin/third-party/**").permitAll()
               .requestMatchers("/admin/**").hasRole("ADMIN")

使/admin路径(应该重定向到我们的Keycloak登录页面)只产生以下空白页面:

我想这是因为我还没有真正 * 配置 * 在这个登录过程中应该使用哪个AuthorizedClientManager
我仍然在熟悉所有Spring Security OAuth2 API,但我认为核心问题是我需要 both a:

  • DefaultOAuth2AuthorizedClientManager用于正常的基于会话的登录(用户导航到UI,重定向,登录),
  • AuthorizedClientServiceOAuth2AuthorizedClientManager用于后台线程和基于client_credentials的登录。

然后必须自定义我的SecurityFilterChain

.oauth2Client(httpSecurityOAuth2ClientConfigurer ->
                        httpSecurityOAuth2ClientConfigurer.authorizedClientService(...))

但是DefaultOAuth2AuthorizedClientManager不暴露底层的AuthorizedClientService
在这种情况下,我该如何进行?

r1wp621o

r1wp621o1#

AuthorizedClientServiceOAuth2AuthorizedClientManager公开为@Bean(DI中唯一的OAuth2AuthorizedClientManager bean是DefaultOAuth2AuthorizedClientManager,对于处理HTTP请求的应用程序来说是“正常的”)怎么样?

你可以修改你的HttpHeadersProvider如下:

@Component
public static class KeycloakBearerAuthHeaderProvider implements HttpHeadersProvider {

    private final String apiToken;
    private final AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager;

    public KeycloakBearerAuthHeaderProvider(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientService authorizedClientService,
            @Value("${SBA_API_TOKEN}") String apiToken) {
        this.apiToken = apiToken;
        this.authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService);
    }

    @Override
    public HttpHeaders getHeaders(Instance provider) {
        HttpHeaders headers = new HttpHeaders();
        headers.add("X-Api-Token", apiToken);
        OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("sba").principal("spring-boot-admin").build();

        OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
        OAuth2AccessToken accessToken = Objects.requireNonNull(authorizedClient).getAccessToken();
        headers.add("X-Authorization-Token", "keycloak-bearer " + accessToken.getTokenValue());

        return headers;
    }
}

相关问题