diff --git a/docs/manual/src/docs/asciidoc/_includes/preface/whats-new.adoc b/docs/manual/src/docs/asciidoc/_includes/preface/whats-new.adoc index c3c2c353b5..baebebf655 100644 --- a/docs/manual/src/docs/asciidoc/_includes/preface/whats-new.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/preface/whats-new.adoc @@ -12,7 +12,7 @@ Below are the highlights of the release. ** `authorization_code` grant support ** `client_credentials` grant support * OAuth 2.0 Resource Server - support for {gh-samples-url}/boot/oauth2resourceserver[JWT-encoded bearer tokens] -* Added OAuth2 <> integration +* Added OAuth2 <> integration * <> protects against HTTP Verb Tampering and Cross-site Tracing * <> support for selecting an `AccessDeniedHandler` by `RequestMatcher` * <> support for excluding certain requests diff --git a/docs/manual/src/docs/asciidoc/_includes/reactive/webclient.adoc b/docs/manual/src/docs/asciidoc/_includes/reactive/webclient.adoc index 24aae10e8c..f3347b6cde 100644 --- a/docs/manual/src/docs/asciidoc/_includes/reactive/webclient.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/reactive/webclient.adoc @@ -3,7 +3,7 @@ [NOTE] ==== The following documentation is for use within Reactive environments. -For Servlet environments, refer to <> environments. +For Servlet environments, refer to <> environments. ==== diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/additional-topics/webclient.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/additional-topics/webclient.adoc deleted file mode 100644 index 5777d089d8..0000000000 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/additional-topics/webclient.adoc +++ /dev/null @@ -1,102 +0,0 @@ -[[servlet-webclient]] -== WebClient for Servlet Environments - -[NOTE] -==== -The following documentation is for use within Servlet environments. -For all other environments, refer to <> environments. -==== - - -Spring Framework has built in support for setting a Bearer token. - -[source,java] ----- -webClient.get() - .headers(h -> h.setBearerAuth(token)) - ... ----- - -Spring Security builds on this support to provide additional benefits: - -* Spring Security will automatically refresh expired tokens (if a refresh token is present) -* If an access token is requested and not present, Spring Security will automatically request the access token. -** For authorization_code this involves performing the redirect and then replaying the original request -** For client_credentials the token is simply requested and saved -* Support for the ability to transparently include the current OAuth token or explicitly select which token should be used. - -[[servlet-webclient-setup]] -=== WebClient OAuth2 Setup - -The first step is ensuring to setup the `WebClient` correctly. -An example of setting up `WebClient` in a servlet environment can be found below: - -[source,java] ----- -@Bean -WebClient webClient(ClientRegistrationRepository clientRegistrations, - OAuth2AuthorizedClientRepository authorizedClients) { - ServletOAuth2AuthorizedClientExchangeFilterFunction oauth = - new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients); - // (optional) explicitly opt into using the oauth2Login to provide an access token implicitly - // oauth.setDefaultOAuth2AuthorizedClient(true); - // (optional) set a default ClientRegistration.registrationId - // oauth.setDefaultClientRegistrationId("client-registration-id"); - return WebClient.builder() - .apply(oauth2.oauth2Configuration()) - .build(); -} ----- - -[[servlet-webclient-implicit]] -=== Implicit OAuth2AuthorizedClient - -If we set `defaultOAuth2AuthorizedClient` to `true` in our setup and the user authenticated with oauth2Login (i.e. OIDC), then the current authentication is used to automatically provide the access token. -Alternatively, if we set `defaultClientRegistrationId` to a valid `ClientRegistration` id, that registration is used to provide the access token. -This is convenient, but in environments where not all endpoints should get the access token, it is dangerous (you might provide the wrong access token to an endpoint). - -[source,java] ----- -Mono body = this.webClient - .get() - .uri(this.uri) - .retrieve() - .bodyToMono(String.class); ----- - -[[servlet-webclient-explicit]] -=== Explicit OAuth2AuthorizedClient - -The `OAuth2AuthorizedClient` can be explicitly provided by setting it on the request attributes. -In the example below we resolve the `OAuth2AuthorizedClient` using Spring WebFlux or Spring MVC argument resolver support. -However, it does not matter how the `OAuth2AuthorizedClient` is resolved. - -[source,java] ----- -@GetMapping("/explicit") -Mono explicit(@RegisteredOAuth2AuthorizedClient("client-id") OAuth2AuthorizedClient authorizedClient) { - return this.webClient - .get() - .uri(this.uri) - .attributes(oauth2AuthorizedClient(authorizedClient)) - .retrieve() - .bodyToMono(String.class); -} ----- - - -[[servlet-webclient-clientregistrationid]] -=== clientRegistrationId - -Alternatively, it is possible to specify the `clientRegistrationId` on the request attributes and the `WebClient` will attempt to lookup the `OAuth2AuthorizedClient`. -If it is not found, one will automatically be acquired. - -[source,java] ----- -Mono body = this.webClient - .get() - .uri(this.uri) - .attributes(clientRegistrationId("client-id")) - .retrieve() - .bodyToMono(String.class); ----- diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/preface/oauth2-client.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/preface/oauth2-client.adoc index c5e93eabac..07403292c0 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/preface/oauth2-client.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/preface/oauth2-client.adoc @@ -12,7 +12,7 @@ At a high-level, the core features available are: * https://tools.ietf.org/html/rfc6749#section-1.3.3[Resource Owner Password Credentials] .HTTP Client support -* <> (for requesting protected resources) +* <> (for requesting protected resources) The `HttpSecurity.oauth2Client()` DSL provides a number of configuration options for customizing the core components used by OAuth 2.0 Client. In addition, `HttpSecurity.oauth2Client().authorizationCodeGrant()` enables the customization of the Authorization Code grant. @@ -87,6 +87,7 @@ The following sections will go into more detail on the core components used by O ** <> * <> ** <> +* <> [[oauth2Client-core-interface-class]] @@ -1058,3 +1059,131 @@ The `@RegisteredOAuth2AuthorizedClient` annotation is handled by `OAuth2Authoriz ** `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 renewed (or refreshed) if an `OAuth2AuthorizedClientProvider` is available to perform the authorization + + +[[oauth2Client-webclient-servlet]] +=== WebClient integration for Servlet Environments + +The OAuth 2.0 Client support integrates with `WebClient` using an `ExchangeFilterFunction`. + +The `ServletOAuth2AuthorizedClientExchangeFilterFunction` 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 <> 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 an `OAuth2AuthorizedClientProvider` is available to perform the authorization + +The following code shows an example of how to configure `WebClient` with OAuth 2.0 Client support: + +[source,java] +---- +@Bean +WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) { + ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = + new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); + return WebClient.builder() + .apply(oauth2Client.oauth2Configuration()) + .build(); +} +---- + +[NOTE] +Spring Boot 2.x auto-configuration registers an `OAuth2AuthorizedClientManager` `@Bean` in the `ApplicationContext`. + + +==== Providing the Authorized Client + +The `ServletOAuth2AuthorizedClientExchangeFilterFunction` 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: + +[source,java] +---- +@GetMapping("/") +public String index(@RegisteredOAuth2AuthorizedClient("okta") OAuth2AuthorizedClient authorizedClient) { + String resourceUri = ... + + String body = webClient + .get() + .uri(resourceUri) + .attributes(oauth2AuthorizedClient(authorizedClient)) <1> + .retrieve() + .bodyToMono(String.class) + .block(); + + ... + + return "index"; +} +---- +<1> `oauth2AuthorizedClient()` is a `static` method in `ServletOAuth2AuthorizedClientExchangeFilterFunction`. + +The following code shows how to set the `ClientRegistration.getRegistrationId()` as a request attribute: + +[source,java] +---- +@GetMapping("/") +public String index() { + String resourceUri = ... + + String body = webClient + .get() + .uri(resourceUri) + .attributes(clientRegistrationId("okta")) <1> + .retrieve() + .bodyToMono(String.class) + .block(); + + ... + + return "index"; +} +---- +<1> `clientRegistrationId()` is a `static` method in `ServletOAuth2AuthorizedClientExchangeFilterFunction`. + + +==== Defaulting the Authorized Client + +If neither `OAuth2AuthorizedClient` or `ClientRegistration.getRegistrationId()` is provided as a request attribute, the `ServletOAuth2AuthorizedClientExchangeFilterFunction` can determine the _default_ client to use depending on it's configuration. + +If `setDefaultOAuth2AuthorizedClient(true)` is configured and the user has authenticated using `HttpSecurity.oauth2Login()`, the `OAuth2AccessToken` associated with the current `OAuth2AuthenticationToken` is used. + +The following code shows the specific configuration: + +[source,java] +---- +@Bean +WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) { + ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = + new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); + oauth2Client.setDefaultOAuth2AuthorizedClient(true); + return WebClient.builder() + .apply(oauth2Client.oauth2Configuration()) + .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: + +[source,java] +---- +@Bean +WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) { + ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = + new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); + oauth2Client.setDefaultClientRegistrationId("okta"); + return WebClient.builder() + .apply(oauth2Client.oauth2Configuration()) + .build(); +} +---- + +[WARNING] +It is recommended to be cautious with this feature since all HTTP requests will receive the access token.