Skip to content

Commit 4a96374

Browse files
committed
Separate OAuth 2.0 Client Reactive Docs
Issue gh-10367
1 parent 7708418 commit 4a96374

File tree

7 files changed

+992
-1015
lines changed

7 files changed

+992
-1015
lines changed

docs/modules/ROOT/nav.adoc

+5-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,11 @@
122122
*** xref:reactive/authorization/method.adoc[EnableReactiveMethodSecurity]
123123
** xref:reactive/oauth2/index.adoc[OAuth2]
124124
*** xref:reactive/oauth2/login.adoc[OAuth2 Log In]
125-
*** xref:reactive/oauth2/oauth2-client.adoc[OAuth2 Client]
125+
*** xref:reactive/oauth2/client/index.adoc[OAuth2 Client]
126+
**** xref:reactive/oauth2/client/core.adoc[Core Interfaces and Classes]
127+
**** xref:reactive/oauth2/client/authorization-grants.adoc[OAuth2 Authorization Grants]
128+
**** xref:reactive/oauth2/client/client-authentication.adoc[OAuth2 Client Authentication]
129+
**** xref:reactive/oauth2/client/authorized-clients.adoc[OAuth2 Authorized Clients]
126130
*** xref:reactive/oauth2/resource-server/index.adoc[OAuth2 Resource Server]
127131
**** xref:reactive/oauth2/resource-server/jwt.adoc[JWT]
128132
**** xref:reactive/oauth2/resource-server/opaque-token.adoc[Opaque Token]

docs/modules/ROOT/pages/reactive/oauth2/oauth2-client.adoc renamed to docs/modules/ROOT/pages/reactive/oauth2/client/authorization-grants.adoc

