2
0

rest-client-access-token-response-client.adoc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. To opt-in to using `{class-name}`, simply provide a bean as in the following example and it will be picked up by the default `OAuth2AuthorizedClientManager` automatically:
  2. [#oauth2-client-{section-id}-access-token-response-client-bean]
  3. .Access Token Response Configuration
  4. [tabs]
  5. ======
  6. Java::
  7. +
  8. [source,java,role="primary",subs="+attributes"]
  9. ----
  10. @Bean
  11. public OAuth2AccessTokenResponseClient<{grant-request}> accessTokenResponseClient() {
  12. return new {class-name}();
  13. }
  14. ----
  15. Kotlin::
  16. +
  17. [source,kotlin,role="secondary",subs="+attributes"]
  18. ----
  19. @Bean
  20. fun accessTokenResponseClient(): OAuth2AccessTokenResponseClient<{grant-type}> {
  21. return {class-name}()
  22. }
  23. ----
  24. ======
  25. [NOTE]
  26. ====
  27. The new implementation will be the default in Spring Security 7.
  28. ====
  29. `{class-name}` is very flexible and provides several options for customizing the OAuth 2.0 Access Token request and response for the {grant-type} grant.
  30. Choose from the following use cases to learn more:
  31. * I want to <<oauth2-client-{section-id}-access-token-request-headers,customize headers of the Access Token request>>
  32. * I want to <<oauth2-client-{section-id}-access-token-request-parameters,customize parameters of the Access Token request>>
  33. * I want to <<oauth2-client-{section-id}-access-token-response-rest-client,customize the instance of `RestClient` that is used>>
  34. * I want to <<oauth2-client-{section-id}-access-token-response-parameters,customize parameters of the Access Token response>>
  35. * I want to <<oauth2-client-{section-id}-access-token-response-errors,customize error handling of the Access Token response>>
  36. [#oauth2-client-{section-id}-access-token-request]
  37. == Customizing the Access Token Request
  38. `{class-name}` provides hooks for customizing HTTP headers and request parameters of the OAuth 2.0 Access Token Request.
  39. [#oauth2-client-{section-id}-access-token-request-headers]
  40. === Customizing Request Headers
  41. There are two options for customizing HTTP headers:
  42. * Add additional headers by calling `addHeadersConverter()`
  43. * Fully customize headers by calling `setHeadersConverter()`
  44. You can include additional headers without affecting the default headers added to every request using `addHeadersConverter()`.
  45. The following example adds a `User-Agent` header to the request when the `registrationId` is `spring`:
  46. .Include Additional HTTP Headers
  47. [tabs]
  48. ======
  49. Java::
  50. +
  51. [source,java,role="primary",subs="+attributes"]
  52. ----
  53. {class-name} accessTokenResponseClient =
  54. new {class-name}();
  55. accessTokenResponseClient.addHeadersConverter(grantRequest -> {
  56. ClientRegistration clientRegistration = grantRequest.getClientRegistration();
  57. HttpHeaders headers = new HttpHeaders();
  58. if (clientRegistration.getRegistrationId().equals("spring")) {
  59. headers.set(HttpHeaders.USER_AGENT, "my-user-agent");
  60. }
  61. return headers;
  62. });
  63. ----
  64. Kotlin::
  65. +
  66. [source,kotlin,role="secondary",subs="+attributes"]
  67. ----
  68. val accessTokenResponseClient = {class-name}()
  69. accessTokenResponseClient.addHeadersConverter { grantRequest ->
  70. val clientRegistration = grantRequest.getClientRegistration()
  71. val headers = HttpHeaders()
  72. if (clientRegistration.getRegistrationId() == "spring") {
  73. headers[HttpHeaders.USER_AGENT] = "my-user-agent"
  74. }
  75. headers
  76. }
  77. ----
  78. ======
  79. You can fully customize headers by re-using `DefaultOAuth2TokenRequestHeadersConverter` or providing a custom implementation using `setHeadersConverter()`.
  80. The following example re-uses `DefaultOAuth2TokenRequestHeadersConverter` and disables `encodeClientCredentials` so that HTTP Basic credentials are no longer encoded with `application/x-www-form-urlencoded`:
  81. .Customize HTTP Headers
  82. [tabs]
  83. ======
  84. Java::
  85. +
  86. [source,java,role="primary",subs="+attributes"]
  87. ----
  88. DefaultOAuth2TokenRequestHeadersConverter headersConverter =
  89. new DefaultOAuth2TokenRequestHeadersConverter();
  90. headersConverter.setEncodeClientCredentials(false);
  91. {class-name} accessTokenResponseClient =
  92. new {class-name}();
  93. accessTokenResponseClient.setHeadersConverter(headersConverter);
  94. ----
  95. Kotlin::
  96. +
  97. [source,kotlin,role="secondary",subs="+attributes"]
  98. ----
  99. val headersConverter = DefaultOAuth2TokenRequestHeadersConverter()
  100. headersConverter.setEncodeClientCredentials(false)
  101. val accessTokenResponseClient = {class-name}()
  102. accessTokenResponseClient.setHeadersConverter(headersConverter)
  103. ----
  104. ======
  105. [#oauth2-client-{section-id}-access-token-request-parameters]
  106. === Customizing Request Parameters
  107. There are three options for customizing request parameters:
  108. * Add additional parameters by calling `addParametersConverter()`
  109. * Override parameters by calling `setParametersConverter()`
  110. * Fully customize parameters by calling `setParametersCustomizer()`
  111. [NOTE]
  112. ====
  113. Using `setParametersConverter()` does not fully customize parameters because it would require the user to provide all default parameters themselves.
  114. Default parameters are always provided, but can be fully customized or omitted by calling `setParametersCustomizer()`.
  115. ====
  116. You can include additional parameters without affecting the default parameters added to every request using `addParametersConverter()`.
  117. The following example adds an `audience` parameter to the request when the `registrationId` is `keycloak`:
  118. .Include Additional Request Parameters
  119. [tabs]
  120. ======
  121. Java::
  122. +
  123. [source,java,role="primary",subs="+attributes"]
  124. ----
  125. {class-name} accessTokenResponseClient =
  126. new {class-name}();
  127. accessTokenResponseClient.addParametersConverter(grantRequest -> {
  128. ClientRegistration clientRegistration = grantRequest.getClientRegistration();
  129. MultiValueMap<String, String> parameters = new LinkedMultiValueMap<String, String>();
  130. if (clientRegistration.getRegistrationId().equals("keycloak")) {
  131. parameters.set(OAuth2ParameterNames.AUDIENCE, "my-audience");
  132. }
  133. return parameters;
  134. });
  135. ----
  136. Kotlin::
  137. +
  138. [source,kotlin,role="secondary",subs="+attributes"]
  139. ----
  140. val accessTokenResponseClient = {class-name}()
  141. accessTokenResponseClient.addParametersConverter { grantRequest ->
  142. val clientRegistration = grantRequest.getClientRegistration()
  143. val parameters = LinkedMultiValueMap<String, String>()
  144. if (clientRegistration.getRegistrationId() == "keycloak") {
  145. parameters[OAuth2ParameterNames.AUDIENCE] = "my-audience"
  146. }
  147. parameters
  148. }
  149. ----
  150. ======
  151. You can override default parameters using `setParametersConverter()`.
  152. The following example overrides the `client_id` parameter when the `registrationId` is `okta`:
  153. .Override Request Parameters
  154. [tabs]
  155. ======
  156. Java::
  157. +
  158. [source,java,role="primary",subs="+attributes"]
  159. ----
  160. {class-name} accessTokenResponseClient =
  161. new {class-name}();
  162. accessTokenResponseClient.setParametersConverter(grantRequest -> {
  163. ClientRegistration clientRegistration = grantRequest.getClientRegistration();
  164. LinkedMultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
  165. if (clientRegistration.getRegistrationId().equals("okta")) {
  166. parameters.set(OAuth2ParameterNames.CLIENT_ID, "my-client");
  167. }
  168. return parameters;
  169. });
  170. ----
  171. Kotlin::
  172. +
  173. [source,kotlin,role="secondary",subs="+attributes"]
  174. ----
  175. val parametersConverter = DefaultOAuth2TokenRequestParametersConverter<{grant-request}>()
  176. parametersConverter.setParametersCustomizer { parameters ->
  177. if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
  178. parameters.remove(OAuth2ParameterNames.CLIENT_ID)
  179. }
  180. }
  181. val accessTokenResponseClient = {class-name}()
  182. accessTokenResponseClient.setParametersConverter { grantRequest ->
  183. val clientRegistration = grantRequest.getClientRegistration()
  184. val parameters = LinkedMultiValueMap<String, String>()
  185. if (clientRegistration.getRegistrationId() == "okta") {
  186. parameters[OAuth2ParameterNames.CLIENT_ID] = "my-client"
  187. }
  188. parameters
  189. }
  190. ----
  191. ======
  192. You can fully customize parameters (including omitting default parameters) using `setParametersCustomizer()`.
  193. The following example omits the `client_id` parameter when the `client_assertion` parameter is present in the request:
  194. .Omit Request Parameters
  195. [tabs]
  196. ======
  197. Java::
  198. +
  199. [source,java,role="primary",subs="+attributes"]
  200. ----
  201. {class-name} accessTokenResponseClient =
  202. new {class-name}();
  203. accessTokenResponseClient.setParametersCustomizer(parameters -> {
  204. if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
  205. parameters.remove(OAuth2ParameterNames.CLIENT_ID);
  206. }
  207. });
  208. ----
  209. Kotlin::
  210. +
  211. [source,kotlin,role="secondary",subs="+attributes"]
  212. ----
  213. val accessTokenResponseClient = {class-name}()
  214. accessTokenResponseClient.setParametersCustomizer { parameters ->
  215. if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ASSERTION)) {
  216. parameters.remove(OAuth2ParameterNames.CLIENT_ID)
  217. }
  218. }
  219. ----
  220. ======
  221. [#oauth2-client-{section-id}-access-token-response]
  222. == Customizing the Access Token Response
  223. `{class-name}` provides hooks for customizing response parameters and error handling of the OAuth 2.0 Access Token Response.
  224. [#oauth2-client-{section-id}-access-token-response-rest-client]
  225. === Customizing the `RestClient`
  226. You can customize the Token Response by providing a pre-configured `RestClient` to `setRestClient()`.
  227. The default `RestClient` is configured as follows:
  228. .Default `RestClient` Configuration
  229. [tabs]
  230. ======
  231. Java::
  232. +
  233. [source,java,role="primary",subs="+attributes"]
  234. ----
  235. RestClient restClient = RestClient.builder()
  236. .messageConverters(messageConverters -> {
  237. messageConverters.clear();
  238. messageConverters.add(new FormHttpMessageConverter());
  239. messageConverters.add(new OAuth2AccessTokenResponseHttpMessageConverter());
  240. })
  241. .defaultStatusHandler(new OAuth2ErrorResponseErrorHandler())
  242. .build();
  243. {class-name} accessTokenResponseClient =
  244. new {class-name}();
  245. accessTokenResponseClient.setRestClient(restClient);
  246. ----
  247. Kotlin::
  248. +
  249. [source,kotlin,role="secondary",subs="+attributes"]
  250. ----
  251. val restClient = RestClient.builder()
  252. .messageConverters { messageConverters ->
  253. messageConverters.clear()
  254. messageConverters.add(FormHttpMessageConverter())
  255. messageConverters.add(OAuth2AccessTokenResponseHttpMessageConverter())
  256. }
  257. .defaultStatusHandler(OAuth2ErrorResponseErrorHandler())
  258. .build()
  259. val accessTokenResponseClient = {class-name}()
  260. accessTokenResponseClient.setRestClient(restClient)
  261. ----
  262. ======
  263. `OAuth2AccessTokenResponseHttpMessageConverter` is an `HttpMessageConverter` for an OAuth 2.0 Access Token Response.
  264. You can customize the conversion of Token Response parameters to an `OAuth2AccessTokenResponse` by calling `setAccessTokenResponseConverter()`.
  265. The default implementation is `DefaultMapOAuth2AccessTokenResponseConverter`.
  266. `OAuth2ErrorResponseErrorHandler` is a `ResponseErrorHandler` that can handle an OAuth 2.0 Error, such as `400 Bad Request`.
  267. It uses an `OAuth2ErrorHttpMessageConverter` for converting the OAuth 2.0 Error parameters to an `OAuth2Error`.
  268. You can customize the conversion of Token Response parameters to an `OAuth2Error` by calling `setErrorConverter()`.
  269. [TIP]
  270. ====
  271. Spring MVC `FormHttpMessageConverter` is required, as it is used when sending the OAuth 2.0 Access Token Request.
  272. ====
  273. [#oauth2-client-{section-id}-access-token-response-parameters]
  274. === Customizing Response Parameters
  275. The following example provides a starting point for customizing the conversion of Token Response parameters to an `OAuth2AccessTokenResponse`:
  276. .Customize Access Token Response Converter
  277. [tabs]
  278. ======
  279. Java::
  280. +
  281. [source,java,role="primary"]
  282. ----
  283. OAuth2AccessTokenResponseHttpMessageConverter accessTokenResponseMessageConverter =
  284. new OAuth2AccessTokenResponseHttpMessageConverter();
  285. accessTokenResponseMessageConverter.setAccessTokenResponseConverter(parameters -> {
  286. // ...
  287. return OAuth2AccessTokenResponse.withToken("custom-token")
  288. // ...
  289. .build();
  290. });
  291. ----
  292. Kotlin::
  293. +
  294. [source,kotlin,role="secondary"]
  295. ----
  296. val accessTokenResponseMessageConverter = OAuth2AccessTokenResponseHttpMessageConverter()
  297. accessTokenResponseMessageConverter.setAccessTokenResponseConverter { parameters ->
  298. // ...
  299. return OAuth2AccessTokenResponse.withToken("custom-token")
  300. // ...
  301. .build()
  302. }
  303. ----
  304. ======
  305. [#oauth2-client-{section-id}-access-token-response-errors]
  306. === Customizing Error Handling
  307. The following example provides a starting point for customizing the conversion of Error parameters to an `OAuth2Error`:
  308. .Customize Access Token Error Handler
  309. [tabs]
  310. ======
  311. Java::
  312. +
  313. [source,java,role="primary"]
  314. ----
  315. OAuth2ErrorHttpMessageConverter errorConverter =
  316. new OAuth2ErrorHttpMessageConverter();
  317. errorConverter.setErrorConverter(parameters -> {
  318. // ...
  319. return new OAuth2Error("custom-error", "custom description", "custom-uri");
  320. });
  321. OAuth2ErrorResponseErrorHandler errorHandler =
  322. new OAuth2ErrorResponseErrorHandler();
  323. errorHandler.setErrorConverter(errorConverter);
  324. ----
  325. Kotlin::
  326. +
  327. [source,kotlin,role="secondary"]
  328. ----
  329. val errorConverter = OAuth2ErrorHttpMessageConverter()
  330. errorConverter.setErrorConverter { parameters ->
  331. // ...
  332. return OAuth2Error("custom-error", "custom description", "custom-uri")
  333. }
  334. val errorHandler = OAuth2ErrorResponseErrorHandler()
  335. errorHandler.setErrorConverter(errorConverter)
  336. ----
  337. ======