Spring Security 5 替代 OAuth2RestTemplate

新手上路,请多包涵

In spring-security-oauth2:2.4.0.RELEASE classes such as OAuth2RestTemplate , OAuth2ProtectedResourceDetails and ClientCredentialsAccessTokenProvider have all been marked as deprecated.

从这些类的 javadoc 中,它指向一个 spring security 迁移指南,暗示人们应该迁移到核心 spring-security 5 项目。但是,我无法找到如何在此项目中实现我的用例。

如果您希望对应用程序的传入请求进行身份验证并且希望使用第 3 方 OAuth 提供程序来验证身份,则所有文档和示例都会讨论与第 3 部分 OAuth 提供程序集成。

在我的用例中,我想做的就是使用 RestTemplate 向受 OAuth 保护的外部服务发出请求。目前,我创建了一个 OAuth2ProtectedResourceDetails 以及我传递给 OAuth2RestTemplate 的客户端 ID 和密码。我还有一个自定义 ClientCredentialsAccessTokenProvider 添加到 OAuth2ResTemplate 只是向我正在使用的 OAuth 提供程序所需的令牌请求添加一些额外的标头。

在 spring-security 5 文档中,我找到了一个部分,其中提到 了自定义令牌请求,但同样看起来是在使用第三方 OAuth 提供程序对传入请求进行身份验证的上下文中。目前尚不清楚如何将它与类似 ClientHttpRequestInterceptor 的东西结合使用,以确保每个对外部服务的传出请求首先获得一个令牌,然后将其添加到请求中。

同样在上面链接的迁移指南中,有对 OAuth2AuthorizedClientService 的引用,它说它对在拦截器中使用很有用,但这看起来又依赖于 ClientRegistrationRepository 之类的东西,它似乎如果您想使用该提供来确保传入请求经过身份验证,请在它维护第三方提供商注册的地方。

有什么方法可以利用 spring-security 5 中的新功能来注册 OAuth 提供程序,以便获取令牌以添加到我的应用程序的传出请求中?

原文由 Matt Williams 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.3k
2 个回答

Spring Security 5.2.x 的 OAuth 2.0 客户端特性不支持 RestTemplate ,只支持 --- WebClient 。请参阅 Spring 安全参考

HTTP 客户端支持

  • WebClient Servlet 环境集成(用于请求受保护的资源)

此外, RestTemplate 将在未来版本中弃用。请参阅 RestTemplate javadoc

注意: 从 5.0 开始,非阻塞、反应式 org.springframework.web.reactive.client.WebClient 提供了 RestTemplate 的现代替代方案,有效支持同步和异步,以及流媒体场景。 RestTemplate 将在未来的版本中弃用,并且以后不会添加主要的新功能。有关详细信息和示例代码,请参阅 Spring Framework 参考文档的 WebClient 部分。

因此,最好的解决方案是放弃 RestTemplate 支持 WebClient


使用 WebClient 客户端凭证流

以编程方式或使用 Spring Boot 自动配置配置客户端注册和提供程序:

 spring:
  security:
    oauth2:
      client:
        registration:
          custom:
            client-id: clientId
            client-secret: clientSecret
            authorization-grant-type: client_credentials
        provider:
          custom:
            token-uri: http://localhost:8081/oauth/token

… 和 OAuth2AuthorizedClientManager @Bean

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

    OAuth2AuthorizedClientProvider authorizedClientProvider =
            OAuth2AuthorizedClientProviderBuilder.builder()
                    .clientCredentials()
                    .build();

    DefaultOAuth2AuthorizedClientManager authorizedClientManager =
            new DefaultOAuth2AuthorizedClientManager(
                    clientRegistrationRepository, authorizedClientRepository);
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

    return authorizedClientManager;
}

配置 WebClient 实例以使用 ServerOAuth2AuthorizedClientExchangeFilterFunction 提供的 OAuth2AuthorizedClientManager

 @Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
    ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
            new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
    oauth2Client.setDefaultClientRegistrationId("custom");
    return WebClient.builder()
            .apply(oauth2Client.oauth2Configuration())
            .build();
}

