我们正在升级到Spring Security 5.8
,为6
的更大更新做准备,并且我们需要在单个Sping Boot 应用程序中使用多个AuthorizedClientManager
。我还没有找到关于这个主题的结论性指南,所以我希望一些社区的反馈会有所帮助。
我们正在使用Spring Cloud NetflixEureka 服务器进行服务发现(我知道有点旧),并在同一个应用程序中部署了Spring Boot Admin。我们使用Keycloak作为OAuth2授权服务器。
基本前提是:
- 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
。
在这种情况下,我该如何进行?
1条答案
按热度按时间r1wp621o1#
不将
AuthorizedClientServiceOAuth2AuthorizedClientManager
公开为@Bean
(DI中唯一的OAuth2AuthorizedClientManager
bean是DefaultOAuth2AuthorizedClientManager
,对于处理HTTP请求的应用程序来说是“正常的”)怎么样?你可以修改你的
HttpHeadersProvider
如下: