【问题标题】:How to set the access token once during the instanciation of the webClient in spring webflux?如何在spring webflux中的webClient实例化期间设置一次访问令牌?
【发布时间】:2020-06-15 08:04:02
【问题描述】:

我尝试在 spring webflux 中使用带有 oauth2 的 WebClient。我从 url 访问令牌中获取令牌并将其设置到 webclient 中。但我不喜欢在每次调用其他安全端点时获取此访问令牌。意味着我只想在 webclient 实例化和访问令牌过期时第一次获取它。

这是我正在使用的代码:

@Configuration
public class OauthEmployeConfig{

    /**
    ** ... String baseUrl, String accessUrl for the access token url
    **/

    @Bean
    public WebClient webClient(UserRegistration userRegistr) {

        ClientRequest clientRequest = ClientRequest
            .create(HttpMethod.POST, URI.create(accessUrl))
            .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
            .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
            .headers(headers -> headers.setBasicAuth(userRegistr.getClientId(), userRegistr.getClientSecret()))
            .body(BodyInserters.fromFormData("grant_type", userRegistr.getAuthorizGrantType())
                .with("scope", userRegistr.getScope().replaceAll(",", "")))
            .build();

        return WebClient.builder()
            .baseUrl(baseUrl)
            .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
            .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
            .filter((request, next) -> next.exchange(clientRequest)
                .flatMap(response -> response.body(org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors.oauth2AccessTokenResponse()))
                .map(accessToken -> accessToken.getAccessToken().getTokenValue())
                .map(token -> setBearer(request, token))
                .flatMap(next::exchange))
            .filter(logRequest())
            .filter(handleResponseError())
            .build();
    }

    private ClientRequest setBearer(ClientRequest request, String token) {
    return ClientRequest.from(request)
        .header("Authorization", "Bearer " + token).build();
    }


    private static ExchangeFilterFunction handleResponseError() {
        return ExchangeFilterFunction.ofResponseProcessor(
            response -> response.statusCode().isError()
                ? response.bodyToMono(String.class)
                    .flatMap(errorBody -> Mono.error(new RuntimeException(errorBody, response.statusCode())))
                : Mono.just(response));
    }

     private static ExchangeFilterFunction logRequest() {
        return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
          clientRequest.headers().forEach((name, values) -> values.forEach(value -> LOG.info("{}={}", name, value)));
          return Mono.just(clientRequest);
        });
    }
}

【问题讨论】:

  • 使用包装类会起作用。将 WebClient bean 自动装配到该包装类中。为 GET、POST 等编写包装器方法。为了存储 accessToken,请在包装器类中使用字段(变量)。
  • @Nipuna 你能不能通过一个简单的例子来说明你的

标签: spring-boot spring-security spring-security-oauth2 spring-webflux spring-webclient


【解决方案1】:

我关注了this toturialspring doc,我不得不更改我的代码。

所以我的代码看起来像:

application.properties

spring.security.oauth2.client.registration.chris.authorization-grant-type=client_credentials
spring.security.oauth2.client.registration.chris.client-id=chris-client-id
spring.security.oauth2.client.registration.chris.client-secret=chris-secret

spring.security.oauth2.client.provider.chris.token-uri=http://localhost:8085/oauth/token

配置类:

@Configuration
   public OauthEmployeConfig {

    @Bean
    WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations) {
     ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
      new ServerOAuth2AuthorizedClientExchangeFilterFunction(
       clientRegistrations,
       new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
     oauth.setDefaultClientRegistrationId("chris");
     oauth.setDefaultOAuth2AuthorizedClient(true);
     return WebClient.builder()
      .filter(oauth)
      .filter(logRequest())
      .filter(handleResponseError())
      .build();
    }

    private static ExchangeFilterFunction handleResponseError() {
     return ExchangeFilterFunction.ofResponseProcessor(
      response -> response.statusCode().isError() ?
      response.bodyToMono(String.class)
      .flatMap(errorBody -> Mono.error(new RunTimeException(errorBody, response.statusCode()))) :
      Mono.just(response));
    }

    private static ExchangeFilterFunction logRequest() {
     return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
         // To log the headers details like Token ...
      clientRequest.headers().forEach((name, values) -> values.forEach(value -> LOGGER.info("{}={}", name, value)));
      return Mono.just(clientRequest);
     });
    }
   }

通过 webClient 调用休息:

...
webClient.get()
  .uri("http://localhost:8084/retrieve-resource")
  .retrieve()
...

这种方法当然会在访问令牌过期后通过刷新令牌更新访问令牌。

【讨论】:

  • 抱歉挖掘了一个旧线程。是否可以以编程方式使用这种方法,因为我根本没有 application.properties 文件。无论如何,秘密也以编程方式出现。而且我需要为每个请求使用不同的秘密,因为我是一个中继 API。
【解决方案2】:

请参考:https://stackoverflow.com/a/66383617/7927181 因为类 UnAuthenticatedServerOAuth2AuthorizedClientRepository 已弃用。

【讨论】:

    猜你喜欢
    • 2019-04-13
    • 2018-08-06
    • 2018-02-24
    • 2022-09-23
    • 1970-01-01
    • 1970-01-01
    • 2016-01-04
    • 2019-08-22
    • 2019-11-23
    相关资源
    最近更新 更多