Add servlet OAuth2 client Kotlin samples

Issue gh-8172
This commit is contained in:
Eleftheria Stein 2020-08-11 14:07:35 +02:00
parent be6d2f117e
commit 69e0552c30

View File

@ -93,7 +93,9 @@ The `OAuth2AuthorizedClientManager` is responsible for managing the authorizatio
The following code shows an example of how to register an `OAuth2AuthorizedClientManager` `@Bean` and associate it with an `OAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token`, `client_credentials` and `password` authorization grant types: The following code shows an example of how to register an `OAuth2AuthorizedClientManager` `@Bean` and associate it with an `OAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token`, `client_credentials` and `password` authorization grant types:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@Bean @Bean
public OAuth2AuthorizedClientManager authorizedClientManager( public OAuth2AuthorizedClientManager authorizedClientManager(
@ -117,6 +119,27 @@ public OAuth2AuthorizedClientManager authorizedClientManager(
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun authorizedClientManager(
clientRegistrationRepository: ClientRegistrationRepository,
authorizedClientRepository: OAuth2AuthorizedClientRepository): OAuth2AuthorizedClientManager {
val authorizedClientProvider: OAuth2AuthorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.clientCredentials()
.password()
.build()
val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository)
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
return authorizedClientManager
}
----
====
The following sections will go into more detail on the core components used by OAuth 2.0 Client and the configuration options available: The following sections will go into more detail on the core components used by OAuth 2.0 Client and the configuration options available:
* <<oauth2Client-core-interface-class>> * <<oauth2Client-core-interface-class>>
@ -206,12 +229,21 @@ A `ClientRegistration` can be initially configured using discovery of an OpenID
`ClientRegistrations` provides convenience methods for configuring a `ClientRegistration` in this way, as can be seen in the following example: `ClientRegistrations` provides convenience methods for configuring a `ClientRegistration` in this way, as can be seen in the following example:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
ClientRegistration clientRegistration = ClientRegistration clientRegistration =
ClientRegistrations.fromIssuerLocation("https://idp.example.com/issuer").build(); ClientRegistrations.fromIssuerLocation("https://idp.example.com/issuer").build();
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
val clientRegistration = ClientRegistrations.fromIssuerLocation("https://idp.example.com/issuer").build()
----
====
The above code will query in series `https://idp.example.com/issuer/.well-known/openid-configuration`, and then `https://idp.example.com/.well-known/openid-configuration/issuer`, and finally `https://idp.example.com/.well-known/oauth-authorization-server/issuer`, stopping at the first to return a 200 response. The above code will query in series `https://idp.example.com/issuer/.well-known/openid-configuration`, and then `https://idp.example.com/.well-known/openid-configuration/issuer`, and finally `https://idp.example.com/.well-known/oauth-authorization-server/issuer`, stopping at the first to return a 200 response.
As an alternative, you can use `ClientRegistrations.fromOidcIssuerLocation()` to only query the OpenID Connect Provider's Configuration endpoint. As an alternative, you can use `ClientRegistrations.fromOidcIssuerLocation()` to only query the OpenID Connect Provider's Configuration endpoint.
@ -234,7 +266,9 @@ The auto-configuration also registers the `ClientRegistrationRepository` as a `@
The following listing shows an example: The following listing shows an example:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@Controller @Controller
public class OAuth2ClientController { public class OAuth2ClientController {
@ -254,6 +288,27 @@ public class OAuth2ClientController {
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@Controller
class OAuth2ClientController {
@Autowired
private lateinit var clientRegistrationRepository: ClientRegistrationRepository
@GetMapping("/")
fun index(): String {
val oktaRegistration =
this.clientRegistrationRepository.findByRegistrationId("okta")
//...
return "index";
}
}
----
====
[[oauth2Client-authorized-client]] [[oauth2Client-authorized-client]]
==== OAuth2AuthorizedClient ==== OAuth2AuthorizedClient
@ -274,7 +329,9 @@ From a developer perspective, the `OAuth2AuthorizedClientRepository` or `OAuth2A
The following listing shows an example: The following listing shows an example:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@Controller @Controller
public class OAuth2ClientController { public class OAuth2ClientController {
@ -296,6 +353,29 @@ public class OAuth2ClientController {
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@Controller
class OAuth2ClientController {
@Autowired
private lateinit var authorizedClientService: OAuth2AuthorizedClientService
@GetMapping("/")
fun index(authentication: Authentication): String {
val authorizedClient: OAuth2AuthorizedClient =
this.authorizedClientService.loadAuthorizedClient("okta", authentication.getName());
val accessToken = authorizedClient.accessToken
...
return "index";
}
}
----
====
[NOTE] [NOTE]
Spring Boot 2.x auto-configuration registers an `OAuth2AuthorizedClientRepository` and/or `OAuth2AuthorizedClientService` `@Bean` in the `ApplicationContext`. Spring Boot 2.x auto-configuration registers an `OAuth2AuthorizedClientRepository` and/or `OAuth2AuthorizedClientService` `@Bean` in the `ApplicationContext`.
However, the application may choose to override and register a custom `OAuth2AuthorizedClientRepository` or `OAuth2AuthorizedClientService` `@Bean`. However, the application may choose to override and register a custom `OAuth2AuthorizedClientRepository` or `OAuth2AuthorizedClientService` `@Bean`.
@ -328,7 +408,9 @@ The `OAuth2AuthorizedClientProviderBuilder` may be used to configure and build t
The following code shows an example of how to configure and build an `OAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token`, `client_credentials` and `password` authorization grant types: The following code shows an example of how to configure and build an `OAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token`, `client_credentials` and `password` authorization grant types:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@Bean @Bean
public OAuth2AuthorizedClientManager authorizedClientManager( public OAuth2AuthorizedClientManager authorizedClientManager(
@ -352,6 +434,27 @@ public OAuth2AuthorizedClientManager authorizedClientManager(
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun authorizedClientManager(
clientRegistrationRepository: ClientRegistrationRepository,
authorizedClientRepository: OAuth2AuthorizedClientRepository): OAuth2AuthorizedClientManager {
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.clientCredentials()
.password()
.build()
val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository)
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
return authorizedClientManager
}
----
====
When an authorization attempt succeeds, the `DefaultOAuth2AuthorizedClientManager` will delegate to the `OAuth2AuthorizationSuccessHandler`, which (by default) will save the `OAuth2AuthorizedClient` via the `OAuth2AuthorizedClientRepository`. When an authorization attempt succeeds, the `DefaultOAuth2AuthorizedClientManager` will delegate to the `OAuth2AuthorizationSuccessHandler`, which (by default) will save the `OAuth2AuthorizedClient` via the `OAuth2AuthorizedClientRepository`.
In the case of a re-authorization failure, eg. a refresh token is no longer valid, the previously saved `OAuth2AuthorizedClient` will be removed from the `OAuth2AuthorizedClientRepository` via the `RemoveAuthorizedClientOAuth2AuthorizationFailureHandler`. In the case of a re-authorization failure, eg. a refresh token is no longer valid, the previously saved `OAuth2AuthorizedClient` will be removed from the `OAuth2AuthorizedClientRepository` via the `RemoveAuthorizedClientOAuth2AuthorizationFailureHandler`.
The default behaviour may be customized via `setAuthorizationSuccessHandler(OAuth2AuthorizationSuccessHandler)` and `setAuthorizationFailureHandler(OAuth2AuthorizationFailureHandler)`. The default behaviour may be customized via `setAuthorizationSuccessHandler(OAuth2AuthorizationSuccessHandler)` and `setAuthorizationFailureHandler(OAuth2AuthorizationFailureHandler)`.
@ -361,7 +464,9 @@ This can be useful when you need to supply an `OAuth2AuthorizedClientProvider` w
The following code shows an example of the `contextAttributesMapper`: The following code shows an example of the `contextAttributesMapper`:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@Bean @Bean
public OAuth2AuthorizedClientManager authorizedClientManager( public OAuth2AuthorizedClientManager authorizedClientManager(
@ -404,6 +509,46 @@ private Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesM
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun authorizedClientManager(
clientRegistrationRepository: ClientRegistrationRepository,
authorizedClientRepository: OAuth2AuthorizedClientRepository): OAuth2AuthorizedClientManager {
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.password()
.refreshToken()
.build()
val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository)
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
// Assuming the `username` and `password` are supplied as `HttpServletRequest` parameters,
// map the `HttpServletRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper())
return authorizedClientManager
}
private fun contextAttributesMapper(): Function<OAuth2AuthorizeRequest, MutableMap<String, Any>> {
return Function { authorizeRequest ->
var contextAttributes: MutableMap<String, Any> = mutableMapOf()
val servletRequest: HttpServletRequest = authorizeRequest.getAttribute(HttpServletRequest::class.java.name)
val username: String = servletRequest.getParameter(OAuth2ParameterNames.USERNAME)
val password: String = servletRequest.getParameter(OAuth2ParameterNames.PASSWORD)
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
contextAttributes = hashMapOf()
// `PasswordOAuth2AuthorizedClientProvider` requires both attributes
contextAttributes[OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME] = username
contextAttributes[OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME] = password
}
contextAttributes
}
}
----
====
The `DefaultOAuth2AuthorizedClientManager` is designed to be used *_within_* the context of a `HttpServletRequest`. The `DefaultOAuth2AuthorizedClientManager` is designed to be used *_within_* the context of a `HttpServletRequest`.
When operating *_outside_* of a `HttpServletRequest` context, use `AuthorizedClientServiceOAuth2AuthorizedClientManager` instead. When operating *_outside_* of a `HttpServletRequest` context, use `AuthorizedClientServiceOAuth2AuthorizedClientManager` instead.
@ -413,7 +558,9 @@ An OAuth 2.0 Client configured with the `client_credentials` grant type can be c
The following code shows an example of how to configure an `AuthorizedClientServiceOAuth2AuthorizedClientManager` that provides support for the `client_credentials` grant type: The following code shows an example of how to configure an `AuthorizedClientServiceOAuth2AuthorizedClientManager` that provides support for the `client_credentials` grant type:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@Bean @Bean
public OAuth2AuthorizedClientManager authorizedClientManager( public OAuth2AuthorizedClientManager authorizedClientManager(
@ -434,6 +581,24 @@ public OAuth2AuthorizedClientManager authorizedClientManager(
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun authorizedClientManager(
clientRegistrationRepository: ClientRegistrationRepository,
authorizedClientService: OAuth2AuthorizedClientService): OAuth2AuthorizedClientManager {
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build()
val authorizedClientManager = AuthorizedClientServiceOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientService)
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
return authorizedClientManager
}
----
====
[[oauth2Client-auth-grant-support]] [[oauth2Client-auth-grant-support]]
=== Authorization Grant Support === Authorization Grant Support
@ -545,7 +710,9 @@ OPTIONAL. Space delimited, case sensitive list of ASCII string values that speci
The following example shows how to configure the `DefaultOAuth2AuthorizationRequestResolver` with a `Consumer<OAuth2AuthorizationRequest.Builder>` that customizes the Authorization Request for `oauth2Login()`, by including the request parameter `prompt=consent`. The following example shows how to configure the `DefaultOAuth2AuthorizationRequestResolver` with a `Consumer<OAuth2AuthorizationRequest.Builder>` that customizes the Authorization Request for `oauth2Login()`, by including the request parameter `prompt=consent`.
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@EnableWebSecurity @EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter { public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
@ -587,6 +754,47 @@ public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {
@Autowired
private lateinit var customClientRegistrationRepository: ClientRegistrationRepository
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
oauth2Login {
authorizationEndpoint {
authorizationRequestResolver = authorizationRequestResolver(customClientRegistrationRepository)
}
}
}
}
private fun authorizationRequestResolver(
clientRegistrationRepository: ClientRegistrationRepository?): OAuth2AuthorizationRequestResolver? {
val authorizationRequestResolver = DefaultOAuth2AuthorizationRequestResolver(
clientRegistrationRepository, "/oauth2/authorization")
authorizationRequestResolver.setAuthorizationRequestCustomizer(
authorizationRequestCustomizer())
return authorizationRequestResolver
}
private fun authorizationRequestCustomizer(): Consumer<OAuth2AuthorizationRequest.Builder> {
return Consumer { customizer ->
customizer
.additionalParameters { params -> params["prompt"] = "consent" }
}
}
}
----
====
For the simple use case, where the additional request parameter is always the same for a specific provider, it may be added directly in the `authorization-uri` property. For the simple use case, where the additional request parameter is always the same for a specific provider, it may be added directly in the `authorization-uri` property.
For example, if the value for the request parameter `prompt` is always `consent` for the provider `okta`, than simply configure as follows: For example, if the value for the request parameter `prompt` is always `consent` for the provider `okta`, than simply configure as follows:
@ -610,7 +818,9 @@ Alternatively, if your requirements are more advanced, you can take full control
The following example shows a variation of `authorizationRequestCustomizer()` from the preceding example, and instead overrides the `OAuth2AuthorizationRequest.authorizationRequestUri` property. The following example shows a variation of `authorizationRequestCustomizer()` from the preceding example, and instead overrides the `OAuth2AuthorizationRequest.authorizationRequestUri` property.
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer() { private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer() {
return customizer -> customizer return customizer -> customizer
@ -619,6 +829,21 @@ private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomi
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
private fun authorizationRequestCustomizer(): Consumer<OAuth2AuthorizationRequest.Builder> {
return Consumer { customizer: OAuth2AuthorizationRequest.Builder ->
customizer
.authorizationRequestUri { uriBuilder: UriBuilder ->
uriBuilder
.queryParam("prompt", "consent").build()
}
}
}
----
====
===== Storing the Authorization Request ===== Storing the Authorization Request
@ -705,7 +930,9 @@ IMPORTANT: The custom `Converter` must return a valid `RequestEntity` representa
On the other end, if you need to customize the post-handling of the Token Response, you will need to provide `DefaultAuthorizationCodeTokenResponseClient.setRestOperations()` with a custom configured `RestOperations`. On the other end, if you need to customize the post-handling of the Token Response, you will need to provide `DefaultAuthorizationCodeTokenResponseClient.setRestOperations()` with a custom configured `RestOperations`.
The default `RestOperations` is configured as follows: The default `RestOperations` is configured as follows:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
RestTemplate restTemplate = new RestTemplate(Arrays.asList( RestTemplate restTemplate = new RestTemplate(Arrays.asList(
new FormHttpMessageConverter(), new FormHttpMessageConverter(),
@ -714,6 +941,17 @@ RestTemplate restTemplate = new RestTemplate(Arrays.asList(
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
val restTemplate = RestTemplate(listOf(
FormHttpMessageConverter(),
OAuth2AccessTokenResponseHttpMessageConverter()))
restTemplate.errorHandler = OAuth2ErrorResponseErrorHandler()
----
====
TIP: Spring MVC `FormHttpMessageConverter` is required as it's used when sending the OAuth 2.0 Access Token Request. TIP: Spring MVC `FormHttpMessageConverter` is required as it's used when sending the OAuth 2.0 Access Token Request.
`OAuth2AccessTokenResponseHttpMessageConverter` is a `HttpMessageConverter` for an OAuth 2.0 Access Token Response. `OAuth2AccessTokenResponseHttpMessageConverter` is a `HttpMessageConverter` for an OAuth 2.0 Access Token Response.
@ -806,7 +1044,9 @@ IMPORTANT: The custom `Converter` must return a valid `RequestEntity` representa
On the other end, if you need to customize the post-handling of the Token Response, you will need to provide `DefaultRefreshTokenTokenResponseClient.setRestOperations()` with a custom configured `RestOperations`. On the other end, if you need to customize the post-handling of the Token Response, you will need to provide `DefaultRefreshTokenTokenResponseClient.setRestOperations()` with a custom configured `RestOperations`.
The default `RestOperations` is configured as follows: The default `RestOperations` is configured as follows:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
RestTemplate restTemplate = new RestTemplate(Arrays.asList( RestTemplate restTemplate = new RestTemplate(Arrays.asList(
new FormHttpMessageConverter(), new FormHttpMessageConverter(),
@ -815,6 +1055,17 @@ RestTemplate restTemplate = new RestTemplate(Arrays.asList(
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
val restTemplate = RestTemplate(listOf(
FormHttpMessageConverter(),
OAuth2AccessTokenResponseHttpMessageConverter()))
restTemplate.errorHandler = OAuth2ErrorResponseErrorHandler()
----
====
TIP: Spring MVC `FormHttpMessageConverter` is required as it's used when sending the OAuth 2.0 Access Token Request. TIP: Spring MVC `FormHttpMessageConverter` is required as it's used when sending the OAuth 2.0 Access Token Request.
`OAuth2AccessTokenResponseHttpMessageConverter` is a `HttpMessageConverter` for an OAuth 2.0 Access Token Response. `OAuth2AccessTokenResponseHttpMessageConverter` is a `HttpMessageConverter` for an OAuth 2.0 Access Token Response.
@ -825,7 +1076,9 @@ It uses an `OAuth2ErrorHttpMessageConverter` for converting the OAuth 2.0 Error
Whether you customize `DefaultRefreshTokenTokenResponseClient` or provide your own implementation of `OAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example: Whether you customize `DefaultRefreshTokenTokenResponseClient` or provide your own implementation of `OAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
// Customize // Customize
OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenTokenResponseClient = ... OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenTokenResponseClient = ...
@ -841,6 +1094,23 @@ OAuth2AuthorizedClientProvider authorizedClientProvider =
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
// Customize
val refreshTokenTokenResponseClient: OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> = ...
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken { it.accessTokenResponseClient(refreshTokenTokenResponseClient) }
.build()
...
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
----
====
[NOTE] [NOTE]
`OAuth2AuthorizedClientProviderBuilder.builder().refreshToken()` configures a `RefreshTokenOAuth2AuthorizedClientProvider`, `OAuth2AuthorizedClientProviderBuilder.builder().refreshToken()` configures a `RefreshTokenOAuth2AuthorizedClientProvider`,
which is an implementation of an `OAuth2AuthorizedClientProvider` for the Refresh Token grant. which is an implementation of an `OAuth2AuthorizedClientProvider` for the Refresh Token grant.
@ -880,7 +1150,9 @@ IMPORTANT: The custom `Converter` must return a valid `RequestEntity` representa
On the other end, if you need to customize the post-handling of the Token Response, you will need to provide `DefaultClientCredentialsTokenResponseClient.setRestOperations()` with a custom configured `RestOperations`. On the other end, if you need to customize the post-handling of the Token Response, you will need to provide `DefaultClientCredentialsTokenResponseClient.setRestOperations()` with a custom configured `RestOperations`.
The default `RestOperations` is configured as follows: The default `RestOperations` is configured as follows:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
RestTemplate restTemplate = new RestTemplate(Arrays.asList( RestTemplate restTemplate = new RestTemplate(Arrays.asList(
new FormHttpMessageConverter(), new FormHttpMessageConverter(),
@ -889,6 +1161,17 @@ RestTemplate restTemplate = new RestTemplate(Arrays.asList(
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
val restTemplate = RestTemplate(listOf(
FormHttpMessageConverter(),
OAuth2AccessTokenResponseHttpMessageConverter()))
restTemplate.errorHandler = OAuth2ErrorResponseErrorHandler()
----
====
TIP: Spring MVC `FormHttpMessageConverter` is required as it's used when sending the OAuth 2.0 Access Token Request. TIP: Spring MVC `FormHttpMessageConverter` is required as it's used when sending the OAuth 2.0 Access Token Request.
`OAuth2AccessTokenResponseHttpMessageConverter` is a `HttpMessageConverter` for an OAuth 2.0 Access Token Response. `OAuth2AccessTokenResponseHttpMessageConverter` is a `HttpMessageConverter` for an OAuth 2.0 Access Token Response.
@ -899,7 +1182,9 @@ It uses an `OAuth2ErrorHttpMessageConverter` for converting the OAuth 2.0 Error
Whether you customize `DefaultClientCredentialsTokenResponseClient` or provide your own implementation of `OAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example: Whether you customize `DefaultClientCredentialsTokenResponseClient` or provide your own implementation of `OAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
// Customize // Customize
OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsTokenResponseClient = ... OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsTokenResponseClient = ...
@ -914,6 +1199,22 @@ OAuth2AuthorizedClientProvider authorizedClientProvider =
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
// Customize
val clientCredentialsTokenResponseClient: OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> = ...
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials { it.accessTokenResponseClient(clientCredentialsTokenResponseClient) }
.build()
...
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
----
====
[NOTE] [NOTE]
`OAuth2AuthorizedClientProviderBuilder.builder().clientCredentials()` configures a `ClientCredentialsOAuth2AuthorizedClientProvider`, `OAuth2AuthorizedClientProviderBuilder.builder().clientCredentials()` configures a `ClientCredentialsOAuth2AuthorizedClientProvider`,
which is an implementation of an `OAuth2AuthorizedClientProvider` for the Client Credentials grant. which is an implementation of an `OAuth2AuthorizedClientProvider` for the Client Credentials grant.
@ -941,7 +1242,9 @@ spring:
...and the `OAuth2AuthorizedClientManager` `@Bean`: ...and the `OAuth2AuthorizedClientManager` `@Bean`:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@Bean @Bean
public OAuth2AuthorizedClientManager authorizedClientManager( public OAuth2AuthorizedClientManager authorizedClientManager(
@ -962,9 +1265,29 @@ public OAuth2AuthorizedClientManager authorizedClientManager(
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun authorizedClientManager(
clientRegistrationRepository: ClientRegistrationRepository,
authorizedClientRepository: OAuth2AuthorizedClientRepository): OAuth2AuthorizedClientManager {
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build()
val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository)
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
return authorizedClientManager
}
----
====
You may obtain the `OAuth2AccessToken` as follows: You may obtain the `OAuth2AccessToken` as follows:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@Controller @Controller
public class OAuth2ClientController { public class OAuth2ClientController {
@ -995,6 +1318,36 @@ public class OAuth2ClientController {
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
class OAuth2ClientController {
@Autowired
private lateinit var authorizedClientManager: OAuth2AuthorizedClientManager
@GetMapping("/")
fun index(authentication: Authentication?,
servletRequest: HttpServletRequest,
servletResponse: HttpServletResponse): String {
val authorizeRequest: OAuth2AuthorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
.principal(authentication)
.attributes(Consumer { attrs: MutableMap<String, Any> ->
attrs[HttpServletRequest::class.java.name] = servletRequest
attrs[HttpServletResponse::class.java.name] = servletResponse
})
.build()
val authorizedClient = authorizedClientManager.authorize(authorizeRequest)
val accessToken: OAuth2AccessToken = authorizedClient.accessToken
...
return "index"
}
}
----
====
[NOTE] [NOTE]
`HttpServletRequest` and `HttpServletResponse` are both OPTIONAL attributes. `HttpServletRequest` and `HttpServletResponse` are both OPTIONAL attributes.
If not provided, it will default to `ServletRequestAttributes` using `RequestContextHolder.getRequestAttributes()`. If not provided, it will default to `ServletRequestAttributes` using `RequestContextHolder.getRequestAttributes()`.
@ -1031,7 +1384,9 @@ IMPORTANT: The custom `Converter` must return a valid `RequestEntity` representa
On the other end, if you need to customize the post-handling of the Token Response, you will need to provide `DefaultPasswordTokenResponseClient.setRestOperations()` with a custom configured `RestOperations`. On the other end, if you need to customize the post-handling of the Token Response, you will need to provide `DefaultPasswordTokenResponseClient.setRestOperations()` with a custom configured `RestOperations`.
The default `RestOperations` is configured as follows: The default `RestOperations` is configured as follows:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
RestTemplate restTemplate = new RestTemplate(Arrays.asList( RestTemplate restTemplate = new RestTemplate(Arrays.asList(
new FormHttpMessageConverter(), new FormHttpMessageConverter(),
@ -1040,6 +1395,17 @@ RestTemplate restTemplate = new RestTemplate(Arrays.asList(
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
val restTemplate = RestTemplate(listOf(
FormHttpMessageConverter(),
OAuth2AccessTokenResponseHttpMessageConverter()))
restTemplate.errorHandler = OAuth2ErrorResponseErrorHandler()
----
====
TIP: Spring MVC `FormHttpMessageConverter` is required as it's used when sending the OAuth 2.0 Access Token Request. TIP: Spring MVC `FormHttpMessageConverter` is required as it's used when sending the OAuth 2.0 Access Token Request.
`OAuth2AccessTokenResponseHttpMessageConverter` is a `HttpMessageConverter` for an OAuth 2.0 Access Token Response. `OAuth2AccessTokenResponseHttpMessageConverter` is a `HttpMessageConverter` for an OAuth 2.0 Access Token Response.
@ -1050,7 +1416,9 @@ It uses an `OAuth2ErrorHttpMessageConverter` for converting the OAuth 2.0 Error
Whether you customize `DefaultPasswordTokenResponseClient` or provide your own implementation of `OAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example: Whether you customize `DefaultPasswordTokenResponseClient` or provide your own implementation of `OAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
// Customize // Customize
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient = ... OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient = ...
@ -1066,6 +1434,22 @@ OAuth2AuthorizedClientProvider authorizedClientProvider =
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
val passwordTokenResponseClient: OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> = ...
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.password { it.accessTokenResponseClient(passwordTokenResponseClient) }
.refreshToken()
.build()
...
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
----
====
[NOTE] [NOTE]
`OAuth2AuthorizedClientProviderBuilder.builder().password()` configures a `PasswordOAuth2AuthorizedClientProvider`, `OAuth2AuthorizedClientProviderBuilder.builder().password()` configures a `PasswordOAuth2AuthorizedClientProvider`,
which is an implementation of an `OAuth2AuthorizedClientProvider` for the Resource Owner Password Credentials grant. which is an implementation of an `OAuth2AuthorizedClientProvider` for the Resource Owner Password Credentials grant.
@ -1093,7 +1477,9 @@ spring:
...and the `OAuth2AuthorizedClientManager` `@Bean`: ...and the `OAuth2AuthorizedClientManager` `@Bean`:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@Bean @Bean
public OAuth2AuthorizedClientManager authorizedClientManager( public OAuth2AuthorizedClientManager authorizedClientManager(
@ -1135,10 +1521,51 @@ private Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesM
}; };
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun authorizedClientManager(
clientRegistrationRepository: ClientRegistrationRepository,
authorizedClientRepository: OAuth2AuthorizedClientRepository): OAuth2AuthorizedClientManager {
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.password()
.refreshToken()
.build()
val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository)
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
// Assuming the `username` and `password` are supplied as `HttpServletRequest` parameters,
// map the `HttpServletRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper())
return authorizedClientManager
}
private fun contextAttributesMapper(): Function<OAuth2AuthorizeRequest, MutableMap<String, Any>> {
return Function { authorizeRequest ->
var contextAttributes: MutableMap<String, Any> = mutableMapOf()
val servletRequest: HttpServletRequest = authorizeRequest.getAttribute(HttpServletRequest::class.java.name)
val username = servletRequest.getParameter(OAuth2ParameterNames.USERNAME)
val password = servletRequest.getParameter(OAuth2ParameterNames.PASSWORD)
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
contextAttributes = hashMapOf()
// `PasswordOAuth2AuthorizedClientProvider` requires both attributes
contextAttributes[OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME] = username
contextAttributes[OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME] = password
}
contextAttributes
}
}
----
====
You may obtain the `OAuth2AccessToken` as follows: You may obtain the `OAuth2AccessToken` as follows:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@Controller @Controller
public class OAuth2ClientController { public class OAuth2ClientController {
@ -1169,6 +1596,36 @@ public class OAuth2ClientController {
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@Controller
class OAuth2ClientController {
@Autowired
private lateinit var authorizedClientManager: OAuth2AuthorizedClientManager
@GetMapping("/")
fun index(authentication: Authentication?,
servletRequest: HttpServletRequest,
servletResponse: HttpServletResponse): String {
val authorizeRequest: OAuth2AuthorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
.principal(authentication)
.attributes(Consumer {
it[HttpServletRequest::class.java.name] = servletRequest
it[HttpServletResponse::class.java.name] = servletResponse
})
.build()
val authorizedClient = authorizedClientManager.authorize(authorizeRequest)
val accessToken: OAuth2AccessToken = authorizedClient.accessToken
...
return "index"
}
}
----
====
[NOTE] [NOTE]
`HttpServletRequest` and `HttpServletResponse` are both OPTIONAL attributes. `HttpServletRequest` and `HttpServletResponse` are both OPTIONAL attributes.
If not provided, it will default to `ServletRequestAttributes` using `RequestContextHolder.getRequestAttributes()`. If not provided, it will default to `ServletRequestAttributes` using `RequestContextHolder.getRequestAttributes()`.
@ -1184,7 +1641,9 @@ If not provided, it will default to `ServletRequestAttributes` using `RequestCon
The `@RegisteredOAuth2AuthorizedClient` annotation provides the capability of resolving a method parameter to an argument value of type `OAuth2AuthorizedClient`. 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 `OAuth2AuthorizedClientManager` or `OAuth2AuthorizedClientService`. This is a convenient alternative compared to accessing the `OAuth2AuthorizedClient` using the `OAuth2AuthorizedClientManager` or `OAuth2AuthorizedClientService`.
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@Controller @Controller
public class OAuth2ClientController { public class OAuth2ClientController {
@ -1200,6 +1659,23 @@ public class OAuth2ClientController {
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@Controller
class OAuth2ClientController {
@GetMapping("/")
fun index(@RegisteredOAuth2AuthorizedClient("okta") authorizedClient: OAuth2AuthorizedClient): String {
val accessToken = authorizedClient.accessToken
...
return "index"
}
}
----
====
The `@RegisteredOAuth2AuthorizedClient` annotation is handled by `OAuth2AuthorizedClientArgumentResolver`, which directly uses an <<oauth2Client-authorized-manager-provider, OAuth2AuthorizedClientManager>> and therefore inherits it's capabilities. The `@RegisteredOAuth2AuthorizedClient` annotation is handled by `OAuth2AuthorizedClientArgumentResolver`, which directly uses an <<oauth2Client-authorized-manager-provider, OAuth2AuthorizedClientManager>> and therefore inherits it's capabilities.
@ -1219,7 +1695,9 @@ It directly uses an <<oauth2Client-authorized-manager-provider, OAuth2Authorized
The following code shows an example of how to configure `WebClient` with OAuth 2.0 Client support: The following code shows an example of how to configure `WebClient` with OAuth 2.0 Client support:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@Bean @Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) { WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
@ -1231,6 +1709,18 @@ WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun webClient(authorizedClientManager: OAuth2AuthorizedClientManager?): WebClient {
val oauth2Client = ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build()
}
----
====
==== Providing the Authorized Client ==== Providing the Authorized Client
@ -1238,7 +1728,9 @@ The `ServletOAuth2AuthorizedClientExchangeFilterFunction` determines the client
The following code shows how to set an `OAuth2AuthorizedClient` as a request attribute: The following code shows how to set an `OAuth2AuthorizedClient` as a request attribute:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@GetMapping("/") @GetMapping("/")
public String index(@RegisteredOAuth2AuthorizedClient("okta") OAuth2AuthorizedClient authorizedClient) { public String index(@RegisteredOAuth2AuthorizedClient("okta") OAuth2AuthorizedClient authorizedClient) {
@ -1257,11 +1749,35 @@ public String index(@RegisteredOAuth2AuthorizedClient("okta") OAuth2AuthorizedCl
return "index"; return "index";
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@GetMapping("/")
fun index(@RegisteredOAuth2AuthorizedClient("okta") authorizedClient: OAuth2AuthorizedClient): String {
val resourceUri: String = ...
val body: String = webClient
.get()
.uri(resourceUri)
.attributes(oauth2AuthorizedClient(authorizedClient)) <1>
.retrieve()
.bodyToMono()
.block()
...
return "index"
}
----
====
<1> `oauth2AuthorizedClient()` is a `static` method in `ServletOAuth2AuthorizedClientExchangeFilterFunction`. <1> `oauth2AuthorizedClient()` is a `static` method in `ServletOAuth2AuthorizedClientExchangeFilterFunction`.
The following code shows how to set the `ClientRegistration.getRegistrationId()` as a request attribute: The following code shows how to set the `ClientRegistration.getRegistrationId()` as a request attribute:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@GetMapping("/") @GetMapping("/")
public String index() { public String index() {
@ -1280,6 +1796,28 @@ public String index() {
return "index"; return "index";
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@GetMapping("/")
fun index(): String {
val resourceUri: String = ...
val body: String = webClient
.get()
.uri(resourceUri)
.attributes(clientRegistrationId("okta")) <1>
.retrieve()
.bodyToMono()
.block()
...
return "index"
}
----
====
<1> `clientRegistrationId()` is a `static` method in `ServletOAuth2AuthorizedClientExchangeFilterFunction`. <1> `clientRegistrationId()` is a `static` method in `ServletOAuth2AuthorizedClientExchangeFilterFunction`.
@ -1291,7 +1829,9 @@ If `setDefaultOAuth2AuthorizedClient(true)` is configured and the user has authe
The following code shows the specific configuration: The following code shows the specific configuration:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@Bean @Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) { WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
@ -1304,6 +1844,20 @@ WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun webClient(authorizedClientManager: OAuth2AuthorizedClientManager?): WebClient {
val oauth2Client = ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
oauth2Client.setDefaultOAuth2AuthorizedClient(true)
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build()
}
----
====
[WARNING] [WARNING]
It is recommended to be cautious with this feature since all HTTP requests will receive the access token. It is recommended to be cautious with this feature since all HTTP requests will receive the access token.
@ -1311,7 +1865,9 @@ Alternatively, if `setDefaultClientRegistrationId("okta")` is configured with a
The following code shows the specific configuration: The following code shows the specific configuration:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@Bean @Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) { WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
@ -1324,5 +1880,19 @@ WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun webClient(authorizedClientManager: OAuth2AuthorizedClientManager?): WebClient {
val oauth2Client = ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
oauth2Client.setDefaultClientRegistrationId("okta")
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build()
}
----
====
[WARNING] [WARNING]
It is recommended to be cautious with this feature since all HTTP requests will receive the access token. It is recommended to be cautious with this feature since all HTTP requests will receive the access token.