Remove Resource Owner Password Credentials grant
Closes gh-17446
This commit is contained in:
@@ -493,7 +493,7 @@ authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
which is an implementation of a `ReactiveOAuth2AuthorizedClientProvider` for the Refresh Token grant.
|
||||
====
|
||||
|
||||
The `OAuth2RefreshToken` may optionally be returned in the Access Token Response for the `authorization_code` and `password` grant types.
|
||||
The `OAuth2RefreshToken` may optionally be returned in the Access Token Response for the `authorization_code` grant type.
|
||||
If the `OAuth2AuthorizedClient.getRefreshToken()` is available and the `OAuth2AuthorizedClient.getAccessToken()` is expired, it will automatically be refreshed by the `RefreshTokenReactiveOAuth2AuthorizedClientProvider`.
|
||||
|
||||
[[oauth2-client-client-credentials]]
|
||||
@@ -698,264 +698,8 @@ class OAuth2ClientController {
|
||||
If not provided, it will be obtained from the https://projectreactor.io/docs/core/release/reference/#context[Reactor's Context] via the key `ServerWebExchange.class`.
|
||||
====
|
||||
|
||||
[[oauth2-client-password]]
|
||||
== [[oauth2Client-password-grant]]Resource Owner Password Credentials
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Please refer to the OAuth 2.0 Authorization Framework for further details on the https://tools.ietf.org/html/rfc6749#section-1.3.3[Resource Owner Password Credentials] grant.
|
||||
====
|
||||
|
||||
[[oauth2-client-password-access-token]]
|
||||
=== Requesting an Access Token
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Please refer to the https://tools.ietf.org/html/rfc6749#section-4.3.2[Access Token Request/Response] protocol flow for the Resource Owner Password Credentials grant.
|
||||
====
|
||||
|
||||
The default implementation of `ReactiveOAuth2AccessTokenResponseClient` for the Resource Owner Password Credentials grant is `WebClientReactivePasswordTokenResponseClient`, which uses a `WebClient` when requesting an access token at the Authorization Server’s Token Endpoint.
|
||||
|
||||
[CAUTION]
|
||||
====
|
||||
The `WebClientReactivePasswordTokenResponseClient` class and support for the Resource Owner Password Credentials grant are deprecated.
|
||||
This section will be removed in Spring Security 7.
|
||||
====
|
||||
|
||||
:section-id: password
|
||||
:grant-type: Password
|
||||
:class-name: WebClientReactivePasswordTokenResponseClient
|
||||
:grant-request: OAuth2PasswordGrantRequest
|
||||
:leveloffset: +1
|
||||
include::partial$reactive/oauth2/client/web-client-access-token-response-client.adoc[]
|
||||
|
||||
:leveloffset: -1
|
||||
|
||||
[[oauth2-client-password-authorized-client-provider-builder]]
|
||||
=== Customize using the Builder
|
||||
|
||||
Whether you customize `WebClientReactivePasswordTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you can configure it using the `ReactiveOAuth2AuthorizedClientProviderBuilder` (as an alternative to <<oauth2-client-password-access-token-response-client-bean,publishing a bean>>) as follows:
|
||||
|
||||
.Access Token Response Configuration via Builder
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
// Customize
|
||||
ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient = ...
|
||||
|
||||
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password((configurer) -> configurer.accessTokenResponseClient(passwordTokenResponseClient))
|
||||
.refreshToken()
|
||||
.build();
|
||||
|
||||
// ...
|
||||
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val passwordTokenResponseClient: ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> = ...
|
||||
|
||||
val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password { it.accessTokenResponseClient(passwordTokenResponseClient) }
|
||||
.refreshToken()
|
||||
.build()
|
||||
|
||||
// ...
|
||||
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
`ReactiveOAuth2AuthorizedClientProviderBuilder.builder().password()` configures a `PasswordReactiveOAuth2AuthorizedClientProvider`,
|
||||
which is an implementation of a `ReactiveOAuth2AuthorizedClientProvider` for the Resource Owner Password Credentials grant.
|
||||
====
|
||||
|
||||
[[oauth2-client-password-authorized-client-manager]]
|
||||
=== Using the Access Token
|
||||
|
||||
Given the following Spring Boot properties for an OAuth 2.0 Client registration:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
registration:
|
||||
okta:
|
||||
client-id: okta-client-id
|
||||
client-secret: okta-client-secret
|
||||
authorization-grant-type: password
|
||||
scope: read, write
|
||||
provider:
|
||||
okta:
|
||||
token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token
|
||||
----
|
||||
|
||||
...and the `ReactiveOAuth2AuthorizedClientManager` `@Bean`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
|
||||
ReactiveClientRegistrationRepository clientRegistrationRepository,
|
||||
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
|
||||
|
||||
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password()
|
||||
.refreshToken()
|
||||
.build();
|
||||
|
||||
DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
|
||||
new DefaultReactiveOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository);
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
|
||||
// Assuming the `username` and `password` are supplied as `ServerHttpRequest` parameters,
|
||||
// map the `ServerHttpRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
|
||||
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());
|
||||
|
||||
return authorizedClientManager;
|
||||
}
|
||||
|
||||
private Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttributesMapper() {
|
||||
return authorizeRequest -> {
|
||||
Map<String, Object> contextAttributes = Collections.emptyMap();
|
||||
ServerWebExchange exchange = authorizeRequest.getAttribute(ServerWebExchange.class.getName());
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
String username = request.getQueryParams().getFirst(OAuth2ParameterNames.USERNAME);
|
||||
String password = request.getQueryParams().getFirst(OAuth2ParameterNames.PASSWORD);
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
contextAttributes = new HashMap<>();
|
||||
|
||||
// `PasswordReactiveOAuth2AuthorizedClientProvider` requires both attributes
|
||||
contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
|
||||
contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
|
||||
}
|
||||
return Mono.just(contextAttributes);
|
||||
};
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun authorizedClientManager(
|
||||
clientRegistrationRepository: ReactiveClientRegistrationRepository,
|
||||
authorizedClientRepository: ServerOAuth2AuthorizedClientRepository): ReactiveOAuth2AuthorizedClientManager {
|
||||
val authorizedClientProvider: ReactiveOAuth2AuthorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password()
|
||||
.refreshToken()
|
||||
.build()
|
||||
val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository)
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
|
||||
// Assuming the `username` and `password` are supplied as `ServerHttpRequest` parameters,
|
||||
// map the `ServerHttpRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
|
||||
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper())
|
||||
return authorizedClientManager
|
||||
}
|
||||
|
||||
private fun contextAttributesMapper(): Function<OAuth2AuthorizeRequest, Mono<MutableMap<String, Any>>> {
|
||||
return Function { authorizeRequest ->
|
||||
var contextAttributes: MutableMap<String, Any> = mutableMapOf()
|
||||
val exchange: ServerWebExchange = authorizeRequest.getAttribute(ServerWebExchange::class.java.name)!!
|
||||
val request: ServerHttpRequest = exchange.request
|
||||
val username: String? = request.queryParams.getFirst(OAuth2ParameterNames.USERNAME)
|
||||
val password: String? = request.queryParams.getFirst(OAuth2ParameterNames.PASSWORD)
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
contextAttributes = hashMapOf()
|
||||
|
||||
// `PasswordReactiveOAuth2AuthorizedClientProvider` requires both attributes
|
||||
contextAttributes[OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME] = username!!
|
||||
contextAttributes[OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME] = password!!
|
||||
}
|
||||
Mono.just(contextAttributes)
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
You may obtain the `OAuth2AccessToken` as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
public class OAuth2ClientController {
|
||||
|
||||
@Autowired
|
||||
private ReactiveOAuth2AuthorizedClientManager authorizedClientManager;
|
||||
|
||||
@GetMapping("/")
|
||||
public Mono<String> index(Authentication authentication, ServerWebExchange exchange) {
|
||||
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
|
||||
.principal(authentication)
|
||||
.attribute(ServerWebExchange.class.getName(), exchange)
|
||||
.build();
|
||||
|
||||
return this.authorizedClientManager.authorize(authorizeRequest)
|
||||
.map(OAuth2AuthorizedClient::getAccessToken)
|
||||
// ...
|
||||
.thenReturn("index");
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Controller
|
||||
class OAuth2ClientController {
|
||||
@Autowired
|
||||
private lateinit var authorizedClientManager: ReactiveOAuth2AuthorizedClientManager
|
||||
|
||||
@GetMapping("/")
|
||||
fun index(authentication: Authentication, exchange: ServerWebExchange): Mono<String> {
|
||||
val authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
|
||||
.principal(authentication)
|
||||
.attribute(ServerWebExchange::class.java.name, exchange)
|
||||
.build()
|
||||
|
||||
return authorizedClientManager.authorize(authorizeRequest)
|
||||
.map { it.accessToken }
|
||||
// ...
|
||||
.thenReturn("index")
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
`ServerWebExchange` is an OPTIONAL attribute.
|
||||
If not provided, it will be obtained from the https://projectreactor.io/docs/core/release/reference/#context[Reactor's Context] via the key `ServerWebExchange.class`.
|
||||
====
|
||||
|
||||
[[oauth2-client-jwt-bearer]]
|
||||
== [[oauth2Client-jwt-bearer-grant]]JWT Bearer
|
||||
== JWT Bearer
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
||||
@@ -56,7 +56,6 @@ It directly uses an xref:reactive/oauth2/client/core.adoc#oauth2Client-authorize
|
||||
* 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:
|
||||
|
||||
@@ -51,7 +51,7 @@ public final class ClientRegistration {
|
||||
<4> `clientAuthenticationMethod`: The method used to authenticate the Client with the Provider.
|
||||
The supported values are *client_secret_basic*, *client_secret_post*, *private_key_jwt*, *client_secret_jwt* and *none* https://tools.ietf.org/html/rfc6749#section-2.1[(public clients)].
|
||||
<5> `authorizationGrantType`: The OAuth 2.0 Authorization Framework defines four https://tools.ietf.org/html/rfc6749#section-1.3[Authorization Grant] types.
|
||||
The supported values are `authorization_code`, `client_credentials`, `password`, as well as, extension grant type `urn:ietf:params:oauth:grant-type:jwt-bearer`.
|
||||
The supported values are `authorization_code`, `client_credentials`, as well as, extension grant type `urn:ietf:params:oauth:grant-type:jwt-bearer`.
|
||||
<6> `redirectUri`: The client's registered redirect URI that the _Authorization Server_ redirects the end-user's user-agent
|
||||
to after the end-user has authenticated and authorized access to the client.
|
||||
<7> `scopes`: The scope(s) requested by the client during the Authorization Request flow, such as openid, email, or profile.
|
||||
@@ -255,7 +255,7 @@ Implementations will typically implement an authorization grant type, eg. `autho
|
||||
The default implementation of `ReactiveOAuth2AuthorizedClientManager` is `DefaultReactiveOAuth2AuthorizedClientManager`, which is associated with a `ReactiveOAuth2AuthorizedClientProvider` that may support multiple authorization grant types using a delegation-based composite.
|
||||
The `ReactiveOAuth2AuthorizedClientProviderBuilder` may be used to configure and build the delegation-based composite.
|
||||
|
||||
The following code shows an example of how to configure and build a `ReactiveOAuth2AuthorizedClientProvider` 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 a `ReactiveOAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token` and `client_credentials` authorization grant types:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -273,7 +273,6 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.build();
|
||||
|
||||
DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
|
||||
@@ -297,7 +296,6 @@ fun authorizedClientManager(
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.build()
|
||||
val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository)
|
||||
@@ -312,7 +310,7 @@ In the case of a re-authorization failure, eg. a refresh token is no longer vali
|
||||
The default behaviour may be customized via `setAuthorizationSuccessHandler(ReactiveOAuth2AuthorizationSuccessHandler)` and `setAuthorizationFailureHandler(ReactiveOAuth2AuthorizationFailureHandler)`.
|
||||
|
||||
The `DefaultReactiveOAuth2AuthorizedClientManager` is also associated with a `contextAttributesMapper` of type `Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>>`, which is responsible for mapping attribute(s) from the `OAuth2AuthorizeRequest` to a `Map` of attributes to be associated to the `OAuth2AuthorizationContext`.
|
||||
This can be useful when you need to supply a `ReactiveOAuth2AuthorizedClientProvider` with required (supported) attribute(s), eg. the `PasswordReactiveOAuth2AuthorizedClientProvider` requires the resource owner's `username` and `password` to be available in `OAuth2AuthorizationContext.getAttributes()`.
|
||||
This can be useful when you need to supply a `ReactiveOAuth2AuthorizedClientProvider` with required (supported) attribute(s).
|
||||
|
||||
The following code shows an example of the `contextAttributesMapper`:
|
||||
|
||||
@@ -329,7 +327,7 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
|
||||
|
||||
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password()
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.build();
|
||||
|
||||
@@ -338,7 +336,7 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository);
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
|
||||
// Assuming the `username` and `password` are supplied as `ServerHttpRequest` parameters,
|
||||
// Assuming the attributes are supplied as `ServerHttpRequest` parameters,
|
||||
// map the `ServerHttpRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
|
||||
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());
|
||||
|
||||
@@ -350,14 +348,12 @@ private Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttri
|
||||
Map<String, Object> contextAttributes = Collections.emptyMap();
|
||||
ServerWebExchange exchange = authorizeRequest.getAttribute(ServerWebExchange.class.getName());
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
String username = request.getQueryParams().getFirst(OAuth2ParameterNames.USERNAME);
|
||||
String password = request.getQueryParams().getFirst(OAuth2ParameterNames.PASSWORD);
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
String param1 = request.getQueryParams().getFirst("param1");
|
||||
String param2 = request.getQueryParams().getFirst("param2");
|
||||
if (StringUtils.hasText(param1) && StringUtils.hasText(param2)) {
|
||||
contextAttributes = new HashMap<>();
|
||||
|
||||
// `PasswordReactiveOAuth2AuthorizedClientProvider` requires both attributes
|
||||
contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
|
||||
contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
|
||||
contextAttributes.put("param1", param1);
|
||||
contextAttributes.put("param2", param2);
|
||||
}
|
||||
return Mono.just(contextAttributes);
|
||||
};
|
||||
@@ -373,15 +369,15 @@ fun authorizedClientManager(
|
||||
clientRegistrationRepository: ReactiveClientRegistrationRepository,
|
||||
authorizedClientRepository: ServerOAuth2AuthorizedClientRepository): ReactiveOAuth2AuthorizedClientManager {
|
||||
val authorizedClientProvider: ReactiveOAuth2AuthorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password()
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.build()
|
||||
val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository)
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
|
||||
// Assuming the `username` and `password` are supplied as `ServerHttpRequest` parameters,
|
||||
// map the `ServerHttpRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
|
||||
// Assuming the attributes are supplied as `ServerHttpRequest` parameters,
|
||||
// map the `ServerHttpRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
|
||||
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper())
|
||||
return authorizedClientManager
|
||||
}
|
||||
@@ -391,14 +387,12 @@ private fun contextAttributesMapper(): Function<OAuth2AuthorizeRequest, Mono<Mut
|
||||
var contextAttributes: MutableMap<String, Any> = mutableMapOf()
|
||||
val exchange: ServerWebExchange = authorizeRequest.getAttribute(ServerWebExchange::class.java.name)!!
|
||||
val request: ServerHttpRequest = exchange.request
|
||||
val username: String? = request.queryParams.getFirst(OAuth2ParameterNames.USERNAME)
|
||||
val password: String? = request.queryParams.getFirst(OAuth2ParameterNames.PASSWORD)
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
val param1: String? = request.queryParams.getFirst("param1")
|
||||
val param2: String? = request.queryParams.getFirst("param2")
|
||||
if (StringUtils.hasText(param1) && StringUtils.hasText(param2)) {
|
||||
contextAttributes = hashMapOf()
|
||||
|
||||
// `PasswordReactiveOAuth2AuthorizedClientProvider` requires both attributes
|
||||
contextAttributes[OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME] = username!!
|
||||
contextAttributes[OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME] = password!!
|
||||
contextAttributes["param1"] = param1!!
|
||||
contextAttributes["param2"] = param2!!
|
||||
}
|
||||
Mono.just(contextAttributes)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ At a high-level, the core features available are:
|
||||
* xref:reactive/oauth2/client/authorization-grants.adoc#oauth2-client-authorization-code[Authorization Code]
|
||||
* xref:reactive/oauth2/client/authorization-grants.adoc#oauth2-client-refresh-token[Refresh Token]
|
||||
* xref:reactive/oauth2/client/authorization-grants.adoc#oauth2-client-client-credentials[Client Credentials]
|
||||
* xref:reactive/oauth2/client/authorization-grants.adoc#oauth2-client-password[Resource Owner Password Credentials]
|
||||
* xref:reactive/oauth2/client/authorization-grants.adoc#oauth2-client-jwt-bearer[JWT Bearer]
|
||||
* xref:reactive/oauth2/client/authorization-grants.adoc#oauth2-client-token-exchange[Token Exchange]
|
||||
|
||||
@@ -81,7 +80,7 @@ class OAuth2ClientSecurityConfig {
|
||||
|
||||
The `ReactiveOAuth2AuthorizedClientManager` is responsible for managing the authorization (or re-authorization) of an OAuth 2.0 Client, in collaboration with one or more `ReactiveOAuth2AuthorizedClientProvider`(s).
|
||||
|
||||
The following code shows an example of how to register a `ReactiveOAuth2AuthorizedClientManager` `@Bean` and associate it with a `ReactiveOAuth2AuthorizedClientProvider` 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 a `ReactiveOAuth2AuthorizedClientManager` `@Bean` and associate it with a `ReactiveOAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token` and `client_credentials` authorization grant types:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -99,7 +98,6 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.build();
|
||||
|
||||
DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
|
||||
@@ -123,7 +121,6 @@ fun authorizedClientManager(
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.build()
|
||||
val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository)
|
||||
|
||||
@@ -953,7 +953,6 @@ public class SecurityConfig {
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.provider(new JwtBearerReactiveOAuth2AuthorizedClientProvider())
|
||||
.build();
|
||||
|
||||
@@ -984,7 +983,6 @@ class SecurityConfig {
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.provider(JwtBearerReactiveOAuth2AuthorizedClientProvider())
|
||||
.build()
|
||||
|
||||
@@ -1273,7 +1271,6 @@ Spring Security automatically resolves the following generic types of `ReactiveO
|
||||
* `OAuth2AuthorizationCodeGrantRequest` (see `WebClientReactiveAuthorizationCodeTokenResponseClient`)
|
||||
* `OAuth2RefreshTokenGrantRequest` (see `WebClientReactiveRefreshTokenTokenResponseClient`)
|
||||
* `OAuth2ClientCredentialsGrantRequest` (see `WebClientReactiveClientCredentialsTokenResponseClient`)
|
||||
* `OAuth2PasswordGrantRequest` (see `WebClientReactivePasswordTokenResponseClient`)
|
||||
* `JwtBearerGrantRequest` (see `WebClientReactiveJwtBearerTokenResponseClient`)
|
||||
* `TokenExchangeGrantRequest` (see `WebClientReactiveTokenExchangeTokenResponseClient`)
|
||||
|
||||
@@ -1334,15 +1331,6 @@ public class SecurityConfig {
|
||||
return accessTokenResponseClient;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordAccessTokenResponseClient() {
|
||||
WebClientReactivePasswordTokenResponseClient accessTokenResponseClient =
|
||||
new WebClientReactivePasswordTokenResponseClient();
|
||||
accessTokenResponseClient.setWebClient(webClient());
|
||||
|
||||
return accessTokenResponseClient;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ReactiveOAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerAccessTokenResponseClient() {
|
||||
WebClientReactiveJwtBearerTokenResponseClient accessTokenResponseClient =
|
||||
@@ -1400,14 +1388,6 @@ class SecurityConfig {
|
||||
return accessTokenResponseClient
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun passwordAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
|
||||
val accessTokenResponseClient = WebClientReactivePasswordTokenResponseClient()
|
||||
accessTokenResponseClient.setWebClient(webClient())
|
||||
|
||||
return accessTokenResponseClient
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun jwtBearerAccessTokenResponseClient(): ReactiveOAuth2AccessTokenResponseClient<JwtBearerGrantRequest> {
|
||||
val accessTokenResponseClient = WebClientReactiveJwtBearerTokenResponseClient()
|
||||
@@ -1477,10 +1457,6 @@ public class SecurityConfig {
|
||||
new WebClientReactiveClientCredentialsTokenResponseClient();
|
||||
clientCredentialsAccessTokenResponseClient.setWebClient(webClient());
|
||||
|
||||
WebClientReactivePasswordTokenResponseClient passwordAccessTokenResponseClient =
|
||||
new WebClientReactivePasswordTokenResponseClient();
|
||||
passwordAccessTokenResponseClient.setWebClient(webClient());
|
||||
|
||||
WebClientReactiveJwtBearerTokenResponseClient jwtBearerAccessTokenResponseClient =
|
||||
new WebClientReactiveJwtBearerTokenResponseClient();
|
||||
jwtBearerAccessTokenResponseClient.setWebClient(webClient());
|
||||
@@ -1506,9 +1482,6 @@ public class SecurityConfig {
|
||||
.clientCredentials((clientCredentials) -> clientCredentials
|
||||
.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient)
|
||||
)
|
||||
.password((password) -> password
|
||||
.accessTokenResponseClient(passwordAccessTokenResponseClient)
|
||||
)
|
||||
.provider(jwtBearerAuthorizedClientProvider)
|
||||
.provider(tokenExchangeAuthorizedClientProvider)
|
||||
.build();
|
||||
@@ -1557,9 +1530,6 @@ class SecurityConfig {
|
||||
val clientCredentialsAccessTokenResponseClient = WebClientReactiveClientCredentialsTokenResponseClient()
|
||||
clientCredentialsAccessTokenResponseClient.setWebClient(webClient())
|
||||
|
||||
val passwordAccessTokenResponseClient = WebClientReactivePasswordTokenResponseClient()
|
||||
passwordAccessTokenResponseClient.setWebClient(webClient())
|
||||
|
||||
val jwtBearerAccessTokenResponseClient = WebClientReactiveJwtBearerTokenResponseClient()
|
||||
jwtBearerAccessTokenResponseClient.setWebClient(webClient())
|
||||
|
||||
@@ -1580,9 +1550,6 @@ class SecurityConfig {
|
||||
.clientCredentials { clientCredentials ->
|
||||
clientCredentials.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient)
|
||||
}
|
||||
.password { password ->
|
||||
password.accessTokenResponseClient(passwordAccessTokenResponseClient)
|
||||
}
|
||||
.provider(jwtBearerAuthorizedClientProvider)
|
||||
.provider(tokenExchangeAuthorizedClientProvider)
|
||||
.build()
|
||||
|
||||
@@ -546,7 +546,7 @@ authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
which is an implementation of an `OAuth2AuthorizedClientProvider` for the Refresh Token grant.
|
||||
====
|
||||
|
||||
The `OAuth2RefreshToken` can optionally be returned in the Access Token Response for the `authorization_code` and `password` grant types.
|
||||
The `OAuth2RefreshToken` can optionally be returned in the Access Token Response for the `authorization_code` grant type.
|
||||
If the `OAuth2AuthorizedClient.getRefreshToken()` is available and the `OAuth2AuthorizedClient.getAccessToken()` is expired, it is automatically refreshed by the `RefreshTokenOAuth2AuthorizedClientProvider`.
|
||||
|
||||
[[oauth2-client-client-credentials]]
|
||||
@@ -778,330 +778,8 @@ class OAuth2ClientController {
|
||||
If not provided, they default to `ServletRequestAttributes` by using `RequestContextHolder.getRequestAttributes()`.
|
||||
====
|
||||
|
||||
[[oauth2-client-password]]
|
||||
== [[oauth2Client-password-grant]]Resource Owner Password Credentials
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
See the OAuth 2.0 Authorization Framework for further details on the https://tools.ietf.org/html/rfc6749#section-1.3.3[Resource Owner Password Credentials] grant.
|
||||
====
|
||||
|
||||
[[oauth2-client-password-access-token]]
|
||||
=== Requesting an Access Token
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
See the https://tools.ietf.org/html/rfc6749#section-4.3.2[Access Token Request/Response] protocol flow for the Resource Owner Password Credentials grant.
|
||||
====
|
||||
|
||||
The default implementation of `OAuth2AccessTokenResponseClient` for the Resource Owner Password Credentials grant is `DefaultPasswordTokenResponseClient`, which uses a `RestOperations` when requesting an access token at the Authorization Server’s Token Endpoint.
|
||||
|
||||
[CAUTION]
|
||||
====
|
||||
The `DefaultPasswordTokenResponseClient` class and support for the Resource Owner Password Credentials grant are deprecated.
|
||||
This section will be removed in Spring Security 7.
|
||||
====
|
||||
|
||||
The `DefaultPasswordTokenResponseClient` is flexible, as it lets you customize the pre-processing of the Token Request or post-handling of the Token Response.
|
||||
|
||||
[[oauth2-client-password-access-token-request]]
|
||||
=== Customizing the Access Token Request
|
||||
|
||||
If you need to customize the pre-processing of the Token Request, you can provide `DefaultPasswordTokenResponseClient.setRequestEntityConverter()` with a custom `Converter<OAuth2PasswordGrantRequest, RequestEntity<?>>`.
|
||||
The default implementation (`OAuth2PasswordGrantRequestEntityConverter`) builds a `RequestEntity` representation of a standard https://tools.ietf.org/html/rfc6749#section-4.3.2[OAuth 2.0 Access Token Request].
|
||||
However, providing a custom `Converter` would let you extend the standard Token Request and add custom parameter(s).
|
||||
|
||||
To customize only the parameters of the request, you can provide `OAuth2PasswordGrantRequestEntityConverter.setParametersConverter()` with a custom `Converter<OAuth2PasswordGrantRequest, MultiValueMap<String, String>>` to completely override the parameters sent with the request. This is often simpler than constructing a `RequestEntity` directly.
|
||||
|
||||
[TIP]
|
||||
====
|
||||
If you prefer to only add additional parameters, you can provide `OAuth2PasswordGrantRequestEntityConverter.addParametersConverter()` with a custom `Converter<OAuth2PasswordGrantRequest, MultiValueMap<String, String>>` which constructs an aggregate `Converter`.
|
||||
====
|
||||
|
||||
[IMPORTANT]
|
||||
====
|
||||
The custom `Converter` must return a valid `RequestEntity` representation of an OAuth 2.0 Access Token Request that is understood by the intended OAuth 2.0 Provider.
|
||||
====
|
||||
|
||||
[[oauth2-client-password-access-token-response]]
|
||||
=== Customizing the Access Token Response
|
||||
|
||||
On the other end, if you need to customize the post-handling of the Token Response, you need to provide `DefaultPasswordTokenResponseClient.setRestOperations()` with a custom configured `RestOperations`.
|
||||
The default `RestOperations` is configured as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
RestTemplate restTemplate = new RestTemplate(Arrays.asList(
|
||||
new FormHttpMessageConverter(),
|
||||
new OAuth2AccessTokenResponseHttpMessageConverter()));
|
||||
|
||||
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 is used when sending the OAuth 2.0 Access Token Request.
|
||||
====
|
||||
|
||||
`OAuth2AccessTokenResponseHttpMessageConverter` is a `HttpMessageConverter` for an OAuth 2.0 Access Token Response.
|
||||
You can provide `OAuth2AccessTokenResponseHttpMessageConverter.setTokenResponseConverter()` with a custom `Converter<Map<String, String>, OAuth2AccessTokenResponse>` that is used to convert the OAuth 2.0 Access Token Response parameters to an `OAuth2AccessTokenResponse`.
|
||||
|
||||
`OAuth2ErrorResponseErrorHandler` is a `ResponseErrorHandler` that can handle an OAuth 2.0 Error, such as `400 Bad Request`.
|
||||
It uses an `OAuth2ErrorHttpMessageConverter` to convert the OAuth 2.0 Error parameters to an `OAuth2Error`.
|
||||
|
||||
[[oauth2-client-password-authorized-client-provider-builder]]
|
||||
=== Customize using the Builder
|
||||
|
||||
Whether you customize `DefaultPasswordTokenResponseClient` or provide your own implementation of `OAuth2AccessTokenResponseClient`, you need to configure it as follows:
|
||||
|
||||
.Access Token Response Configuration via Builder
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
// Customize
|
||||
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient = ...
|
||||
|
||||
OAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||
OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password((configurer) -> configurer.accessTokenResponseClient(passwordTokenResponseClient))
|
||||
.refreshToken()
|
||||
.build();
|
||||
|
||||
// ...
|
||||
|
||||
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]
|
||||
====
|
||||
`OAuth2AuthorizedClientProviderBuilder.builder().password()` configures a `PasswordOAuth2AuthorizedClientProvider`,
|
||||
which is an implementation of an `OAuth2AuthorizedClientProvider` for the Resource Owner Password Credentials grant.
|
||||
====
|
||||
|
||||
[[oauth2-client-password-authorized-client-manager]]
|
||||
=== Using the Access Token
|
||||
|
||||
Consider the following Spring Boot properties for an OAuth 2.0 Client registration:
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
registration:
|
||||
okta:
|
||||
client-id: okta-client-id
|
||||
client-secret: okta-client-secret
|
||||
authorization-grant-type: password
|
||||
scope: read, write
|
||||
provider:
|
||||
okta:
|
||||
token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token
|
||||
----
|
||||
|
||||
Further consider the `OAuth2AuthorizedClientManager` `@Bean`:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
public OAuth2AuthorizedClientManager authorizedClientManager(
|
||||
ClientRegistrationRepository clientRegistrationRepository,
|
||||
OAuth2AuthorizedClientRepository authorizedClientRepository) {
|
||||
|
||||
OAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||
OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password()
|
||||
.refreshToken()
|
||||
.build();
|
||||
|
||||
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
|
||||
new 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 Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesMapper() {
|
||||
return authorizeRequest -> {
|
||||
Map<String, Object> contextAttributes = Collections.emptyMap();
|
||||
HttpServletRequest servletRequest = authorizeRequest.getAttribute(HttpServletRequest.class.getName());
|
||||
String username = servletRequest.getParameter(OAuth2ParameterNames.USERNAME);
|
||||
String password = servletRequest.getParameter(OAuth2ParameterNames.PASSWORD);
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
contextAttributes = new HashMap<>();
|
||||
|
||||
// `PasswordOAuth2AuthorizedClientProvider` requires both attributes
|
||||
contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
|
||||
contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
|
||||
}
|
||||
return contextAttributes;
|
||||
};
|
||||
}
|
||||
----
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
Given the preceding properties and bean, you can obtain the `OAuth2AccessToken` as follows:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
public class OAuth2ClientController {
|
||||
|
||||
@Autowired
|
||||
private OAuth2AuthorizedClientManager authorizedClientManager;
|
||||
|
||||
@GetMapping("/")
|
||||
public String index(Authentication authentication,
|
||||
HttpServletRequest servletRequest,
|
||||
HttpServletResponse servletResponse) {
|
||||
|
||||
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
|
||||
.principal(authentication)
|
||||
.attributes(attrs -> {
|
||||
attrs.put(HttpServletRequest.class.getName(), servletRequest);
|
||||
attrs.put(HttpServletResponse.class.getName(), servletResponse);
|
||||
})
|
||||
.build();
|
||||
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
|
||||
|
||||
OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
|
||||
|
||||
// ...
|
||||
|
||||
return "index";
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
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]
|
||||
====
|
||||
`HttpServletRequest` and `HttpServletResponse` are both OPTIONAL attributes.
|
||||
If not provided, they default to `ServletRequestAttributes` using `RequestContextHolder.getRequestAttributes()`.
|
||||
====
|
||||
|
||||
[[oauth2-client-jwt-bearer]]
|
||||
== [[oauth2Client-jwt-bearer-grant]]JWT Bearer
|
||||
== JWT Bearer
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
||||
@@ -60,7 +60,6 @@ The interceptor directly uses an `OAuth2AuthorizedClientManager` and therefore i
|
||||
* Performs an OAuth 2.0 Access Token request to obtain `OAuth2AccessToken` 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
|
||||
** Additional grant types are supported by xref:servlet/oauth2/index.adoc#oauth2-client-enable-extension-grant-type[enabling extension grant types]
|
||||
* If an existing `OAuth2AccessToken` is expired, it is refreshed (or renewed)
|
||||
|
||||
@@ -511,7 +510,6 @@ It directly uses an xref:servlet/oauth2/client/core.adoc#oauth2Client-authorized
|
||||
* An `OAuth2AccessToken` is 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 is 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:
|
||||
|
||||
@@ -52,7 +52,7 @@ public final class ClientRegistration {
|
||||
<4> `clientAuthenticationMethod`: The method used to authenticate the Client with the Provider.
|
||||
The supported values are *client_secret_basic*, *client_secret_post*, *private_key_jwt*, *client_secret_jwt* and *none* https://tools.ietf.org/html/rfc6749#section-2.1[(public clients)].
|
||||
<5> `authorizationGrantType`: The OAuth 2.0 Authorization Framework defines four https://tools.ietf.org/html/rfc6749#section-1.3[Authorization Grant] types.
|
||||
The supported values are `authorization_code`, `client_credentials`, `password`, as well as, extension grant type `urn:ietf:params:oauth:grant-type:jwt-bearer`.
|
||||
The supported values are `authorization_code`, `client_credentials`, as well as, extension grant type `urn:ietf:params:oauth:grant-type:jwt-bearer`.
|
||||
<6> `redirectUri`: The client's registered redirect URI that the _Authorization Server_ redirects the end-user's user-agent
|
||||
to after the end-user has authenticated and authorized access to the client.
|
||||
<7> `scopes`: The scope(s) requested by the client during the Authorization Request flow, such as openid, email, or profile.
|
||||
@@ -268,7 +268,7 @@ Implementations typically implement an authorization grant type, such as `author
|
||||
The default implementation of `OAuth2AuthorizedClientManager` is `DefaultOAuth2AuthorizedClientManager`, which is associated with an `OAuth2AuthorizedClientProvider` that may support multiple authorization grant types using a delegation-based composite.
|
||||
You can use `OAuth2AuthorizedClientProviderBuilder` to configure and build the delegation-based composite.
|
||||
|
||||
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` and `client_credentials` authorization grant types:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -286,7 +286,6 @@ public OAuth2AuthorizedClientManager authorizedClientManager(
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.build();
|
||||
|
||||
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
|
||||
@@ -310,7 +309,6 @@ fun authorizedClientManager(
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.build()
|
||||
val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository)
|
||||
@@ -325,7 +323,7 @@ In the case of a re-authorization failure (for example, a refresh token is no lo
|
||||
You can customize the default behavior through `setAuthorizationSuccessHandler(OAuth2AuthorizationSuccessHandler)` and `setAuthorizationFailureHandler(OAuth2AuthorizationFailureHandler)`.
|
||||
|
||||
The `DefaultOAuth2AuthorizedClientManager` is also associated with a `contextAttributesMapper` of type `Function<OAuth2AuthorizeRequest, Map<String, Object>>`, which is responsible for mapping attribute(s) from the `OAuth2AuthorizeRequest` to a `Map` of attributes to be associated to the `OAuth2AuthorizationContext`.
|
||||
This can be useful when you need to supply an `OAuth2AuthorizedClientProvider` with required (supported) attribute(s), eg. the `PasswordOAuth2AuthorizedClientProvider` requires the resource owner's `username` and `password` to be available in `OAuth2AuthorizationContext.getAttributes()`.
|
||||
This can be useful when you need to supply an `OAuth2AuthorizedClientProvider` with required (supported) attribute(s).
|
||||
|
||||
The following code shows an example of the `contextAttributesMapper`:
|
||||
|
||||
@@ -342,7 +340,7 @@ public OAuth2AuthorizedClientManager authorizedClientManager(
|
||||
|
||||
OAuth2AuthorizedClientProvider authorizedClientProvider =
|
||||
OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password()
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.build();
|
||||
|
||||
@@ -351,7 +349,7 @@ public OAuth2AuthorizedClientManager authorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository);
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
|
||||
// Assuming the `username` and `password` are supplied as `HttpServletRequest` parameters,
|
||||
// Assuming the attributes are supplied as `HttpServletRequest` parameters,
|
||||
// map the `HttpServletRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
|
||||
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());
|
||||
|
||||
@@ -362,14 +360,12 @@ private Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesM
|
||||
return authorizeRequest -> {
|
||||
Map<String, Object> contextAttributes = Collections.emptyMap();
|
||||
HttpServletRequest servletRequest = authorizeRequest.getAttribute(HttpServletRequest.class.getName());
|
||||
String username = servletRequest.getParameter(OAuth2ParameterNames.USERNAME);
|
||||
String password = servletRequest.getParameter(OAuth2ParameterNames.PASSWORD);
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
String param1 = servletRequest.getParameter("param1");
|
||||
String param2 = servletRequest.getParameter("param2");
|
||||
if (StringUtils.hasText(param1) && StringUtils.hasText(param2)) {
|
||||
contextAttributes = new HashMap<>();
|
||||
|
||||
// `PasswordOAuth2AuthorizedClientProvider` requires both attributes
|
||||
contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
|
||||
contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
|
||||
contextAttributes.put("param1", param1);
|
||||
contextAttributes.put("param2", param2);
|
||||
}
|
||||
return contextAttributes;
|
||||
};
|
||||
@@ -385,15 +381,15 @@ fun authorizedClientManager(
|
||||
clientRegistrationRepository: ClientRegistrationRepository,
|
||||
authorizedClientRepository: OAuth2AuthorizedClientRepository): OAuth2AuthorizedClientManager {
|
||||
val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
|
||||
.password()
|
||||
.authorizationCode()
|
||||
.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()`
|
||||
// Assuming the attributes are supplied as `HttpServletRequest` parameters,
|
||||
// map the `HttpServletRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
|
||||
authorizedClientManager.setContextAttributesMapper(contextAttributesMapper())
|
||||
return authorizedClientManager
|
||||
}
|
||||
@@ -402,14 +398,12 @@ private fun contextAttributesMapper(): Function<OAuth2AuthorizeRequest, MutableM
|
||||
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)) {
|
||||
val param1: String = servletRequest.getParameter("param1")
|
||||
val param2: String = servletRequest.getParameter("param2")
|
||||
if (StringUtils.hasText(param1) && StringUtils.hasText(param2)) {
|
||||
contextAttributes = hashMapOf()
|
||||
|
||||
// `PasswordOAuth2AuthorizedClientProvider` requires both attributes
|
||||
contextAttributes[OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME] = username
|
||||
contextAttributes[OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME] = password
|
||||
contextAttributes["param1"] = param1
|
||||
contextAttributes["param2"] = param2
|
||||
}
|
||||
contextAttributes
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ At a high-level, the core features available are:
|
||||
* xref:servlet/oauth2/client/authorization-grants.adoc#oauth2-client-authorization-code[Authorization Code]
|
||||
* xref:servlet/oauth2/client/authorization-grants.adoc#oauth2-client-refresh-token[Refresh Token]
|
||||
* xref:servlet/oauth2/client/authorization-grants.adoc#oauth2-client-client-credentials[Client Credentials]
|
||||
* xref:servlet/oauth2/client/authorization-grants.adoc#oauth2-client-password[Resource Owner Password Credentials]
|
||||
* xref:servlet/oauth2/client/authorization-grants.adoc#oauth2-client-jwt-bearer[JWT Bearer]
|
||||
* xref:servlet/oauth2/client/authorization-grants.adoc#oauth2-client-token-exchange[Token Exchange]
|
||||
|
||||
@@ -104,7 +103,7 @@ The following code shows the complete configuration options available in the xre
|
||||
|
||||
The `OAuth2AuthorizedClientManager` is responsible for managing the authorization (or re-authorization) of an OAuth 2.0 Client, in collaboration with one or more `OAuth2AuthorizedClientProvider`(s).
|
||||
|
||||
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` and `client_credentials` authorization grant types:
|
||||
|
||||
[tabs]
|
||||
======
|
||||
@@ -122,7 +121,6 @@ public OAuth2AuthorizedClientManager authorizedClientManager(
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.build();
|
||||
|
||||
DefaultOAuth2AuthorizedClientManager authorizedClientManager =
|
||||
@@ -146,7 +144,6 @@ fun authorizedClientManager(
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.build()
|
||||
val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository)
|
||||
|
||||
@@ -1367,7 +1367,6 @@ public class SecurityConfig {
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.provider(new JwtBearerOAuth2AuthorizedClientProvider())
|
||||
.build();
|
||||
|
||||
@@ -1398,7 +1397,6 @@ class SecurityConfig {
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials()
|
||||
.password()
|
||||
.provider(JwtBearerOAuth2AuthorizedClientProvider())
|
||||
.build()
|
||||
|
||||
@@ -1699,7 +1697,6 @@ Spring Security automatically resolves the following generic types of `OAuth2Acc
|
||||
* `OAuth2AuthorizationCodeGrantRequest` (see `DefaultAuthorizationCodeTokenResponseClient`)
|
||||
* `OAuth2RefreshTokenGrantRequest` (see `DefaultRefreshTokenTokenResponseClient`)
|
||||
* `OAuth2ClientCredentialsGrantRequest` (see `DefaultClientCredentialsTokenResponseClient`)
|
||||
* `OAuth2PasswordGrantRequest` (see `DefaultPasswordTokenResponseClient`)
|
||||
* `JwtBearerGrantRequest` (see `DefaultJwtBearerTokenResponseClient`)
|
||||
* `TokenExchangeGrantRequest` (see `DefaultTokenExchangeTokenResponseClient`)
|
||||
|
||||
@@ -1760,15 +1757,6 @@ public class SecurityConfig {
|
||||
return accessTokenResponseClient;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordAccessTokenResponseClient() {
|
||||
DefaultPasswordTokenResponseClient accessTokenResponseClient =
|
||||
new DefaultPasswordTokenResponseClient();
|
||||
accessTokenResponseClient.setRestOperations(restTemplate());
|
||||
|
||||
return accessTokenResponseClient;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerAccessTokenResponseClient() {
|
||||
DefaultJwtBearerTokenResponseClient accessTokenResponseClient =
|
||||
@@ -1826,14 +1814,6 @@ class SecurityConfig {
|
||||
return accessTokenResponseClient
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun passwordAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
|
||||
val accessTokenResponseClient = DefaultPasswordTokenResponseClient()
|
||||
accessTokenResponseClient.setRestOperations(restTemplate())
|
||||
|
||||
return accessTokenResponseClient
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun jwtBearerAccessTokenResponseClient(): OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> {
|
||||
val accessTokenResponseClient = DefaultJwtBearerTokenResponseClient()
|
||||
@@ -1917,10 +1897,6 @@ public class SecurityConfig {
|
||||
new DefaultClientCredentialsTokenResponseClient();
|
||||
clientCredentialsAccessTokenResponseClient.setRestOperations(restTemplate());
|
||||
|
||||
DefaultPasswordTokenResponseClient passwordAccessTokenResponseClient =
|
||||
new DefaultPasswordTokenResponseClient();
|
||||
passwordAccessTokenResponseClient.setRestOperations(restTemplate());
|
||||
|
||||
DefaultJwtBearerTokenResponseClient jwtBearerAccessTokenResponseClient =
|
||||
new DefaultJwtBearerTokenResponseClient();
|
||||
jwtBearerAccessTokenResponseClient.setRestOperations(restTemplate());
|
||||
@@ -1946,9 +1922,6 @@ public class SecurityConfig {
|
||||
.clientCredentials((clientCredentials) -> clientCredentials
|
||||
.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient)
|
||||
)
|
||||
.password((password) -> password
|
||||
.accessTokenResponseClient(passwordAccessTokenResponseClient)
|
||||
)
|
||||
.provider(jwtBearerAuthorizedClientProvider)
|
||||
.provider(tokenExchangeAuthorizedClientProvider)
|
||||
.build();
|
||||
@@ -2012,9 +1985,6 @@ class SecurityConfig {
|
||||
val clientCredentialsAccessTokenResponseClient = DefaultClientCredentialsTokenResponseClient()
|
||||
clientCredentialsAccessTokenResponseClient.setRestOperations(restTemplate())
|
||||
|
||||
val passwordAccessTokenResponseClient = DefaultPasswordTokenResponseClient()
|
||||
passwordAccessTokenResponseClient.setRestOperations(restTemplate())
|
||||
|
||||
val jwtBearerAccessTokenResponseClient = DefaultJwtBearerTokenResponseClient()
|
||||
jwtBearerAccessTokenResponseClient.setRestOperations(restTemplate())
|
||||
|
||||
@@ -2035,9 +2005,6 @@ class SecurityConfig {
|
||||
.clientCredentials { clientCredentials ->
|
||||
clientCredentials.accessTokenResponseClient(clientCredentialsAccessTokenResponseClient)
|
||||
}
|
||||
.password { password ->
|
||||
password.accessTokenResponseClient(passwordAccessTokenResponseClient)
|
||||
}
|
||||
.provider(jwtBearerAuthorizedClientProvider)
|
||||
.provider(tokenExchangeAuthorizedClientProvider)
|
||||
.build()
|
||||
|
||||
Reference in New Issue
Block a user