Skip to content

Commit

Permalink
Separate OAuth 2.0 Client Reactive Docs
Browse files Browse the repository at this point in the history
Issue gh-10367
  • Loading branch information
jzheaux committed Nov 5, 2021
1 parent 7708418 commit 4a96374
Show file tree
Hide file tree
Showing 7 changed files with 992 additions and 1,015 deletions.
6 changes: 5 additions & 1 deletion docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,11 @@
*** xref:reactive/authorization/method.adoc[EnableReactiveMethodSecurity]
** xref:reactive/oauth2/index.adoc[OAuth2]
*** xref:reactive/oauth2/login.adoc[OAuth2 Log In]
*** xref:reactive/oauth2/oauth2-client.adoc[OAuth2 Client]
*** xref:reactive/oauth2/client/index.adoc[OAuth2 Client]
**** xref:reactive/oauth2/client/core.adoc[Core Interfaces and Classes]
**** xref:reactive/oauth2/client/authorization-grants.adoc[OAuth2 Authorization Grants]
**** xref:reactive/oauth2/client/client-authentication.adoc[OAuth2 Client Authentication]
**** xref:reactive/oauth2/client/authorized-clients.adoc[OAuth2 Authorized Clients]
*** xref:reactive/oauth2/resource-server/index.adoc[OAuth2 Resource Server]
**** xref:reactive/oauth2/resource-server/jwt.adoc[JWT]
**** xref:reactive/oauth2/resource-server/opaque-token.adoc[Opaque Token]
Expand Down

Large diffs are not rendered by default.

250 changes: 250 additions & 0 deletions docs/modules/ROOT/pages/reactive/oauth2/client/authorized-clients.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
[[oauth2Client-additional-features]]
= Authorized Clients


[[oauth2Client-registered-authorized-client]]
== Resolving an Authorized Client

The `@RegisteredOAuth2AuthorizedClient` annotation provides the capability of resolving a method parameter to an argument value of type `OAuth2AuthorizedClient`.
This is a convenient alternative compared to accessing the `OAuth2AuthorizedClient` using the `ReactiveOAuth2AuthorizedClientManager` or `ReactiveOAuth2AuthorizedClientService`.

====
.Java
[source,java,role="primary"]
----
@Controller
public class OAuth2ClientController {
@GetMapping("/")
public Mono<String> index(@RegisteredOAuth2AuthorizedClient("okta") OAuth2AuthorizedClient authorizedClient) {
return Mono.just(authorizedClient.getAccessToken())
...
.thenReturn("index");
}
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Controller
class OAuth2ClientController {
@GetMapping("/")
fun index(@RegisteredOAuth2AuthorizedClient("okta") authorizedClient: OAuth2AuthorizedClient): Mono<String> {
return Mono.just(authorizedClient.accessToken)
...
.thenReturn("index")
}
}
----
====

The `@RegisteredOAuth2AuthorizedClient` annotation is handled by `OAuth2AuthorizedClientArgumentResolver`, which directly uses a <<oauth2Client-authorized-manager-provider, ReactiveOAuth2AuthorizedClientManager>> and therefore inherits it's capabilities.


[[oauth2Client-webclient-webflux]]
== WebClient integration for Reactive Environments

The OAuth 2.0 Client support integrates with `WebClient` using an `ExchangeFilterFunction`.

The `ServerOAuth2AuthorizedClientExchangeFilterFunction` provides a simple mechanism for requesting protected resources by using an `OAuth2AuthorizedClient` and including the associated `OAuth2AccessToken` as a Bearer Token.
It directly uses an <<oauth2Client-authorized-manager-provider, ReactiveOAuth2AuthorizedClientManager>> and therefore inherits the following capabilities:

* An `OAuth2AccessToken` will be requested if the client has not yet been authorized.
** `authorization_code` - triggers the Authorization Request redirect to initiate the flow
** `client_credentials` - the access token is obtained directly from the Token Endpoint
** `password` - the access token is obtained directly from the Token Endpoint
* If the `OAuth2AccessToken` is expired, it will be refreshed (or renewed) if a `ReactiveOAuth2AuthorizedClientProvider` is available to perform the authorization

The following code shows an example of how to configure `WebClient` with OAuth 2.0 Client support:

====
.Java
[source,java,role="primary"]
----
@Bean
WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.filter(oauth2Client)
.build();
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): WebClient {
val oauth2Client = ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
return WebClient.builder()
.filter(oauth2Client)
.build()
}
----
====