+33-1,013
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
[[oauth2Client-additional-features]]
2+
= Authorized Clients
3+
4+
5+
[[oauth2Client-registered-authorized-client]]
6+
== Resolving an Authorized Client
7+
8+
The `@RegisteredOAuth2AuthorizedClient` annotation provides the capability of resolving a method parameter to an argument value of type `OAuth2AuthorizedClient`.
9+
This is a convenient alternative compared to accessing the `OAuth2AuthorizedClient` using the `ReactiveOAuth2AuthorizedClientManager` or `ReactiveOAuth2AuthorizedClientService`.
10+
11+
====
12+
.Java
13+
[source,java,role="primary"]
14+
----
15+
@Controller
16+
public class OAuth2ClientController {
17+
18+
@GetMapping("/")
19+
public Mono<String> index(@RegisteredOAuth2AuthorizedClient("okta") OAuth2AuthorizedClient authorizedClient) {
20+
return Mono.just(authorizedClient.getAccessToken())
21+
...
22+
.thenReturn("index");
23+
}
24+
}
25+
----
26+
27+
.Kotlin
28+
[source,kotlin,role="secondary"]
29+
----
30+
@Controller
31+
class OAuth2ClientController {
32+
@GetMapping("/")
33+
fun index(@RegisteredOAuth2AuthorizedClient("okta") authorizedClient: OAuth2AuthorizedClient): Mono<String> {
34+
return Mono.just(authorizedClient.accessToken)
35+
...
36+
.thenReturn("index")
37+
}
38+
}
39+
----
40+
====
41+
42+
The `@RegisteredOAuth2AuthorizedClient` annotation is handled by `OAuth2AuthorizedClientArgumentResolver`, which directly uses a <<oauth2Client-authorized-manager-provider, ReactiveOAuth2AuthorizedClientManager>> and therefore inherits it's capabilities.
43+
44+
45+
[[oauth2Client-webclient-webflux]]
46+
== WebClient integration for Reactive Environments
47+
48+
The OAuth 2.0 Client support integrates with `WebClient` using an `ExchangeFilterFunction`.
49+
50+
The `ServerOAuth2AuthorizedClientExchangeFilterFunction` provides a simple mechanism for requesting protected resources by using an `OAuth2AuthorizedClient` and including the associated `OAuth2AccessToken` as a Bearer Token.
51+
It directly uses an <<oauth2Client-authorized-manager-provider, ReactiveOAuth2AuthorizedClientManager>> and therefore inherits the following capabilities:
52+
53+
* An `OAuth2AccessToken` will be requested if the client has not yet been authorized.
54+
** `authorization_code` - triggers the Authorization Request redirect to initiate the flow
55+
** `client_credentials` - the access token is obtained directly from the Token Endpoint
56+
** `password` - the access token is obtained directly from the Token Endpoint
57+
* If the `OAuth2AccessToken` is expired, it will be refreshed (or renewed) if a `ReactiveOAuth2AuthorizedClientProvider` is available to perform the authorization
58+
59+
The following code shows an example of how to configure `WebClient` with OAuth 2.0 Client support:
60+
61+
====
62+
.Java
63+
[source,java,role="primary"]
64+
----
65+
@Bean
66+
WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
67+
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
68+
new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
69+
return WebClient.builder()
70+
.filter(oauth2Client)
71+
.build();
72+
}
73+
----
74+
75+
.Kotlin
76+
[source,kotlin,role="secondary"]
77+
----
78+
@Bean
79+
fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): WebClient {
80+
val oauth2Client = ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
81+
return WebClient.builder()
82+
.filter(oauth2Client)
83+
.build()
84+
}
85+
----
86+
====
87+
88+
=== Providing the Authorized Client
89+
90+
The `ServerOAuth2AuthorizedClientExchangeFilterFunction` determines the client to use (for a request) by resolving the `OAuth2AuthorizedClient` from the `ClientRequest.attributes()` (request attributes).
91+
92+
The following code shows how to set an `OAuth2AuthorizedClient` as a request attribute:
93+
94+
====
95+
.Java
96+
[source,java,role="primary"]
97+
----
98+
@GetMapping("/")
99+
public Mono<String> index(@RegisteredOAuth2AuthorizedClient("okta") OAuth2AuthorizedClient authorizedClient) {
100+
String resourceUri = ...
101+
102+
return webClient
103+
.get()
104+
.uri(resourceUri)
105+
.attributes(oauth2AuthorizedClient(authorizedClient)) <1>
106+
.retrieve()
107+
.bodyToMono(String.class)
108+
...
109+
.thenReturn("index");
110+
}
111+
----
112+
113+
.Kotlin
114+
[source,kotlin,role="secondary"]
115+
----
116+
@GetMapping("/")
117+
fun index(@RegisteredOAuth2AuthorizedClient("okta") authorizedClient: OAuth2AuthorizedClient): Mono<String> {
118+
val resourceUri: String = ...
119+
120+
return webClient
121+
.get()
122+
.uri(resourceUri)
123+
.attributes(oauth2AuthorizedClient(authorizedClient)) <1>
124+
.retrieve()
125+
.bodyToMono<String>()
126+
...
127+
.thenReturn("index")
128+
}
129+
----
130+
====
131+
132+
<1> `oauth2AuthorizedClient()` is a `static` method in `ServerOAuth2AuthorizedClientExchangeFilterFunction`.
133+
134+
The following code shows how to set the `ClientRegistration.getRegistrationId()` as a request attribute:
135+
136+
====
137+
.Java
138+
[source,java,role="primary"]
139+
----
140+
@GetMapping("/")
141+
public Mono<String> index() {
142+
String resourceUri = ...
143+
144+
return webClient
145+
.get()
146+
.uri(resourceUri)
147+
.attributes(clientRegistrationId("okta")) <1>
148+
.retrieve()
149+
.bodyToMono(String.class)
150+
...
151+
.thenReturn("index");
152+
}
153+
----
154+
155+
.Kotlin
156+
[source,kotlin,role="secondary"]
157+
----
158+
@GetMapping("/")
159+
fun index(): Mono<String> {
160+
val resourceUri: String = ...
161+
162+
return webClient
163+
.get()
164+
.uri(resourceUri)
165+
.attributes(clientRegistrationId("okta")) <1>
166+
.retrieve()
167+
.bodyToMono<String>()
168+
...
169+
.thenReturn("index")
170+
}
171+
----
172+
====
173+
<1> `clientRegistrationId()` is a `static` method in `ServerOAuth2AuthorizedClientExchangeFilterFunction`.
174+
175+
176+
=== Defaulting the Authorized Client
177+
178+
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.
179+
180+
If `setDefaultOAuth2AuthorizedClient(true)` is configured and the user has authenticated using `ServerHttpSecurity.oauth2Login()`, the `OAuth2AccessToken` associated with the current `OAuth2AuthenticationToken` is used.
181+
182+
The following code shows the specific configuration:
183+
184+
====
185+
.Java
186+
[source,java,role="primary"]
187+
----
188+
@Bean
189+
WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
190+
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
191+
new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
192+
oauth2Client.setDefaultOAuth2AuthorizedClient(true);
193+
return WebClient.builder()
194+
.filter(oauth2Client)
195+
.build();
196+
}
197+
----
198+
199+
.Kotlin
200+
[source,kotlin,role="secondary"]
201+
----
202+
@Bean
203+
fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): WebClient {
204+
val oauth2Client = ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
205+
oauth2Client.setDefaultOAuth2AuthorizedClient(true)
206+
return WebClient.builder()
207+
.filter(oauth2Client)
208+
.build()
209+
}
210+
----
211+
====
212+
213+
[WARNING]
214+
It is recommended to be cautious with this feature since all HTTP requests will receive the access token.
215+
216+
Alternatively, if `setDefaultClientRegistrationId("okta")` is configured with a valid `ClientRegistration`, the `OAuth2AccessToken` associated with the `OAuth2AuthorizedClient` is used.
217+
218+
The following code shows the specific configuration:
219+
220+
====
221+
.Java
222+
[source,java,role="primary"]
223+
----
224+
@Bean
225+
WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
226+
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
227+
new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
228+
oauth2Client.setDefaultClientRegistrationId("okta");
229+
return WebClient.builder()
230+
.filter(oauth2Client)
231+
.build();
232+
}
233+
----
234+
235+
.Kotlin
236+
[source,kotlin,role="secondary"]
237+
----
238+
@Bean
239+
fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): WebClient {
240+
val oauth2Client = ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
241+
oauth2Client.setDefaultClientRegistrationId("okta")
242+
return WebClient.builder()
243+
.filter(oauth2Client)
244+
.build()
245+
}
246+
----
247+
====
248+
249+
[WARNING]
250+
It is recommended to be cautious with this feature since all HTTP requests will receive the access token.

0 commit comments

Comments
 (0)