【问题标题】:Create route in Spring Cloud Gateway with OAuth2 Resource Owner Password grant type使用 OAuth2 资源所有者密码授予类型在 Spring Cloud Gateway 中创建路由
【发布时间】:2020-04-25 19:58:53
【问题描述】:

如何在 Spring Cloud Gateway 中配置路由以使用带有authorization-grant-type: password 的 OAuth2 客户端?换句话说,如何在 API 的请求中添加带有令牌的 Authorization 标头?因为我正在与遗留应用程序集成,所以我必须使用授权类型密码。

我有这个应用程序:

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
           .route("route_path", r -> r.path("/**")
                   .filters(f -> f.addRequestHeader("Authorization", "bearer <token>"))
                   .uri("http://localhost:8092/messages"))
           .build();
    }
}

用实际的令牌替换&lt;token&gt;,一切正常。

我发现这个项目做了类似的事情:https://github.com/jgrandja/spring-security-oauth-5-2-migrate。它有一个客户端 (messaging-client-password),用于配置 WebClient 以添加 OAuth2 支持以发出请求(即通过添加 Authorization 标头)。

我们不能立即使用这个示例项目,因为 Spring Cloud Gateway 是响应式的,并且我们配置事物的方式发生了重大变化。我认为解决这个问题主要是转换WebClientConfig类。

更新

我有点让它工作,但它的状态非常糟糕。

首先,我找到了如何将WebClientConfig 转换为反应式:

@Configuration
public class WebClientConfig {

    @Bean
    WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
        ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
                new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        oauth.setDefaultOAuth2AuthorizedClient(true);
        oauth.setDefaultClientRegistrationId("messaging-client-password");
        return WebClient.builder()
                .filter(oauth)
                .build();
    }

    @Bean
    ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
            ReactiveClientRegistrationRepository clientRegistrationRepository,
            ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {

        ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
                ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
                        .refreshToken()
                        .password()
                        .build();
        DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
                new DefaultReactiveOAuth2AuthorizedClientManager(
                        clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        // For the `password` grant, the `username` and `password` are supplied via request parameters,
        // so map it to `OAuth2AuthorizationContext.getAttributes()`.
        authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());

        return authorizedClientManager;
    }

    private Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttributesMapper() {
        return authorizeRequest -> {
            Map<String, Object> contextAttributes = Collections.emptyMap();
            ServerWebExchange serverWebExchange = authorizeRequest.getAttribute(ServerWebExchange.class.getName());
            String username = serverWebExchange.getRequest().getQueryParams().getFirst(OAuth2ParameterNames.USERNAME);
            String password = serverWebExchange.getRequest().getQueryParams().getFirst(OAuth2ParameterNames.PASSWORD);
            if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
                contextAttributes = new HashMap<>();

                // `PasswordOAuth2AuthorizedClientProvider` requires both attributes
                contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
                contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
            }
            return Mono.just(contextAttributes);
        };
    }
}

通过此配置,我们可以使用WebClient 发出请求。这会在调用端点后以某种方式初始化 OAuth2 客户端:

@GetMapping("/explicit")
public Mono<String[]> explicit() {
    return this.webClient
        .get()
        .uri("http://localhost:8092/messages")
        .attributes(clientRegistrationId("messaging-client-password"))
        .retrieve()
        .bodyToMono(String[].class);
}

然后,通过调用这个,我们可以获得对授权客户端的引用:

private OAuth2AuthorizedClient authorizedClient;
@GetMapping("/token")
public String token(@RegisteredOAuth2AuthorizedClient("messaging-client-password") OAuth2AuthorizedClient authorizedClient) {
    this.authorizedClient = authorizedClient;
    return authorizedClient.getAccessToken().getTokenValue();
}

最后,通过配置全局过滤器,我们可以修改请求以包含 Authorization 标头:

@Bean
public GlobalFilter customGlobalFilter() {
    return (exchange, chain) -> {
        //adds header to proxied request
        exchange.getRequest().mutate().header("Authorization", authorizedClient.getAccessToken().getTokenType().getValue() + " " + authorizedClient.getAccessToken().getTokenValue()).build();
        return chain.filter(exchange);
    };
}

依次运行这三个请求后,我们可以使用 Spring Cloud Gateway 的密码授予。

当然,这个过程非常混乱。还需要做什么:

  1. 在过滤器中获取授权客户端的引用
  2. 使用contextAttributesMapper 使用凭据初始化授权客户端
  3. 将所有这些写入过滤器,而不是全局过滤器。 TokenRelayGatewayFilterFactory 的实现可以提供很好的帮助。

【问题讨论】:

  • 嗨@rigor,你能链接一个例子吗?谢谢

标签: spring spring-boot oauth-2.0 spring-cloud spring-cloud-gateway


【解决方案1】:

我使用WebClientHttpRoutingFilter 实现了授权授权类型:密码。

默认情况下,spring cloud gateway 使用 Netty 路由过滤器,但有一个替代方案不需要 Netty (https://cloud.spring.io/spring-cloud-gateway/reference/html/#the-netty-routing-filter)

WebClientHttpRoutingFilter 使用WebClient 路由请求。

WebClient 可以通过ExchangeFilterFunction (https://docs.spring.io/spring-security/site/docs/current/reference/html5/#webclient) 配置ReactiveOAuth2AuthorizedClientManagerReactiveOAuth2AuthorizedClientManager 将负责管理访问/刷新令牌,并将为您完成所有艰苦的工作

Here 你可以查看这个实现。此外,我使用这种方法实现了客户端凭据授予

【讨论】:

    猜你喜欢
    • 2015-05-20
    • 2014-09-02
    • 2023-04-05
    • 2018-05-25
    • 2015-05-30
    • 2016-12-31
    • 1970-01-01
    • 2017-07-05
    • 2015-04-01
    相关资源
    最近更新 更多