现在,如果您尝试使用此 WebClient 实例发出请求,它将首先从授权服务器请求令牌并将其包含在请求中。

原文由 Anar Sultanov 发布,翻译遵循 CC BY-SA 4.0 许可协议

嗨,也许为时已晚,但是 Spring Security 5 仍然支持 RestTemplate,对于非反应性应用程序,RestTemplate 仍在使用您所要做的只是正确配置 spring 安全性并创建一个拦截器,如迁移指南中所述

使用以下配置使用 client_credentials 流

应用.yml

 spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: ${okta.oauth2.issuer}/v1/keys
      client:
        registration:
          okta:
            client-id: ${okta.oauth2.clientId}
            client-secret: ${okta.oauth2.clientSecret}
            scope: "custom-scope"
            authorization-grant-type: client_credentials
            provider: okta
        provider:
          okta:
            authorization-uri: ${okta.oauth2.issuer}/v1/authorize
            token-uri: ${okta.oauth2.issuer}/v1/token

OauthResTemplate 的配置

@Configuration
@RequiredArgsConstructor
public class OAuthRestTemplateConfig {

    public static final String OAUTH_WEBCLIENT = "OAUTH_WEBCLIENT";

    private final RestTemplateBuilder restTemplateBuilder;
    private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
    private final ClientRegistrationRepository clientRegistrationRepository;

    @Bean(OAUTH_WEBCLIENT)
    RestTemplate oAuthRestTemplate() {
        var clientRegistration = clientRegistrationRepository.findByRegistrationId(Constants.OKTA_AUTH_SERVER_ID);

        return restTemplateBuilder
                .additionalInterceptors(new OAuthClientCredentialsRestTemplateInterceptorConfig(authorizedClientManager(), clientRegistration))
                .setReadTimeout(Duration.ofSeconds(5))
                .setConnectTimeout(Duration.ofSeconds(1))
                .build();
    }

    @Bean
    OAuth2AuthorizedClientManager authorizedClientManager() {
        var authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
                .clientCredentials()
                .build();

        var authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, oAuth2AuthorizedClientService);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    }

}

拦截器

public class OAuthClientCredentialsRestTemplateInterceptor implements ClientHttpRequestInterceptor {

    private final OAuth2AuthorizedClientManager manager;
    private final Authentication principal;
    private final ClientRegistration clientRegistration;

    public OAuthClientCredentialsRestTemplateInterceptor(OAuth2AuthorizedClientManager manager, ClientRegistration clientRegistration) {
        this.manager = manager;
        this.clientRegistration = clientRegistration;
        this.principal = createPrincipal();
    }

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        OAuth2AuthorizeRequest oAuth2AuthorizeRequest = OAuth2AuthorizeRequest
                .withClientRegistrationId(clientRegistration.getRegistrationId())
                .principal(principal)
                .build();
        OAuth2AuthorizedClient client = manager.authorize(oAuth2AuthorizeRequest);
        if (isNull(client)) {
            throw new IllegalStateException("client credentials flow on " + clientRegistration.getRegistrationId() + " failed, client is null");
        }

        request.getHeaders().add(HttpHeaders.AUTHORIZATION, BEARER_PREFIX + client.getAccessToken().getTokenValue());
        return execution.execute(request, body);
    }

    private Authentication createPrincipal() {
        return new Authentication() {
            @Override
            public Collection<? extends GrantedAuthority> getAuthorities() {
                return Collections.emptySet();
            }

            @Override
            public Object getCredentials() {
                return null;
            }

            @Override
            public Object getDetails() {
                return null;
            }

            @Override
            public Object getPrincipal() {
                return this;
            }

            @Override
            public boolean isAuthenticated() {
                return false;
            }

            @Override
            public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
            }

            @Override
            public String getName() {
                return clientRegistration.getClientId();
            }
        };
    }
}

这将在第一次调用时以及令牌过期时生成 access_token。 OAuth2AuthorizedClientManager 将为您管理这一切

原文由 Leandro Assis 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题