=== Providing the Authorized Client

The `ServerOAuth2AuthorizedClientExchangeFilterFunction` determines the client to use (for a request) by resolving the `OAuth2AuthorizedClient` from the `ClientRequest.attributes()` (request attributes).

The following code shows how to set an `OAuth2AuthorizedClient` as a request attribute:

====
.Java
[source,java,role="primary"]
----
@GetMapping("/")
public Mono<String> index(@RegisteredOAuth2AuthorizedClient("okta") OAuth2AuthorizedClient authorizedClient) {
String resourceUri = ...
return webClient
.get()
.uri(resourceUri)
.attributes(oauth2AuthorizedClient(authorizedClient)) <1>
.retrieve()
.bodyToMono(String.class)
...
.thenReturn("index");
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@GetMapping("/")
fun index(@RegisteredOAuth2AuthorizedClient("okta") authorizedClient: OAuth2AuthorizedClient): Mono<String> {
val resourceUri: String = ...
return webClient
.get()
.uri(resourceUri)
.attributes(oauth2AuthorizedClient(authorizedClient)) <1>
.retrieve()
.bodyToMono<String>()
...
.thenReturn("index")
}
----
====

<1> `oauth2AuthorizedClient()` is a `static` method in `ServerOAuth2AuthorizedClientExchangeFilterFunction`.

The following code shows how to set the `ClientRegistration.getRegistrationId()` as a request attribute:

====
.Java
[source,java,role="primary"]
----
@GetMapping("/")
public Mono<String> index() {
String resourceUri = ...
return webClient
.get()
.uri(resourceUri)
.attributes(clientRegistrationId("okta")) <1>
.retrieve()
.bodyToMono(String.class)
...
.thenReturn("index");
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@GetMapping("/")
fun index(): Mono<String> {
val resourceUri: String = ...
return webClient
.get()
.uri(resourceUri)
.attributes(clientRegistrationId("okta")) <1>
.retrieve()
.bodyToMono<String>()
...
.thenReturn("index")
}
----
====
<1> `clientRegistrationId()` is a `static` method in `ServerOAuth2AuthorizedClientExchangeFilterFunction`.


=== Defaulting the Authorized Client

If neither `OAuth2AuthorizedClient` or `ClientRegistration.getRegistrationId()` is provided as a request attribute, the `ServerOAuth2AuthorizedClientExchangeFilterFunction` can determine the _default_ client to use depending on it's configuration.

If `setDefaultOAuth2AuthorizedClient(true)` is configured and the user has authenticated using `ServerHttpSecurity.oauth2Login()`, the `OAuth2AccessToken` associated with the current `OAuth2AuthenticationToken` is used.

The following code shows the specific configuration:

====
.Java
[source,java,role="primary"]
----
@Bean
WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultOAuth2AuthorizedClient(true);
return WebClient.builder()
.filter(oauth2Client)
.build();
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): WebClient {
val oauth2Client = ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
oauth2Client.setDefaultOAuth2AuthorizedClient(true)
return WebClient.builder()
.filter(oauth2Client)
.build()
}
----
====

[WARNING]
It is recommended to be cautious with this feature since all HTTP requests will receive the access token.

Alternatively, if `setDefaultClientRegistrationId("okta")` is configured with a valid `ClientRegistration`, the `OAuth2AccessToken` associated with the `OAuth2AuthorizedClient` is used.

The following code shows the specific configuration:

====
.Java
[source,java,role="primary"]
----
@Bean
WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
oauth2Client.setDefaultClientRegistrationId("okta");
return WebClient.builder()
.filter(oauth2Client)
.build();
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): WebClient {
val oauth2Client = ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
oauth2Client.setDefaultClientRegistrationId("okta")
return WebClient.builder()
.filter(oauth2Client)
.build()
}
----
====

[WARNING]
It is recommended to be cautious with this feature since all HTTP requests will receive the access token.
Loading

0 comments on commit 4a96374

Please sign in to comment.