如何自定义 OAuth2 令牌请求的 Authorization 标头

时间:2021-07-10 00:25:26

标签: spring-security oauth-2.0

我正在使用 Spring Security 5.5 来执行访问令牌请求,最近升级到 5.5.1,现在我的客户端机密被我的 OAuth 2.0 提供商拒绝。这是由 a bug fix 根据 RFC 6749 Section 2.3.1 对客户端凭据进行 URL 编码造成的。

由于我的 OAuth 2.0 提供程序不合规,我想恢复到 Spring Security 5.5.0 中的旧行为,并发送不带 URL 编码的客户端凭据。

reference documentation 中,如果我定义一个 @Bean 类型的 OAuth2AuthorizedClientManager

    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientRepository authorizedClientRepository) {
        // @formatter:off
        OAuth2AuthorizedClientProvider authorizedClientProvider =
            OAuth2AuthorizedClientProviderBuilder.builder()
                .clientCredentials()
                .build();

        DefaultOAuth2AuthorizedClientManager authorizedClientManager =
            new DefaultOAuth2AuthorizedClientManager(
                clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
        // @formatter:on

        return authorizedClientManager;
    }

如何配置它以使用自定义转换器来设置凭据?

注意:this question 有关,但解决了 Servlet 支持而不是 WebClient 的响应式支持。

1 个答案:

答案 0 :(得分:0)

假设您有以下配置:

spring:
  security:
    oauth2:
      client:
        registration:
          test-client:
            provider: spring
            client-id: aladdin
            client-secret: "open sesame"
            authorization-grant-type: client_credentials
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
            scope: resource:read
        provider:
          spring:
            authorization-uri: http://auth-server:9000/oauth/authorize
            token-uri: http://auth-server:9000/oauth/token

在 OP 的示例中,OAuth2AuthorizedClientManager 支持 client_credentials 授予以发出访问令牌请求。这对前很有用。如果我们想实现以下(虚构的)端点:

@RestController
public class TokenController {
    @GetMapping("/token")
    public OAuth2AccessToken token(@RegisteredOAuth2AuthorizedClient("test-client") OAuth2AuthorizedClient testClient) {
        return testClient.getAccessToken();
    }
}

OAuth2AuthorizedClientManager 提供了本示例中注入的 OAuth2AuthorizedClient。它可以使用自定义转换器进行配置,如下所示:

注意:以下是改编自参考文档 Client Credentials section 的扩展示例。

    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientRepository authorizedClientRepository) {
        // @formatter:off
        OAuth2AuthorizedClientProvider authorizedClientProvider =
            OAuth2AuthorizedClientProviderBuilder.builder()
                .clientCredentials((builder) ->
                    builder.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient())
                        .build())
                .build();

        DefaultOAuth2AuthorizedClientManager authorizedClientManager =
            new DefaultOAuth2AuthorizedClientManager(
                clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
        // @formatter:on

        return authorizedClientManager;
    }

    @Bean
    public OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
        // @formatter:off
        OAuth2ClientCredentialsGrantRequestEntityConverter requestEntityConverter =
            new OAuth2ClientCredentialsGrantRequestEntityConverter();
        requestEntityConverter.setHeadersConverter(headersConverter());

        DefaultClientCredentialsTokenResponseClient accessTokenResponseClient =
            new DefaultClientCredentialsTokenResponseClient();
        accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);
        // @formatter:on

        return accessTokenResponseClient;
    }

    private static <T extends AbstractOAuth2AuthorizationGrantRequest> Converter<T, HttpHeaders> headersConverter() {
        // @formatter:off
        Converter<T, ClientRegistration> clientRegistrationConverter =
            AbstractOAuth2AuthorizationGrantRequest::getClientRegistration;
        return clientRegistrationConverter
            .andThen((clientRegistration) -> {
                HttpHeaders headers = new HttpHeaders();
                headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8));
                headers.setContentType(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"));
                headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
                return headers;
            });
        // @formatter:on
    }

如果我们想支持 authorization_code 授予登录应用程序,我们可以使用以下配置(包含上述方法和 @Bean 定义):

@EnableWebSecurity
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .authorizeRequests(authorizeRequests -> authorizeRequests
                .anyRequest().authenticated())
            .oauth2Login(oauth2Login -> oauth2Login
                .tokenEndpoint(tokenEndpoint -> tokenEndpoint
                    .accessTokenResponseClient(authorizationCodeAccessTokenResponseClient())));
        // @formatter:on

        return http.build();        
    }

    @Bean
    public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeAccessTokenResponseClient() {
        // @formatter:off
        OAuth2AuthorizationCodeGrantRequestEntityConverter requestEntityConverter =
            new OAuth2AuthorizationCodeGrantRequestEntityConverter();
        requestEntityConverter.setHeadersConverter(headersConverter());

        DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
            new DefaultAuthorizationCodeTokenResponseClient();
        accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);
        // @formatter:on

        return accessTokenResponseClient;
    }

    // ...

}