Spring WebClient and OAuth2 Support – Spring WebClient和OAuth2支持

最后修改: 2019年 1月 13日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Spring Security 5 provides OAuth2 support for Spring Webflux’s non-blocking WebClient class.

Spring Security 5为Spring Webflux的非阻塞式WebClient类提供OAuth2支持。

In this tutorial, we’ll analyze the different approaches to accessing secured resources using this class. We’ll also look under the hood to understand how Spring handles the OAuth2 authorization process.

在本教程中,我们将分析使用该类访问安全资源的不同方法。我们还将深入了解Spring如何处理OAuth2的授权过程。

2. Setting up the Scenario

2.设置场景

In line with the OAuth2 specification, apart from our Client, which is the focus subject of this tutorial, we naturally need an Authorization Server and Resource Server. 

根据OAuth2规范,除了本教程的重点主题–我们的客户端之外,我们自然需要一个授权服务器和资源服务器。

We can use well-known authorization providers, like Google or Github. To better understand the role of the OAuth2 Client, we can also use our own servers, with an implementation available here. We won’t go over the full configuration, since it’s not the topic of this tutorial, so it’s enough knowing that:

我们可以使用知名的授权提供商,如Google或Github。为了更好地理解OAuth2客户端的作用,我们也可以使用自己的服务器,这里有一个实现。我们不会去看完整的配置,因为这不是本教程的主题,所以知道这些就足够了。

  • the Authorization Server will be:
    • running on port 8081
    • exposing the /oauth/authorize, /oauth/token and oauth/check_token endpoints to carry out the desired functionality
    • configured with sample users (e.g. john/123) and a single OAuth client (fooClientIdPassword/secret)
  • the Resource Server will be separated from the Authentication Server and will be:
    • running on port 8082
    • serving a simple Foo object secured resource accessible using the /foos/{id} endpoint

Note: it’s important to understand that several Spring projects are offering different OAuth-related features and implementations. We can see what each library provides in this Spring Projects matrix.

注意:需要了解的是,几个Spring项目正在提供不同的OAuth相关功能和实现。我们可以在这个Spring项目矩阵中看到每个库提供的内容。

The WebClient and all the reactive Webflux related functionality is part of the Spring Security 5 project. Therefore, we’ll mainly be using this framework throughout this tutorial.

WebClient和所有反应式Webflux相关的功能是Spring Security 5项目的一部分。因此,我们将在本教程中主要使用这个框架。

3. Spring Security 5 Under the Hood

3.Spring的安全 5 在引擎盖下

In order to fully understand the examples we’ll be discussing, it’s good to know how Spring Security manages the OAuth2 features internally.

为了充分理解我们将要讨论的例子,了解Spring Security如何在内部管理OAuth2功能是很好的。

This framework offers the capabilities to:

这个框架提供了以下能力。

  • rely on an OAuth2 provider account to login users into the application
  • configure our service as an OAuth2 Client
  • manage the authorization procedures for us
  • refresh tokens automatically
  • store the credentials if necessary

Some of the fundamental concepts of the Spring Security’s OAuth2 world are described in the following diagram:

Spring Security的OAuth2世界的一些基本概念在下图中描述。

websecurity webclient oauth2

3.1. Providers

3.1. 提供者

Spring defines the OAuth2 Provider role responsible for exposing OAuth 2.0 protected resources.

Spring定义了负责暴露OAuth 2.0保护资源的OAuth2 Provider角色。

In our example, our Authentication Service will be the one offering the Provider capabilities.

在我们的例子中,我们的认证服务将是提供提供者能力的一个。

3.2. Client Registrations

3.2.客户注册

ClientRegistration is an entity containing all the relevant information of a specific client registered in an OAuth2 (or an OpenID) provider.

一个ClientRegistration是一个实体,包含了在OAuth2(或OpenID)提供者中注册的特定客户的所有相关信息。

In our scenario, it’ll be the client registered in the Authentication Server, identified by the bael-client-id id.

在我们的方案中,它将是在认证服务器中注册的客户,由bael-client-id id识别。

3.3. Authorized Clients

3.3.授权的客户

Once the end-user (aka the Resource Owner) grants permissions to the client to access its resources, an OAuth2AuthorizedClient entity is created.

一旦终端用户(又称资源所有者)授予客户端访问其资源的权限,就会创建一个OAuth2AuthorizedClient实体。

It’ll be responsible for associating access tokens to client registrations and resource owners (represented by Principal objects).

它将负责将访问令牌与客户注册和资源所有者(由Principal对象代表)联系起来。

3.4. Repositories

3.4.存储库

Furthermore, Spring Security also offers repository classes to access the entities mentioned above.

此外,Spring Security还提供存储库类来访问上述实体。

Particularly, the ReactiveClientRegistrationRepository and the ServerOAuth2AuthorizedClientRepository classes are used in reactive stacks, and they use the in-memory storage by default.

特别是,ReactiveClientRegistrationRepositoryServerOAuth2AuthorizedClientRepository类被用于反应式堆栈中,它们默认使用内存存储。

Spring Boot 2.x creates beans of these repository classes and adds them automatically to the context.

Spring Boot 2.x创建了这些资源库类的Bean,并将其自动添加到上下文中。

3.5. Security Web Filter Chain

3.5.安全网络过滤器链

One of the key concepts in Spring Security 5 is the reactive SecurityWebFilterChain entity.

Spring Security 5的一个关键概念是反应式的SecurityWebFilterChain实体。

As its name indicates, it represents a chained collection of WebFilter objects.

正如其名称所示,它代表了一个WebFilter对象的链式集合。

When we enable the OAuth2 features in our application, Spring Security adds two filters to the chain:

当我们在应用程序中启用OAuth2功能时,Spring Security会在链上添加两个过滤器。

  1. One filter responds to authorization requests (the /oauth2/authorization/{registrationId} URI) or throws a ClientAuthorizationRequiredException. It contains a reference to the ReactiveClientRegistrationRepository, and it’s in charge of creating the authorization request to redirect the user-agent.
  2. The second filter differs depending on which feature we’re adding (OAuth2 Client capabilities or the OAuth2 Login functionality). In both cases, the main responsibility of this filter is to create the OAuth2AuthorizedClient instance and store it using the ServerOAuth2AuthorizedClientRepository.

3.6. Web Client

3.6 网络客户端

The web client will be configured with an ExchangeFilterFunction containing references to the repositories.

网络客户端将被配置一个ExchangeFilterFunction,其中包含对资源库的引用。

It’ll use them to obtain the access token to add it automatically to the request.

它将使用它们来获取访问令牌,以便将其自动添加到请求中。

4. Spring Security 5 Support – the Client Credentials Flow

4.Spring Security 5支持 – 客户端证书流程

Spring Security allows us to configure our application as an OAuth2 Client.

Spring Security允许我们将我们的应用程序配置为OAuth2客户端。

In this article, we’ll use a WebClient instance to retrieve resources using the ‘Client Credentials’ grant type, and then using the ‘Authorization Code’ flow.

在本文中,我们将使用一个WebClient 实例,使用 “客户凭证”授予类型检索资源,然后使用 “授权代码 “流程。

The first thing we’ll have to do is configure the client registration and the provider that we’ll use to obtain the access token.

我们要做的第一件事是配置客户注册和我们用来获取访问令牌的提供者。

4.1. Client and Provider Configurations

4.1.客户端和供应商配置

As we saw in the OAuth2 Login article, we can either configure it programmatically, or rely on the Spring Boot auto-configuration by using properties to define our registration:

正如我们在OAuth2登录文章中所看到的那样,我们可以通过编程来配置它,或者通过使用属性来定义我们的注册来依赖Spring Boot的自动配置。

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

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

These are all the configurations we need to retrieve the resource using the client_credentials flow.

这些是我们使用client_credentials流程检索资源所需的所有配置。

4.2. Using the WebClient

4.2.使用WebClient

We use this grant type in machine-to-machine communications where there’s no end-user interacting with our application.

我们在机器对机器的通信中使用这种授予类型,在这种通信中没有终端用户与我们的应用程序进行互动。

For example, let’s imagine we have a cron job trying to obtain a secured resource using a WebClient in our application:

例如,让我们想象一下,我们有一个cron作业,试图使用我们应用程序中的WebClient获得一个安全资源。

@Autowired
private WebClient webClient;

@Scheduled(fixedRate = 5000)
public void logResourceServiceResponse() {

    webClient.get()
      .uri("http://localhost:8084/retrieve-resource")
      .retrieve()
      .bodyToMono(String.class)
      .map(string 
        -> "Retrieved using Client Credentials Grant Type: " + string)
      .subscribe(logger::info);
}

4.3. Configuring the WebClient

4.3.配置WebClient

Next, we’ll set the webClient instance that we autowired in our scheduled task:

接下来,我们将设置我们在计划任务中自动连接的webClient实例。

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

As we previously mentioned, the client registration repository is automatically created and added to the context by Spring Boot.

正如我们之前提到的,客户注册库是由Spring Boot自动创建并添加到上下文中的。

The next thing to note here is that we’re using an UnAuthenticatedServerOAuth2AuthorizedClientRepository instance. This is because no end-user will take part in the process, since it’s a machine-to-machine communication. Finally, like we stated, we’ll be using the bael client registration by default.

接下来要注意的是,我们正在使用一个UnAuthenticatedServerOAuth2AuthorizedClientRepository实例。这是因为没有终端用户会参与这个过程,因为这是一个机器对机器的通信。最后,像我们所说的,我们将默认使用bael客户端注册。

Otherwise, we have to specify it by the time we define the request in the cron job:

否则,我们必须在cron job中定义请求时指定它。

webClient.get()
  .uri("http://localhost:8084/retrieve-resource")
  .attributes(
    ServerOAuth2AuthorizedClientExchangeFilterFunction
      .clientRegistrationId("bael"))
  .retrieve()
  // ...

4.4. Testing

4.4.测试

If we run our application with the DEBUG logging level enabled, we’ll be able to see the calls that Spring Security is doing for us:

如果我们在运行应用程序时启用DEBUG日志级别,我们就能看到Spring Security为我们进行的调用。

o.s.w.r.f.client.ExchangeFunctions:
  HTTP POST http://localhost:8085/oauth/token
o.s.http.codec.json.Jackson2JsonDecoder:
  Decoded [{access_token=89cf72cd-183e-48a8-9d08-661584db4310,
    token_type=bearer,
    expires_in=41196,
    scope=read
    (truncated)...]
o.s.w.r.f.client.ExchangeFunctions:
  HTTP GET http://localhost:8084/retrieve-resource
o.s.core.codec.StringDecoder:
  Decoded "This is the resource!"
c.b.w.c.service.WebClientChonJob:
  We retrieved the following resource using Client Credentials Grant Type: This is the resource!

We’ll also notice that the second time the task runs, the application requests the resource without asking for a token first, since the last one hasn’t expired.

我们也会注意到,在任务第二次运行时,应用程序请求资源时没有先询问令牌,因为上次的令牌还没有过期。

5. Spring Security 5 Support – Implementation Using the Authorization Code Flow

5.Spring Security 5支持 – 使用授权代码流实现

This grant type is usually used in cases where less-trusted third-party applications need to access resources.

这种授予类型通常用于信任度较低的第三方应用程序需要访问资源的情况。

5.1. Client and Provider Configurations

5.1.客户端和供应商配置

In order to execute the OAuth2 process using the Authorization Code flow, we’ll need to define several more properties for our client registration and the provider:

为了使用授权代码流程执行OAuth2流程,我们还需要为我们的客户注册和提供者定义几个属性。

spring.security.oauth2.client.registration.bael.client-name=bael
spring.security.oauth2.client.registration.bael.client-id=bael-client-id
spring.security.oauth2.client.registration.bael.client-secret=bael-secret
spring.security.oauth2.client.registration.bael
  .authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.bael
  .redirect-uri=http://localhost:8080/login/oauth2/code/bael

spring.security.oauth2.client.provider.bael.token-uri=http://localhost:8085/oauth/token
spring.security.oauth2.client.provider.bael
  .authorization-uri=http://localhost:8085/oauth/authorize
spring.security.oauth2.client.provider.bael.user-info-uri=http://localhost:8084/user
spring.security.oauth2.client.provider.bael.user-name-attribute=name

Apart from the properties we used in the previous section, this time we also need to include:

除了我们在上一节中使用的属性外,这次我们还需要包括。

  • an endpoint to authenticate on the Authentication Server
  • the URL of an endpoint containing user information
  • the URL of an endpoint in our application to which the user-agent will be redirected after authenticating

Of course, for well-known providers, the first two points don’t need to be specified.

当然,对于知名的供应商来说,前两点不需要具体说明。

The redirect endpoint is created automatically by Spring Security.

重定向端点是由Spring Security自动创建的。

By default, the URL configured for it is /[action]/oauth2/code/[registrationId], with only authorize and login actions permitted (in order to avoid an infinite loop).

默认情况下,为它配置的URL是/[action]/oauth2/code/[registrationId],只允许authorizelogin动作(以避免无限循环)。

This endpoint is in charge of:

这个端点负责的是。

  • receiving the authentication code as a query param
  • using it to obtain an access token
  • creating the Authorized Client instance
  • redirecting the user-agent back to the original endpoint

5.2. HTTP Security Configurations

5.2.HTTP安全配置

Next, we’ll need to configure the SecurityWebFilterChain.

接下来,我们将需要配置SecurityWebFilterChain.

The most common scenario is using Spring Security’s OAuth2 Login capabilities to authenticate users and give them access to our endpoints and resources.

最常见的情况是使用Spring Security的OAuth2登录功能来验证用户,让他们访问我们的端点和资源。

If that’s our case, then just including the oauth2Login directive in the ServerHttpSecurity definition will be enough for our application to work as an OAuth2 Client too:

如果这就是我们的情况,那么只需在ServerHttpSecurity定义中包括oauth2Login指令,就足以使我们的应用程序也能作为OAuth2客户端工作:

@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
    http.authorizeExchange()
      .anyExchange()
      .authenticated()
      .and()
      .oauth2Login();
    return http.build();
}

5.3. Configuring the WebClient

5.3.配置WebClient

Now it’s time to put our WebClient instance in place:

现在是时候把我们的WebClient实例放到位了。

@Bean
WebClient webClient(
  ReactiveClientRegistrationRepository clientRegistrations,
  ServerOAuth2AuthorizedClientRepository authorizedClients) {
    ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
      new ServerOAuth2AuthorizedClientExchangeFilterFunction(
        clientRegistrations,
        authorizedClients);
    oauth.setDefaultOAuth2AuthorizedClient(true);
    return WebClient.builder()
      .filter(oauth)
      .build();
}

This time we’re injecting both the client registration repository and the authorized client repository from the context.

这次我们要从上下文中注入客户注册资源库和授权客户资源库。

We’re also enabling the setDefaultOAuth2AuthorizedClient option. With this, the framework will try to obtain the client information from the current Authentication object managed in Spring Security.

我们还启用了setDefaultOAuth2AuthorizedClient选项。有了这个选项,框架将尝试从Spring Security管理的当前Authentication对象中获取客户端信息。

We have to take into account that with it, all HTTP requests will include the access token, which might not be the desired behavior.

我们必须考虑到,有了它,所有的HTTP请求将包括访问令牌,这可能不是我们想要的行为。

Later we’ll analyze alternatives that will indicate the client that a specific WebClient transaction will use.

稍后我们将分析替代方案,这些替代方案将指出特定的WebClient事务将使用的客户端。

5.4. Using the WebClient

5.4.使用WebClient

The Authorization Code requires a user-agent that can work out redirections (e.g., a browser) to execute the procedure.

授权代码需要一个能够解决重定向问题的用户代理(例如,浏览器)来执行程序。

Therefore, we can make use of this grant type when the user is interacting with our application, usually calling an HTTP endpoint:

因此,当用户与我们的应用程序进行交互时,我们可以利用这种授予类型,通常是调用一个HTTP端点。

@RestController
public class ClientRestController {

    @Autowired
    WebClient webClient;

    @GetMapping("/auth-code")
    Mono<String> useOauthWithAuthCode() {
        Mono<String> retrievedResource = webClient.get()
          .uri("http://localhost:8084/retrieve-resource")
          .retrieve()
          .bodyToMono(String.class);
        return retrievedResource.map(string ->
          "We retrieved the following resource using Oauth: " + string);
    }
}

5.5. Testing

5.5.测试

Finally, we’ll call the endpoint and analyze what’s going on by checking the log entries.

最后,我们将调用端点,并通过检查日志条目来分析正在发生的事情。

After we call the endpoint, the application verifies that we’re not yet authenticated in the application:

在我们调用端点后,应用程序会验证我们在应用程序中还没有得到认证。

o.s.w.s.adapter.HttpWebHandlerAdapter: HTTP GET "/auth-code"
...
HTTP/1.1 302 Found
Location: /oauth2/authorization/bael

The application redirects to the Authorization Service’s endpoint to authenticate using the credentials existing in the Provider’s registries (in our case, we’ll use the bael-user/bael-password):

应用程序重定向到授权服务的端点,使用提供者注册表中现有的凭证进行认证(在我们的例子中,我们将使用bael-user/bael-password)。

HTTP/1.1 302 Found
Location: http://localhost:8085/oauth/authorize
  ?response_type=code
  &client_id=bael-client-id
  &state=...
  &redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Flogin%2Foauth2%2Fcode%2Fbael

After authenticating, the user-agent is sent back to the Redirect URI, along with the code as a query param, and the state value that was first sent (to avoid CSRF attacks):

