From eeb0f56bac4c5a0929eb54dcdfee66a594c9f2d2 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Thu, 19 Sep 2019 07:12:08 -0400 Subject: [PATCH] Add ref doc for password grant Fixes gh-7397 --- .../servlet/preface/oauth2-client.adoc | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/preface/oauth2-client.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/preface/oauth2-client.adoc index c6785bb6ca..0c732b6264 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/preface/oauth2-client.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/preface/oauth2-client.adoc @@ -83,6 +83,7 @@ The following sections will go into more detail on the core components used by O ** <> ** <> ** <> +** <> * <> ** <> @@ -781,6 +782,180 @@ public class OAuth2ClientController { If not provided, it will default to `ServletRequestAttributes` using `RequestContextHolder.getRequestAttributes()`. +[[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. + + +===== 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 `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. + +The `DefaultPasswordTokenResponseClient` is quite flexible as it allows you to customize the pre-processing of the Token Request and/or post-handling of the Token Response. + + +===== 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>`. +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 allow you to extend the standard Token Request and add custom parameter(s). + +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. + + +===== Customizing the Access Token Response + +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: + +[source,java] +---- +RestTemplate restTemplate = new RestTemplate(Arrays.asList( + new FormHttpMessageConverter(), + new OAuth2AccessTokenResponseHttpMessageConverter())); + +restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); +---- + +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. +You can provide `OAuth2AccessTokenResponseHttpMessageConverter.setTokenResponseConverter()` with a custom `Converter, OAuth2AccessTokenResponse>` that is used for converting the OAuth 2.0 Access Token Response parameters to an `OAuth2AccessTokenResponse`. + +`OAuth2ErrorResponseErrorHandler` is a `ResponseErrorHandler` that can handle an OAuth 2.0 Error, eg. 400 Bad Request. +It uses an `OAuth2ErrorHttpMessageConverter` for converting the OAuth 2.0 Error parameters to an `OAuth2Error`. + +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] +---- +// Customize +OAuth2AccessTokenResponseClient passwordTokenResponseClient = ... + +OAuth2AuthorizedClientProvider authorizedClientProvider = + OAuth2AuthorizedClientProviderBuilder.builder() + .password(configurer -> configurer.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. + +===== Using the Access Token + +Given the following Spring Boot 2.x 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 `OAuth2AuthorizedClientManager` `@Bean`: + +[source,java] +---- +@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> contextAttributesMapper() { + return authorizeRequest -> { + Map 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; + }; +} +---- + +You may obtain the `OAuth2AccessToken` as follows: + +[source,java] +---- +@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"; + } +} +---- + +[NOTE] +`HttpServletRequest` and `HttpServletResponse` are both OPTIONAL attributes. +If not provided, it will default to `ServletRequestAttributes` using `RequestContextHolder.getRequestAttributes()`. + + [[oauth2Client-additional-features]] === Additional Features