首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >使用OAuth2资源所有者密码授予类型在Spring网关中创建路由

使用OAuth2资源所有者密码授予类型在Spring网关中创建路由
EN

Stack Overflow用户
提问于 2020-01-08 09:46:01
回答 1查看 2K关注 0票数 3

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

我有这样的申请:

代码语言:javascript
复制
@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>,一切都很好。

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

我们不能马上使用这个示例项目,因为Spring是反应性的,我们配置东西的方式发生了很大的变化。我认为解决这个问题主要是关于转换WebClientConfig类。

更新

我让它起作用了,但它的状态很差。

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

代码语言:javascript
复制
@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客户端:

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

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

代码语言:javascript
复制
private OAuth2AuthorizedClient authorizedClient;
@GetMapping("/token")
public String token(@RegisteredOAuth2AuthorizedClient("messaging-client-password") OAuth2AuthorizedClient authorizedClient) {
    this.authorizedClient = authorizedClient;
    return authorizedClient.getAccessToken().getTokenValue();
}

最后,通过配置全局筛选器,我们可以修改请求以包含授权头:

代码语言:javascript
复制
@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);
    };
}

在按照顺序运行这三个请求之后,我们可以在中使用密码授予。

当然,这个过程是非常混乱的。仍然需要做的是:

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

回答 1

Stack Overflow用户

发布于 2020-05-31 03:33:41

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

默认情况下,spring云网关使用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)的一个ReactiveOAuth2AuthorizedClientManager来配置。ReactiveOAuth2AuthorizedClientManager将负责管理访问/刷新令牌,并为您完成所有的艰苦工作。

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

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/59643201

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档