认证后,用户代理被送回重定向URI,同时将代码作为查询参数,以及第一次发送的状态值(以避免CSRF攻击)。

o.s.w.s.adapter.HttpWebHandlerAdapter:HTTP GET "/login/oauth2/code/bael?code=...&state=...

The application then uses the code to obtain an access token:

然后,应用程序使用该代码获得一个访问令牌。

o.s.w.r.f.client.ExchangeFunctions:HTTP POST http://localhost:8085/oauth/token

It obtains users information:

它获得了用户信息。

o.s.w.r.f.client.ExchangeFunctions:HTTP GET http://localhost:8084/user

And it redirects the user-agent to the original endpoint:

并将用户代理重定向到原始端点。

HTTP/1.1 302 Found
Location: /auth-code

Finally, our WebClient instance can request the secured resource successfully:

最后,我们的WebClient实例可以成功请求安全资源。

o.s.w.r.f.client.ExchangeFunctions:HTTP GET http://localhost:8084/retrieve-resource
o.s.w.r.f.client.ExchangeFunctions:Response 200 OK
o.s.core.codec.StringDecoder :Decoded "This is the resource!"

6. An Alternative – Client Registration in the Call

6.一个替代方案–客户在电话中注册

Earlier, we learned that using the setDefaultOAuth2AuthorizedClient implies that the application will include the access token in any call we realize with the client.

早些时候,我们了解到,使用setDefaultOAuth2AuthorizedClient意味着应用程序将在我们与客户端实现的任何调用中包含访问令牌。

If we remove this command from the configuration, we’ll need to specify the client registration explicitly by the time we define the request.

如果我们从配置中删除这个命令,我们就需要在定义请求时明确指定客户端的注册。

One way, of course, is by using the clientRegistrationId, as we did before when working in the client credentials flow.

当然,一种方法是使用clientRegistrationId,就像我们之前在客户端凭证流程中工作时一样。

Since we associated the Principal with authorized clients, we can obtain the OAuth2AuthorizedClient instance using the @RegisteredOAuth2AuthorizedClient annotation:

由于我们将Principal与授权客户端相关联,我们可以使用@RegisteredOAuth2AuthorizedClientannotation获得OAuth2AuthorizedClientinstance:

@GetMapping("/auth-code-annotated")
Mono<String> useOauthWithAuthCodeAndAnnotation(
  @RegisteredOAuth2AuthorizedClient("bael") OAuth2AuthorizedClient authorizedClient) {
    Mono<String> retrievedResource = webClient.get()
      .uri("http://localhost:8084/retrieve-resource")
      .attributes(
        ServerOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient(authorizedClient))
      .retrieve()
      .bodyToMono(String.class);
    return retrievedResource.map(string -> 
      "Resource: " + string 
        + " - Principal associated: " + authorizedClient.getPrincipalName() 
        + " - Token will expire at: " + authorizedClient.getAccessToken()
          .getExpiresAt());
}

7. Avoiding the OAuth2 Login Features

7.避开OAuth2的登录功能

As we noted, the most common scenario is relying on the OAuth2 authorization provider to login users in our application.

正如我们所指出的,最常见的情况是依靠OAuth2授权提供者来登录我们应用程序中的用户。

But what if we want to avoid this, yet still be able to access secured resources using the OAuth2 protocol? Then we’ll need to make some changes in our configuration.

但是,如果我们想避免这种情况,但仍然能够使用OAuth2协议访问安全的资源呢?那么我们就需要在我们的配置中做一些改变。

For starters, and just to be clear across the board, we can use the authorize action instead of the login one when defining the redirect URI property:

首先,为了全面了解情况,在定义重定向URI属性时,我们可以使用authorize action而不是login one。

spring.security.oauth2.client.registration.bael
  .redirect-uri=http://localhost:8080/login/oauth2/code/bael

We can also drop the user-related properties, since we won’t be using them to create the Principal in our application.

我们也可以放弃与用户相关的属性,因为我们不会用它们来创建我们应用程序中的Principal

Now we’ll configure the SecurityWebFilterChain without including the oauth2Login command, and instead we’ll include the oauth2Client one.

现在我们将配置SecurityWebFilterChain,不包括oauth2Login命令,而是包括oauth2Client

Even though we don’t want to rely on the OAuth2 Login, we still want to authenticate users before accessing our endpoint. For this reason, we’ll also include the formLogin directive here:

即使我们不想依赖OAuth2登录,我们仍然希望在访问我们的端点之前对用户进行认证。出于这个原因,我们也将在这里包括formLogin指令。

@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
    http.authorizeExchange()
      .anyExchange()
      .authenticated()
      .and()
      .oauth2Client()
      .and()
      .formLogin();
    return http.build();
}

Now let’s run the application, and check out what happens when we use the /auth-code-annotated endpoint.

现在让我们运行应用程序,并检查一下当我们使用/auth-code-annotated 端点时会发生什么。

We’ll first have to log in to our application using the form login.

我们首先要用表单登录到我们的应用程序。

Then the application will redirect us to the Authorization Service login to grant access to our resources.

然后,应用程序将把我们重定向到授权服务登录,以授予对我们资源的访问权。

Note: after doing this, we should be redirected back to the original endpoint that we called. However, Spring Security seems to be redirecting back to the root path “/” instead, which appears to be a bug. The following requests after the one triggering the OAuth2 dance will run successfully.

注意:这样做之后,我们应该被重定向到我们调用的原始端点。然而,Spring Security似乎会重定向到根路径”/”,这似乎是一个错误。在触发OAuth2舞蹈的请求之后,以下请求将成功运行。

We can see in the endpoint response that the authorized client this time is associated with a principal named bael-client-id instead of the bael-user, named after the user configured in the Authentication Service.

我们可以在端点响应中看到,这次的授权客户端与一个名为bael-client-id的委托人相关联,而不是bael-user,以认证服务中配置的用户命名。

8. Spring Framework Support – Manual Approach

8.支持Spring框架–手工方法

Out of the box, Spring 5 provides just one OAuth2-related service method to add a Bearer token header to the request easily. It’s the HttpHeaders#setBearerAuth method.

开箱即用,Spring 5只提供了一个与OAuth2相关的服务方法,可以轻松地在请求中添加一个Bearer token头。这就是HttpHeaders#setBearerAuth 方法。

We’ll now look at an example to demonstrate what it would take to obtain our secured resource by performing an OAuth2 dance manually.

我们现在来看一个例子,以证明通过手动执行OAuth2舞蹈来获得我们的安全资源。

Simply put, we’ll need to chain two HTTP requests, one to get an authentication token from the Authorization Server, and the other to obtain the resource using this token:

简单地说,我们需要连锁两个HTTP请求,一个是从授权服务器获取认证令牌,另一个是使用这个令牌获取资源。

@Autowired
WebClient client;

public Mono<String> obtainSecuredResource() {
    String encodedClientData = 
      Base64Utils.encodeToString("bael-client-id:bael-secret".getBytes());
    Mono<String> resource = client.post()
      .uri("localhost:8085/oauth/token")
      .header("Authorization", "Basic " + encodedClientData)
      .body(BodyInserters.fromFormData("grant_type", "client_credentials"))
      .retrieve()
      .bodyToMono(JsonNode.class)
      .flatMap(tokenResponse -> {
          String accessTokenValue = tokenResponse.get("access_token")
            .textValue();
          return client.get()
            .uri("localhost:8084/retrieve-resource")
            .headers(h -> h.setBearerAuth(accessTokenValue))
            .retrieve()
            .bodyToMono(String.class);
        });
    return resource.map(res ->
      "Retrieved the resource using a manual approach: " + res);
}

This example should help us understand how cumbersome it can be to leverage a request following the OAuth2 specification, and show us how the setBearerAuth method is used.

这个例子应该可以帮助我们理解,按照OAuth2规范来利用一个请求是多么的麻烦,并向我们展示了setBearerAuth方法的使用。

In a real-life scenario, we’d let Spring Security take care of all the hard work for us in a transparent manner, as we did in previous sections.

在现实生活中,我们会让Spring Security以透明的方式为我们处理所有的艰苦工作,正如我们在前几节所做的那样。

9. Conclusion

9.结语

In this article, we learned how to set up our application as an OAuth2 Client, and more specifically, how we can configure and use the WebClient to retrieve a secured resource in a full-reactive stack.

在这篇文章中,我们学习了如何将我们的应用程序设置为OAuth2客户端,更具体地说,我们如何配置和使用WebClient来检索全反应堆中的安全资源。

Then we analyzed how Spring Security 5 OAuth2 mechanisms operate under the hood to comply with the OAuth2 specification.

然后我们分析了Spring Security 5 OAuth2机制是如何在引擎盖下运行以符合OAuth2规范的。

As always, the full example is available over on Github.

一如既往,完整的示例可在Github上获得。