使用OAuth2资源所有者密码授予类型在Spring Cloud Gateway中创建路由 [英] Create route in Spring Cloud Gateway with OAuth2 Resource Owner Password grant type

查看:295
本文介绍了使用OAuth2资源所有者密码授予类型在Spring Cloud Gateway中创建路由的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

How to configure a route in Spring Cloud Gateway to use an OAuth2 client with authorization-grant-type: password? In other words, how to add the Authorization header with the token in the requests to an API? Because I'm integrating with a legacy application, I must use the grant type password.

我有这个应用程序:

@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();
    }
}

<token>替换为实际的令牌,一切正常.

Replacing the <token> with an actual token, everything just works fine.

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

I found this project that does something similar: https://github.com/jgrandja/spring-security-oauth-5-2-migrate. It has a client (messaging-client-password) that is used to configure the WebClient to add OAuth2 support to make requests (i.e. by adding the Authorization header).

我们无法立即使用此示例项目,因为Spring Cloud Gateway是反应性的,并且我们配置事物的方式发生了巨大变化.我认为解决这个问题主要是关于转换

We can't use this sample project right away because Spring Cloud Gateway is reactive and the way we configure things changes significantly. I think to solve this problem is mostly about converting the WebClientConfig class.

更新

我有点使它起作用,但是它的形状非常糟糕.

I kinda make it work, but it is in very bad shape.

首先,我发现了如何将WebClientConfig转换为反应性的:

First, I found how to convert WebClientConfig to be reactive:

@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客户端:

With this configuration, we can use the WebClient to make a request. This somehow initializes the OAuth2 client after calling the endpoint:

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

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

Then, by calling this one we are able to get the reference to the authorized client:

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

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

And finally, by configuring a global filter, we can modify the request to include the Authorization header:

@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中使用密码授予.

After running this three requests in order, we can use the password grant with Spring Cloud Gateway.

当然,此过程非常混乱.仍然需要做的事情:

Of course, this process is very messy. What still needs to be done:

  1. 获取过滤器中授权客户的参考
  2. 使用contextAttributesMapper
  3. 使用凭据初始化授权的客户端
  4. 将所有这些内容写在过滤器中,而不是全局过滤器中.
  1. Get the reference for the authorized client inside the filter
  2. Initialize the authorized client with the credentials using contextAttributesMapper
  3. Write all of this in a filter, not in a global filter. TokenRelayGatewayFilterFactory implementation can provide a good help to do this.

推荐答案

我使用WebClientHttpRoutingFilter实现了authorization-grant-type:password.

I implemented authorization-grant-type: password using WebClientHttpRoutingFilter.

默认情况下,Spring云网关使用Netty路由过滤器,但有一种替代方案不需要Netty(

By default, spring cloud gateway use Netty Routing Filter but there is an alternative that not requires Netty (https://cloud.spring.io/spring-cloud-gateway/reference/html/#the-netty-routing-filter)

WebClientHttpRoutingFilter使用WebClient路由请求.

WebClient可以通过ExchangeFilterFunction(

The WebClient can be configured with a ReactiveOAuth2AuthorizedClientManager through of an ExchangeFilterFunction (https://docs.spring.io/spring-security/site/docs/current/reference/html5/#webclient). The ReactiveOAuth2AuthorizedClientManager will be responsible of management the access/refresh tokens and will do all the hard work for you

此处,您可以查看此实现.另外,我通过这种方法实现了客户凭证授予

Here you can review this implementation. In addition, I implemented the client-credentials grant with this approach

这篇关于使用OAuth2资源所有者密码授予类型在Spring Cloud Gateway中创建路由的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