authorization-grants.adoc 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372
  1. [[oauth2Client-auth-grant-support]]
  2. = Authorization Grant Support
  3. [[oauth2Client-auth-code-grant]]
  4. == Authorization Code
  5. [NOTE]
  6. Please refer to the OAuth 2.0 Authorization Framework for further details on the https://tools.ietf.org/html/rfc6749#section-1.3.1[Authorization Code] grant.
  7. === Obtaining Authorization
  8. [NOTE]
  9. Please refer to the https://tools.ietf.org/html/rfc6749#section-4.1.1[Authorization Request/Response] protocol flow for the Authorization Code grant.
  10. === Initiating the Authorization Request
  11. The `OAuth2AuthorizationRequestRedirectWebFilter` uses a `ServerOAuth2AuthorizationRequestResolver` to resolve an `OAuth2AuthorizationRequest` and initiate the Authorization Code grant flow by redirecting the end-user's user-agent to the Authorization Server's Authorization Endpoint.
  12. The primary role of the `ServerOAuth2AuthorizationRequestResolver` is to resolve an `OAuth2AuthorizationRequest` from the provided web request.
  13. The default implementation `DefaultServerOAuth2AuthorizationRequestResolver` matches on the (default) path `+/oauth2/authorization/{registrationId}+` extracting the `registrationId` and using it to build the `OAuth2AuthorizationRequest` for the associated `ClientRegistration`.
  14. Given the following Spring Boot properties for an OAuth 2.0 Client registration:
  15. [source,yaml,attrs="-attributes"]
  16. ----
  17. spring:
  18. security:
  19. oauth2:
  20. client:
  21. registration:
  22. okta:
  23. client-id: okta-client-id
  24. client-secret: okta-client-secret
  25. authorization-grant-type: authorization_code
  26. redirect-uri: "{baseUrl}/authorized/okta"
  27. scope: read, write
  28. provider:
  29. okta:
  30. authorization-uri: https://dev-1234.oktapreview.com/oauth2/v1/authorize
  31. token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token
  32. ----
  33. A request with the base path `/oauth2/authorization/okta` will initiate the Authorization Request redirect by the `OAuth2AuthorizationRequestRedirectWebFilter` and ultimately start the Authorization Code grant flow.
  34. [NOTE]
  35. The `AuthorizationCodeReactiveOAuth2AuthorizedClientProvider` is an implementation of `ReactiveOAuth2AuthorizedClientProvider` for the Authorization Code grant,
  36. which also initiates the Authorization Request redirect by the `OAuth2AuthorizationRequestRedirectWebFilter`.
  37. If the OAuth 2.0 Client is a https://tools.ietf.org/html/rfc6749#section-2.1[Public Client], then configure the OAuth 2.0 Client registration as follows:
  38. [source,yaml,attrs="-attributes"]
  39. ----
  40. spring:
  41. security:
  42. oauth2:
  43. client:
  44. registration:
  45. okta:
  46. client-id: okta-client-id
  47. client-authentication-method: none
  48. authorization-grant-type: authorization_code
  49. redirect-uri: "{baseUrl}/authorized/okta"
  50. ...
  51. ----
  52. Public Clients are supported using https://tools.ietf.org/html/rfc7636[Proof Key for Code Exchange] (PKCE).
  53. If the client is running in an untrusted environment (eg. native application or web browser-based application) and therefore incapable of maintaining the confidentiality of it's credentials, PKCE will automatically be used when the following conditions are true:
  54. . `client-secret` is omitted (or empty)
  55. . `client-authentication-method` is set to "none" (`ClientAuthenticationMethod.NONE`)
  56. [TIP]
  57. If the OAuth 2.0 Provider supports PKCE for https://tools.ietf.org/html/rfc6749#section-2.1[Confidential Clients], you may (optionally) configure it using `DefaultServerOAuth2AuthorizationRequestResolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce())`.
  58. [[oauth2Client-auth-code-redirect-uri]]
  59. The `DefaultServerOAuth2AuthorizationRequestResolver` also supports `URI` template variables for the `redirect-uri` using `UriComponentsBuilder`.
  60. The following configuration uses all the supported `URI` template variables:
  61. [source,yaml,attrs="-attributes"]
  62. ----
  63. spring:
  64. security:
  65. oauth2:
  66. client:
  67. registration:
  68. okta:
  69. ...
  70. redirect-uri: "{baseScheme}://{baseHost}{basePort}{basePath}/authorized/{registrationId}"
  71. ...
  72. ----
  73. [NOTE]
  74. `+{baseUrl}+` resolves to `+{baseScheme}://{baseHost}{basePort}{basePath}+`
  75. Configuring the `redirect-uri` with `URI` template variables is especially useful when the OAuth 2.0 Client is running behind a xref:features/exploits/http.adoc#http-proxy-server[Proxy Server].
  76. This ensures that the `X-Forwarded-*` headers are used when expanding the `redirect-uri`.
  77. === Customizing the Authorization Request
  78. One of the primary use cases a `ServerOAuth2AuthorizationRequestResolver` can realize is the ability to customize the Authorization Request with additional parameters above the standard parameters defined in the OAuth 2.0 Authorization Framework.
  79. For example, OpenID Connect defines additional OAuth 2.0 request parameters for the https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest[Authorization Code Flow] extending from the standard parameters defined in the https://tools.ietf.org/html/rfc6749#section-4.1.1[OAuth 2.0 Authorization Framework].
  80. One of those extended parameters is the `prompt` parameter.
  81. [NOTE]
  82. ====
  83. The `prompt` parameter is optional. Space delimited, case sensitive list of ASCII string values that specifies whether the Authorization Server prompts the End-User for re-authentication and consent. The defined values are: `none`, `login`, `consent`, and `select_account`.
  84. ====
  85. The following example shows how to configure the `DefaultServerOAuth2AuthorizationRequestResolver` with a `Consumer<OAuth2AuthorizationRequest.Builder>` that customizes the Authorization Request for `oauth2Login()`, by including the request parameter `prompt=consent`.
  86. [tabs]
  87. ======
  88. Java::
  89. +
  90. [source,java,role="primary"]
  91. ----
  92. @Configuration
  93. @EnableWebFluxSecurity
  94. public class OAuth2LoginSecurityConfig {
  95. @Autowired
  96. private ReactiveClientRegistrationRepository clientRegistrationRepository;
  97. @Bean
  98. public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  99. http
  100. .authorizeExchange(authorize -> authorize
  101. .anyExchange().authenticated()
  102. )
  103. .oauth2Login(oauth2 -> oauth2
  104. .authorizationRequestResolver(
  105. authorizationRequestResolver(this.clientRegistrationRepository)
  106. )
  107. );
  108. return http.build();
  109. }
  110. private ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver(
  111. ReactiveClientRegistrationRepository clientRegistrationRepository) {
  112. DefaultServerOAuth2AuthorizationRequestResolver authorizationRequestResolver =
  113. new DefaultServerOAuth2AuthorizationRequestResolver(
  114. clientRegistrationRepository);
  115. authorizationRequestResolver.setAuthorizationRequestCustomizer(
  116. authorizationRequestCustomizer());
  117. return authorizationRequestResolver;
  118. }
  119. private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer() {
  120. return customizer -> customizer
  121. .additionalParameters(params -> params.put("prompt", "consent"));
  122. }
  123. }
  124. ----
  125. Kotlin::
  126. +
  127. [source,kotlin,role="secondary"]
  128. ----
  129. @Configuration
  130. @EnableWebFluxSecurity
  131. class SecurityConfig {
  132. @Autowired
  133. private lateinit var customClientRegistrationRepository: ReactiveClientRegistrationRepository
  134. @Bean
  135. fun securityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  136. http {
  137. authorizeExchange {
  138. authorize(anyExchange, authenticated)
  139. }
  140. oauth2Login {
  141. authorizationRequestResolver = authorizationRequestResolver(customClientRegistrationRepository)
  142. }
  143. }
  144. return http.build()
  145. }
  146. private fun authorizationRequestResolver(
  147. clientRegistrationRepository: ReactiveClientRegistrationRepository): ServerOAuth2AuthorizationRequestResolver {
  148. val authorizationRequestResolver = DefaultServerOAuth2AuthorizationRequestResolver(
  149. clientRegistrationRepository)
  150. authorizationRequestResolver.setAuthorizationRequestCustomizer(
  151. authorizationRequestCustomizer())
  152. return authorizationRequestResolver
  153. }
  154. private fun authorizationRequestCustomizer(): Consumer<OAuth2AuthorizationRequest.Builder> {
  155. return Consumer { customizer ->
  156. customizer
  157. .additionalParameters { params -> params["prompt"] = "consent" }
  158. }
  159. }
  160. }
  161. ----
  162. ======
  163. 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.
  164. For example, if the value for the request parameter `prompt` is always `consent` for the provider `okta`, than simply configure as follows:
  165. [source,yaml]
  166. ----
  167. spring:
  168. security:
  169. oauth2:
  170. client:
  171. provider:
  172. okta:
  173. authorization-uri: https://dev-1234.oktapreview.com/oauth2/v1/authorize?prompt=consent
  174. ----
  175. The preceding example shows the common use case of adding a custom parameter on top of the standard parameters.
  176. Alternatively, if your requirements are more advanced, you can take full control in building the Authorization Request URI by simply overriding the `OAuth2AuthorizationRequest.authorizationRequestUri` property.
  177. [TIP]
  178. `OAuth2AuthorizationRequest.Builder.build()` constructs the `OAuth2AuthorizationRequest.authorizationRequestUri`, which represents the Authorization Request URI including all query parameters using the `application/x-www-form-urlencoded` format.
  179. The following example shows a variation of `authorizationRequestCustomizer()` from the preceding example, and instead overrides the `OAuth2AuthorizationRequest.authorizationRequestUri` property.
  180. [tabs]
  181. ======
  182. Java::
  183. +
  184. [source,java,role="primary"]
  185. ----
  186. private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer() {
  187. return customizer -> customizer
  188. .authorizationRequestUri(uriBuilder -> uriBuilder
  189. .queryParam("prompt", "consent").build());
  190. }
  191. ----
  192. Kotlin::
  193. +
  194. [source,kotlin,role="secondary"]
  195. ----
  196. private fun authorizationRequestCustomizer(): Consumer<OAuth2AuthorizationRequest.Builder> {
  197. return Consumer { customizer: OAuth2AuthorizationRequest.Builder ->
  198. customizer
  199. .authorizationRequestUri { uriBuilder: UriBuilder ->
  200. uriBuilder
  201. .queryParam("prompt", "consent").build()
  202. }
  203. }
  204. }
  205. ----
  206. ======
  207. === Storing the Authorization Request
  208. The `ServerAuthorizationRequestRepository` is responsible for the persistence of the `OAuth2AuthorizationRequest` from the time the Authorization Request is initiated to the time the Authorization Response is received (the callback).
  209. [TIP]
  210. The `OAuth2AuthorizationRequest` is used to correlate and validate the Authorization Response.
  211. The default implementation of `ServerAuthorizationRequestRepository` is `WebSessionOAuth2ServerAuthorizationRequestRepository`, which stores the `OAuth2AuthorizationRequest` in the `WebSession`.
  212. If you have a custom implementation of `ServerAuthorizationRequestRepository`, you may configure it as shown in the following example:
  213. .ServerAuthorizationRequestRepository Configuration
  214. [tabs]
  215. ======
  216. Java::
  217. +
  218. [source,java,role="primary"]
  219. ----
  220. @Configuration
  221. @EnableWebFluxSecurity
  222. public class OAuth2ClientSecurityConfig {
  223. @Bean
  224. public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  225. http
  226. .oauth2Client(oauth2 -> oauth2
  227. .authorizationRequestRepository(this.authorizationRequestRepository())
  228. ...
  229. );
  230. return http.build();
  231. }
  232. }
  233. ----
  234. Kotlin::
  235. +
  236. [source,kotlin,role="secondary"]
  237. ----
  238. @Configuration
  239. @EnableWebFluxSecurity
  240. class OAuth2ClientSecurityConfig {
  241. @Bean
  242. fun securityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  243. http {
  244. oauth2Client {
  245. authorizationRequestRepository = authorizationRequestRepository()
  246. }
  247. }
  248. return http.build()
  249. }
  250. }
  251. ----
  252. ======
  253. === Requesting an Access Token
  254. [NOTE]
  255. Please refer to the https://tools.ietf.org/html/rfc6749#section-4.1.3[Access Token Request/Response] protocol flow for the Authorization Code grant.
  256. The default implementation of `ReactiveOAuth2AccessTokenResponseClient` for the Authorization Code grant is `WebClientReactiveAuthorizationCodeTokenResponseClient`, which uses a `WebClient` for exchanging an authorization code for an access token at the Authorization Server’s Token Endpoint.
  257. The `WebClientReactiveAuthorizationCodeTokenResponseClient` is quite flexible as it allows you to customize the pre-processing of the Token Request and/or post-handling of the Token Response.
  258. === Customizing the Access Token Request
  259. If you need to customize the pre-processing of the Token Request, you can provide `WebClientReactiveAuthorizationCodeTokenResponseClient.setParametersConverter()` with a custom `Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>>`.
  260. The default implementation builds a `MultiValueMap<String, String>` containing only the `grant_type` parameter of a standard https://tools.ietf.org/html/rfc6749#section-4.1.3[OAuth 2.0 Access Token Request] which is used to construct the request. Other parameters required by the Authorization Code grant are added directly to the body of the request by the `WebClientReactiveAuthorizationCodeTokenResponseClient`.
  261. However, providing a custom `Converter`, would allow you to extend the standard Token Request and add custom parameter(s).
  262. [TIP]
  263. If you prefer to only add additional parameters, you can instead provide `WebClientReactiveAuthorizationCodeTokenResponseClient.addParametersConverter()` with a custom `Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>>` which constructs an aggregate `Converter`.
  264. IMPORTANT: The custom `Converter` must return valid parameters of an OAuth 2.0 Access Token Request that is understood by the intended OAuth 2.0 Provider.
  265. === Customizing the Access Token Response
  266. On the other end, if you need to customize the post-handling of the Token Response, you will need to provide `WebClientReactiveAuthorizationCodeTokenResponseClient.setBodyExtractor()` with a custom configured `BodyExtractor<Mono<OAuth2AccessTokenResponse>, ReactiveHttpInputMessage>` that is used for converting the OAuth 2.0 Access Token Response to an `OAuth2AccessTokenResponse`.
  267. The default implementation provided by `OAuth2BodyExtractors.oauth2AccessTokenResponse()` parses the response and handles errors accordingly.
  268. === Customizing the `WebClient`
  269. Alternatively, if your requirements are more advanced, you can take full control of the request/response by simply providing `WebClientReactiveAuthorizationCodeTokenResponseClient.setWebClient()` with a custom configured `WebClient`.
  270. Whether you customize `WebClientReactiveAuthorizationCodeTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you’ll need to configure it as shown in the following example:
  271. .Access Token Response Configuration
  272. [tabs]
  273. ======
  274. Java::
  275. +
  276. [source,java,role="primary"]
  277. ----
  278. @Configuration
  279. @EnableWebFluxSecurity
  280. public class OAuth2ClientSecurityConfig {
  281. @Bean
  282. public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  283. http
  284. .oauth2Client(oauth2 -> oauth2
  285. .authenticationManager(this.authorizationCodeAuthenticationManager())
  286. ...
  287. );
  288. return http.build();
  289. }
  290. private ReactiveAuthenticationManager authorizationCodeAuthenticationManager() {
  291. WebClientReactiveAuthorizationCodeTokenResponseClient accessTokenResponseClient =
  292. new WebClientReactiveAuthorizationCodeTokenResponseClient();
  293. ...
  294. return new OAuth2AuthorizationCodeReactiveAuthenticationManager(accessTokenResponseClient);
  295. }
  296. }
  297. ----
  298. Kotlin::
  299. +
  300. [source,kotlin,role="secondary"]
  301. ----
  302. @Configuration
  303. @EnableWebFluxSecurity
  304. class OAuth2ClientSecurityConfig {
  305. @Bean
  306. fun securityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  307. http {
  308. oauth2Client {
  309. authenticationManager = authorizationCodeAuthenticationManager()
  310. }
  311. }
  312. return http.build()
  313. }
  314. private fun authorizationCodeAuthenticationManager(): ReactiveAuthenticationManager {
  315. val accessTokenResponseClient = WebClientReactiveAuthorizationCodeTokenResponseClient()
  316. ...
  317. return OAuth2AuthorizationCodeReactiveAuthenticationManager(accessTokenResponseClient)
  318. }
  319. }
  320. ----
  321. ======
  322. [[oauth2Client-refresh-token-grant]]
  323. == Refresh Token
  324. [NOTE]
  325. Please refer to the OAuth 2.0 Authorization Framework for further details on the https://tools.ietf.org/html/rfc6749#section-1.5[Refresh Token].
  326. === Refreshing an Access Token
  327. [NOTE]
  328. Please refer to the https://tools.ietf.org/html/rfc6749#section-6[Access Token Request/Response] protocol flow for the Refresh Token grant.
  329. The default implementation of `ReactiveOAuth2AccessTokenResponseClient` for the Refresh Token grant is `WebClientReactiveRefreshTokenTokenResponseClient`, which uses a `WebClient` when refreshing an access token at the Authorization Server’s Token Endpoint.
  330. The `WebClientReactiveRefreshTokenTokenResponseClient` is quite flexible as it allows you to customize the pre-processing of the Token Request and/or post-handling of the Token Response.
  331. === Customizing the Access Token Request
  332. If you need to customize the pre-processing of the Token Request, you can provide `WebClientReactiveRefreshTokenTokenResponseClient.setParametersConverter()` with a custom `Converter<OAuth2RefreshTokenGrantRequest, MultiValueMap<String, String>>`.
  333. The default implementation builds a `MultiValueMap<String, String>` containing only the `grant_type` parameter of a standard https://tools.ietf.org/html/rfc6749#section-6[OAuth 2.0 Access Token Request] which is used to construct the request. Other parameters required by the Refresh Token grant are added directly to the body of the request by the `WebClientReactiveRefreshTokenTokenResponseClient`.
  334. However, providing a custom `Converter`, would allow you to extend the standard Token Request and add custom parameter(s).
  335. [TIP]
  336. If you prefer to only add additional parameters, you can instead provide `WebClientReactiveRefreshTokenTokenResponseClient.addParametersConverter()` with a custom `Converter<OAuth2RefreshTokenGrantRequest, MultiValueMap<String, String>>` which constructs an aggregate `Converter`.
  337. IMPORTANT: The custom `Converter` must return valid parameters of an OAuth 2.0 Access Token Request that is understood by the intended OAuth 2.0 Provider.
  338. === Customizing the Access Token Response
  339. On the other end, if you need to customize the post-handling of the Token Response, you will need to provide `WebClientReactiveRefreshTokenTokenResponseClient.setBodyExtractor()` with a custom configured `BodyExtractor<Mono<OAuth2AccessTokenResponse>, ReactiveHttpInputMessage>` that is used for converting the OAuth 2.0 Access Token Response to an `OAuth2AccessTokenResponse`.
  340. The default implementation provided by `OAuth2BodyExtractors.oauth2AccessTokenResponse()` parses the response and handles errors accordingly.
  341. === Customizing the `WebClient`
  342. Alternatively, if your requirements are more advanced, you can take full control of the request/response by simply providing `WebClientReactiveRefreshTokenTokenResponseClient.setWebClient()` with a custom configured `WebClient`.
  343. Whether you customize `WebClientReactiveRefreshTokenTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you’ll need to configure it as shown in the following example:
  344. .Access Token Response Configuration
  345. [tabs]
  346. ======
  347. Java::
  348. +
  349. [source,java,role="primary"]
  350. ----
  351. // Customize
  352. ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenTokenResponseClient = ...
  353. ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
  354. ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
  355. .authorizationCode()
  356. .refreshToken(configurer -> configurer.accessTokenResponseClient(refreshTokenTokenResponseClient))
  357. .build();
  358. ...
  359. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
  360. ----
  361. Kotlin::
  362. +
  363. [source,kotlin,role="secondary"]
  364. ----
  365. // Customize
  366. val refreshTokenTokenResponseClient: ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> = ...
  367. val authorizedClientProvider: ReactiveOAuth2AuthorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
  368. .authorizationCode()
  369. .refreshToken { it.accessTokenResponseClient(refreshTokenTokenResponseClient) }
  370. .build()
  371. ...
  372. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
  373. ----
  374. ======
  375. [NOTE]
  376. `ReactiveOAuth2AuthorizedClientProviderBuilder.builder().refreshToken()` configures a `RefreshTokenReactiveOAuth2AuthorizedClientProvider`,
  377. which is an implementation of a `ReactiveOAuth2AuthorizedClientProvider` for the Refresh Token grant.
  378. The `OAuth2RefreshToken` may optionally be returned in the Access Token Response for the `authorization_code` and `password` grant types.
  379. If the `OAuth2AuthorizedClient.getRefreshToken()` is available and the `OAuth2AuthorizedClient.getAccessToken()` is expired, it will automatically be refreshed by the `RefreshTokenReactiveOAuth2AuthorizedClientProvider`.
  380. [[oauth2Client-client-creds-grant]]
  381. == Client Credentials
  382. [NOTE]
  383. Please refer to the OAuth 2.0 Authorization Framework for further details on the https://tools.ietf.org/html/rfc6749#section-1.3.4[Client Credentials] grant.
  384. === Requesting an Access Token
  385. [NOTE]
  386. Please refer to the https://tools.ietf.org/html/rfc6749#section-4.4.2[Access Token Request/Response] protocol flow for the Client Credentials grant.
  387. The default implementation of `ReactiveOAuth2AccessTokenResponseClient` for the Client Credentials grant is `WebClientReactiveClientCredentialsTokenResponseClient`, which uses a `WebClient` when requesting an access token at the Authorization Server’s Token Endpoint.
  388. The `WebClientReactiveClientCredentialsTokenResponseClient` is quite flexible as it allows you to customize the pre-processing of the Token Request and/or post-handling of the Token Response.
  389. === Customizing the Access Token Request
  390. If you need to customize the pre-processing of the Token Request, you can provide `WebClientReactiveClientCredentialsTokenResponseClient.setParametersConverter()` with a custom `Converter<OAuth2ClientCredentialsGrantRequest, MultiValueMap<String, String>>`.
  391. The default implementation builds a `MultiValueMap<String, String>` containing only the `grant_type` parameter of a standard https://tools.ietf.org/html/rfc6749#section-4.4.2[OAuth 2.0 Access Token Request] which is used to construct the request. Other parameters required by the Client Credentials grant are added directly to the body of the request by the `WebClientReactiveClientCredentialsTokenResponseClient`.
  392. However, providing a custom `Converter`, would allow you to extend the standard Token Request and add custom parameter(s).
  393. [TIP]
  394. If you prefer to only add additional parameters, you can instead provide `WebClientReactiveClientCredentialsTokenResponseClient.addParametersConverter()` with a custom `Converter<OAuth2ClientCredentialsGrantRequest, MultiValueMap<String, String>>` which constructs an aggregate `Converter`.
  395. IMPORTANT: The custom `Converter` must return valid parameters of an OAuth 2.0 Access Token Request that is understood by the intended OAuth 2.0 Provider.
  396. === Customizing the Access Token Response
  397. On the other end, if you need to customize the post-handling of the Token Response, you will need to provide `WebClientReactiveClientCredentialsTokenResponseClient.setBodyExtractor()` with a custom configured `BodyExtractor<Mono<OAuth2AccessTokenResponse>, ReactiveHttpInputMessage>` that is used for converting the OAuth 2.0 Access Token Response to an `OAuth2AccessTokenResponse`.
  398. The default implementation provided by `OAuth2BodyExtractors.oauth2AccessTokenResponse()` parses the response and handles errors accordingly.
  399. === Customizing the `WebClient`
  400. Alternatively, if your requirements are more advanced, you can take full control of the request/response by simply providing `WebClientReactiveClientCredentialsTokenResponseClient.setWebClient()` with a custom configured `WebClient`.
  401. Whether you customize `WebClientReactiveClientCredentialsTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example:
  402. [tabs]
  403. ======
  404. Java::
  405. +
  406. [source,java,role="primary"]
  407. ----
  408. // Customize
  409. ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsTokenResponseClient = ...
  410. ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
  411. ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
  412. .clientCredentials(configurer -> configurer.accessTokenResponseClient(clientCredentialsTokenResponseClient))
  413. .build();
  414. ...
  415. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
  416. ----
  417. Kotlin::
  418. +
  419. [source,kotlin,role="secondary"]
  420. ----
  421. // Customize
  422. val clientCredentialsTokenResponseClient: ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> = ...
  423. val authorizedClientProvider: ReactiveOAuth2AuthorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
  424. .clientCredentials { it.accessTokenResponseClient(clientCredentialsTokenResponseClient) }
  425. .build()
  426. ...
  427. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
  428. ----
  429. ======
  430. [NOTE]
  431. `ReactiveOAuth2AuthorizedClientProviderBuilder.builder().clientCredentials()` configures a `ClientCredentialsReactiveOAuth2AuthorizedClientProvider`,
  432. which is an implementation of a `ReactiveOAuth2AuthorizedClientProvider` for the Client Credentials grant.
  433. === Using the Access Token
  434. Given the following Spring Boot properties for an OAuth 2.0 Client registration:
  435. [source,yaml]
  436. ----
  437. spring:
  438. security:
  439. oauth2:
  440. client:
  441. registration:
  442. okta:
  443. client-id: okta-client-id
  444. client-secret: okta-client-secret
  445. authorization-grant-type: client_credentials
  446. scope: read, write
  447. provider:
  448. okta:
  449. token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token
  450. ----
  451. ...and the `ReactiveOAuth2AuthorizedClientManager` `@Bean`:
  452. [tabs]
  453. ======
  454. Java::
  455. +
  456. [source,java,role="primary"]
  457. ----
  458. @Bean
  459. public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
  460. ReactiveClientRegistrationRepository clientRegistrationRepository,
  461. ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
  462. ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
  463. ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
  464. .clientCredentials()
  465. .build();
  466. DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
  467. new DefaultReactiveOAuth2AuthorizedClientManager(
  468. clientRegistrationRepository, authorizedClientRepository);
  469. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
  470. return authorizedClientManager;
  471. }
  472. ----
  473. Kotlin::
  474. +
  475. [source,kotlin,role="secondary"]
  476. ----
  477. @Bean
  478. fun authorizedClientManager(
  479. clientRegistrationRepository: ReactiveClientRegistrationRepository,
  480. authorizedClientRepository: ServerOAuth2AuthorizedClientRepository): ReactiveOAuth2AuthorizedClientManager {
  481. val authorizedClientProvider: ReactiveOAuth2AuthorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
  482. .clientCredentials()
  483. .build()
  484. val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager(
  485. clientRegistrationRepository, authorizedClientRepository)
  486. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
  487. return authorizedClientManager
  488. }
  489. ----
  490. ======
  491. You may obtain the `OAuth2AccessToken` as follows:
  492. [tabs]
  493. ======
  494. Java::
  495. +
  496. [source,java,role="primary"]
  497. ----
  498. @Controller
  499. public class OAuth2ClientController {
  500. @Autowired
  501. private ReactiveOAuth2AuthorizedClientManager authorizedClientManager;
  502. @GetMapping("/")
  503. public Mono<String> index(Authentication authentication, ServerWebExchange exchange) {
  504. OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
  505. .principal(authentication)
  506. .attribute(ServerWebExchange.class.getName(), exchange)
  507. .build();
  508. return this.authorizedClientManager.authorize(authorizeRequest)
  509. .map(OAuth2AuthorizedClient::getAccessToken)
  510. ...
  511. .thenReturn("index");
  512. }
  513. }
  514. ----
  515. Kotlin::
  516. +
  517. [source,kotlin,role="secondary"]
  518. ----
  519. class OAuth2ClientController {
  520. @Autowired
  521. private lateinit var authorizedClientManager: ReactiveOAuth2AuthorizedClientManager
  522. @GetMapping("/")
  523. fun index(authentication: Authentication, exchange: ServerWebExchange): Mono<String> {
  524. val authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
  525. .principal(authentication)
  526. .attribute(ServerWebExchange::class.java.name, exchange)
  527. .build()
  528. return authorizedClientManager.authorize(authorizeRequest)
  529. .map { it.accessToken }
  530. ...
  531. .thenReturn("index")
  532. }
  533. }
  534. ----
  535. ======
  536. [NOTE]
  537. `ServerWebExchange` is an OPTIONAL attribute.
  538. 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`.
  539. [[oauth2Client-password-grant]]
  540. == Resource Owner Password Credentials
  541. [NOTE]
  542. 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.
  543. === Requesting an Access Token
  544. [NOTE]
  545. 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.
  546. 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.
  547. The `WebClientReactivePasswordTokenResponseClient` is quite flexible as it allows you to customize the pre-processing of the Token Request and/or post-handling of the Token Response.
  548. === Customizing the Access Token Request
  549. If you need to customize the pre-processing of the Token Request, you can provide `WebClientReactivePasswordTokenResponseClient.setParametersConverter()` with a custom `Converter<OAuth2PasswordGrantRequest, MultiValueMap<String, String>>`.
  550. The default implementation builds a `MultiValueMap<String, String>` containing only the `grant_type` parameter of a standard https://tools.ietf.org/html/rfc6749#section-4.4.2[OAuth 2.0 Access Token Request] which is used to construct the request. Other parameters required by the Resource Owner Password Credentials grant are added directly to the body of the request by the `WebClientReactivePasswordTokenResponseClient`.
  551. However, providing a custom `Converter`, would allow you to extend the standard Token Request and add custom parameter(s).
  552. [TIP]
  553. If you prefer to only add additional parameters, you can instead provide `WebClientReactivePasswordTokenResponseClient.addParametersConverter()` with a custom `Converter<OAuth2PasswordGrantRequest, MultiValueMap<String, String>>` which constructs an aggregate `Converter`.
  554. IMPORTANT: The custom `Converter` must return valid parameters of an OAuth 2.0 Access Token Request that is understood by the intended OAuth 2.0 Provider.
  555. === Customizing the Access Token Response
  556. On the other end, if you need to customize the post-handling of the Token Response, you will need to provide `WebClientReactivePasswordTokenResponseClient.setBodyExtractor()` with a custom configured `BodyExtractor<Mono<OAuth2AccessTokenResponse>, ReactiveHttpInputMessage>` that is used for converting the OAuth 2.0 Access Token Response to an `OAuth2AccessTokenResponse`.
  557. The default implementation provided by `OAuth2BodyExtractors.oauth2AccessTokenResponse()` parses the response and handles errors accordingly.
  558. === Customizing the `WebClient`
  559. Alternatively, if your requirements are more advanced, you can take full control of the request/response by simply providing `WebClientReactivePasswordTokenResponseClient.setWebClient()` with a custom configured `WebClient`.
  560. Whether you customize `WebClientReactivePasswordTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example:
  561. [tabs]
  562. ======
  563. Java::
  564. +
  565. [source,java,role="primary"]
  566. ----
  567. // Customize
  568. ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient = ...
  569. ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
  570. ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
  571. .password(configurer -> configurer.accessTokenResponseClient(passwordTokenResponseClient))
  572. .refreshToken()
  573. .build();
  574. ...
  575. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
  576. ----
  577. Kotlin::
  578. +
  579. [source,kotlin,role="secondary"]
  580. ----
  581. val passwordTokenResponseClient: ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> = ...
  582. val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
  583. .password { it.accessTokenResponseClient(passwordTokenResponseClient) }
  584. .refreshToken()
  585. .build()
  586. ...
  587. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
  588. ----
  589. ======
  590. [NOTE]
  591. `ReactiveOAuth2AuthorizedClientProviderBuilder.builder().password()` configures a `PasswordReactiveOAuth2AuthorizedClientProvider`,
  592. which is an implementation of a `ReactiveOAuth2AuthorizedClientProvider` for the Resource Owner Password Credentials grant.
  593. === Using the Access Token
  594. Given the following Spring Boot properties for an OAuth 2.0 Client registration:
  595. [source,yaml]
  596. ----
  597. spring:
  598. security:
  599. oauth2:
  600. client:
  601. registration:
  602. okta:
  603. client-id: okta-client-id
  604. client-secret: okta-client-secret
  605. authorization-grant-type: password
  606. scope: read, write
  607. provider:
  608. okta:
  609. token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token
  610. ----
  611. ...and the `ReactiveOAuth2AuthorizedClientManager` `@Bean`:
  612. [tabs]
  613. ======
  614. Java::
  615. +
  616. [source,java,role="primary"]
  617. ----
  618. @Bean
  619. public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
  620. ReactiveClientRegistrationRepository clientRegistrationRepository,
  621. ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
  622. ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
  623. ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
  624. .password()
  625. .refreshToken()
  626. .build();
  627. DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
  628. new DefaultReactiveOAuth2AuthorizedClientManager(
  629. clientRegistrationRepository, authorizedClientRepository);
  630. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
  631. // Assuming the `username` and `password` are supplied as `ServerHttpRequest` parameters,
  632. // map the `ServerHttpRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
  633. authorizedClientManager.setContextAttributesMapper(contextAttributesMapper());
  634. return authorizedClientManager;
  635. }
  636. private Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttributesMapper() {
  637. return authorizeRequest -> {
  638. Map<String, Object> contextAttributes = Collections.emptyMap();
  639. ServerWebExchange exchange = authorizeRequest.getAttribute(ServerWebExchange.class.getName());
  640. ServerHttpRequest request = exchange.getRequest();
  641. String username = request.getQueryParams().getFirst(OAuth2ParameterNames.USERNAME);
  642. String password = request.getQueryParams().getFirst(OAuth2ParameterNames.PASSWORD);
  643. if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
  644. contextAttributes = new HashMap<>();
  645. // `PasswordReactiveOAuth2AuthorizedClientProvider` requires both attributes
  646. contextAttributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
  647. contextAttributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
  648. }
  649. return Mono.just(contextAttributes);
  650. };
  651. }
  652. ----
  653. Kotlin::
  654. +
  655. [source,kotlin,role="secondary"]
  656. ----
  657. @Bean
  658. fun authorizedClientManager(
  659. clientRegistrationRepository: ReactiveClientRegistrationRepository,
  660. authorizedClientRepository: ServerOAuth2AuthorizedClientRepository): ReactiveOAuth2AuthorizedClientManager {
  661. val authorizedClientProvider: ReactiveOAuth2AuthorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
  662. .password()
  663. .refreshToken()
  664. .build()
  665. val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager(
  666. clientRegistrationRepository, authorizedClientRepository)
  667. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
  668. // Assuming the `username` and `password` are supplied as `ServerHttpRequest` parameters,
  669. // map the `ServerHttpRequest` parameters to `OAuth2AuthorizationContext.getAttributes()`
  670. authorizedClientManager.setContextAttributesMapper(contextAttributesMapper())
  671. return authorizedClientManager
  672. }
  673. private fun contextAttributesMapper(): Function<OAuth2AuthorizeRequest, Mono<MutableMap<String, Any>>> {
  674. return Function { authorizeRequest ->
  675. var contextAttributes: MutableMap<String, Any> = mutableMapOf()
  676. val exchange: ServerWebExchange = authorizeRequest.getAttribute(ServerWebExchange::class.java.name)!!
  677. val request: ServerHttpRequest = exchange.request
  678. val username: String? = request.queryParams.getFirst(OAuth2ParameterNames.USERNAME)
  679. val password: String? = request.queryParams.getFirst(OAuth2ParameterNames.PASSWORD)
  680. if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
  681. contextAttributes = hashMapOf()
  682. // `PasswordReactiveOAuth2AuthorizedClientProvider` requires both attributes
  683. contextAttributes[OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME] = username!!
  684. contextAttributes[OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME] = password!!
  685. }
  686. Mono.just(contextAttributes)
  687. }
  688. }
  689. ----
  690. ======
  691. You may obtain the `OAuth2AccessToken` as follows:
  692. [tabs]
  693. ======
  694. Java::
  695. +
  696. [source,java,role="primary"]
  697. ----
  698. @Controller
  699. public class OAuth2ClientController {
  700. @Autowired
  701. private ReactiveOAuth2AuthorizedClientManager authorizedClientManager;
  702. @GetMapping("/")
  703. public Mono<String> index(Authentication authentication, ServerWebExchange exchange) {
  704. OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
  705. .principal(authentication)
  706. .attribute(ServerWebExchange.class.getName(), exchange)
  707. .build();
  708. return this.authorizedClientManager.authorize(authorizeRequest)
  709. .map(OAuth2AuthorizedClient::getAccessToken)
  710. ...
  711. .thenReturn("index");
  712. }
  713. }
  714. ----
  715. Kotlin::
  716. +
  717. [source,kotlin,role="secondary"]
  718. ----
  719. @Controller
  720. class OAuth2ClientController {
  721. @Autowired
  722. private lateinit var authorizedClientManager: ReactiveOAuth2AuthorizedClientManager
  723. @GetMapping("/")
  724. fun index(authentication: Authentication, exchange: ServerWebExchange): Mono<String> {
  725. val authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
  726. .principal(authentication)
  727. .attribute(ServerWebExchange::class.java.name, exchange)
  728. .build()
  729. return authorizedClientManager.authorize(authorizeRequest)
  730. .map { it.accessToken }
  731. ...
  732. .thenReturn("index")
  733. }
  734. }
  735. ----
  736. ======
  737. [NOTE]
  738. `ServerWebExchange` is an OPTIONAL attribute.
  739. 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`.
  740. [[oauth2Client-jwt-bearer-grant]]
  741. == JWT Bearer
  742. [NOTE]
  743. Please refer to JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants for further details on the https://datatracker.ietf.org/doc/html/rfc7523[JWT Bearer] grant.
  744. === Requesting an Access Token
  745. [NOTE]
  746. Please refer to the https://datatracker.ietf.org/doc/html/rfc7523#section-2.1[Access Token Request/Response] protocol flow for the JWT Bearer grant.
  747. The default implementation of `ReactiveOAuth2AccessTokenResponseClient` for the JWT Bearer grant is `WebClientReactiveJwtBearerTokenResponseClient`, which uses a `WebClient` when requesting an access token at the Authorization Server’s Token Endpoint.
  748. The `WebClientReactiveJwtBearerTokenResponseClient` is quite flexible as it allows you to customize the pre-processing of the Token Request and/or post-handling of the Token Response.
  749. === Customizing the Access Token Request
  750. If you need to customize the pre-processing of the Token Request, you can provide `WebClientReactiveJwtBearerTokenResponseClient.setParametersConverter()` with a custom `Converter<JwtBearerGrantRequest, MultiValueMap<String, String>>`.
  751. The default implementation builds a `MultiValueMap<String, String>` containing only the `grant_type` parameter of a standard https://tools.ietf.org/html/rfc6749#section-4.4.2[OAuth 2.0 Access Token Request] which is used to construct the request. Other parameters required by the JWT Bearer grant are added directly to the body of the request by the `WebClientReactiveJwtBearerTokenResponseClient`.
  752. However, providing a custom `Converter`, would allow you to extend the standard Token Request and add custom parameter(s).
  753. [TIP]
  754. If you prefer to only add additional parameters, you can instead provide `WebClientReactiveJwtBearerTokenResponseClient.addParametersConverter()` with a custom `Converter<JwtBearerGrantRequest, MultiValueMap<String, String>>` which constructs an aggregate `Converter`.
  755. IMPORTANT: The custom `Converter` must return valid parameters of an OAuth 2.0 Access Token Request that is understood by the intended OAuth 2.0 Provider.
  756. === Customizing the Access Token Response
  757. On the other end, if you need to customize the post-handling of the Token Response, you will need to provide `WebClientReactiveJwtBearerTokenResponseClient.setBodyExtractor()` with a custom configured `BodyExtractor<Mono<OAuth2AccessTokenResponse>, ReactiveHttpInputMessage>` that is used for converting the OAuth 2.0 Access Token Response to an `OAuth2AccessTokenResponse`.
  758. The default implementation provided by `OAuth2BodyExtractors.oauth2AccessTokenResponse()` parses the response and handles errors accordingly.
  759. === Customizing the `WebClient`
  760. Alternatively, if your requirements are more advanced, you can take full control of the request/response by simply providing `WebClientReactiveJwtBearerTokenResponseClient.setWebClient()` with a custom configured `WebClient`.
  761. Whether you customize `WebClientReactiveJwtBearerTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example:
  762. [tabs]
  763. ======
  764. Java::
  765. +
  766. [source,java,role="primary"]
  767. ----
  768. // Customize
  769. ReactiveOAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerTokenResponseClient = ...
  770. JwtBearerReactiveOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider = new JwtBearerReactiveOAuth2AuthorizedClientProvider();
  771. jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerTokenResponseClient);
  772. ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
  773. ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
  774. .provider(jwtBearerAuthorizedClientProvider)
  775. .build();
  776. ...
  777. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
  778. ----
  779. Kotlin::
  780. +
  781. [source,kotlin,role="secondary"]
  782. ----
  783. // Customize
  784. val jwtBearerTokenResponseClient: ReactiveOAuth2AccessTokenResponseClient<JwtBearerGrantRequest> = ...
  785. val jwtBearerAuthorizedClientProvider = JwtBearerReactiveOAuth2AuthorizedClientProvider()
  786. jwtBearerAuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerTokenResponseClient)
  787. val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
  788. .provider(jwtBearerAuthorizedClientProvider)
  789. .build()
  790. ...
  791. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
  792. ----
  793. ======
  794. === Using the Access Token
  795. Given the following Spring Boot properties for an OAuth 2.0 Client registration:
  796. [source,yaml]
  797. ----
  798. spring:
  799. security:
  800. oauth2:
  801. client:
  802. registration:
  803. okta:
  804. client-id: okta-client-id
  805. client-secret: okta-client-secret
  806. authorization-grant-type: urn:ietf:params:oauth:grant-type:jwt-bearer
  807. scope: read
  808. provider:
  809. okta:
  810. token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token
  811. ----
  812. ...and the `OAuth2AuthorizedClientManager` `@Bean`:
  813. [tabs]
  814. ======
  815. Java::
  816. +
  817. [source,java,role="primary"]
  818. ----
  819. @Bean
  820. public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
  821. ReactiveClientRegistrationRepository clientRegistrationRepository,
  822. ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
  823. JwtBearerReactiveOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider =
  824. new JwtBearerReactiveOAuth2AuthorizedClientProvider();
  825. ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
  826. ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
  827. .provider(jwtBearerAuthorizedClientProvider)
  828. .build();
  829. DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
  830. new DefaultReactiveOAuth2AuthorizedClientManager(
  831. clientRegistrationRepository, authorizedClientRepository);
  832. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
  833. return authorizedClientManager;
  834. }
  835. ----
  836. Kotlin::
  837. +
  838. [source,kotlin,role="secondary"]
  839. ----
  840. @Bean
  841. fun authorizedClientManager(
  842. clientRegistrationRepository: ReactiveClientRegistrationRepository,
  843. authorizedClientRepository: ServerOAuth2AuthorizedClientRepository): ReactiveOAuth2AuthorizedClientManager {
  844. val jwtBearerAuthorizedClientProvider = JwtBearerReactiveOAuth2AuthorizedClientProvider()
  845. val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
  846. .provider(jwtBearerAuthorizedClientProvider)
  847. .build()
  848. val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager(
  849. clientRegistrationRepository, authorizedClientRepository)
  850. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
  851. return authorizedClientManager
  852. }
  853. ----
  854. ======
  855. You may obtain the `OAuth2AccessToken` as follows:
  856. [tabs]
  857. ======
  858. Java::
  859. +
  860. [source,java,role="primary"]
  861. ----
  862. @RestController
  863. public class OAuth2ResourceServerController {
  864. @Autowired
  865. private ReactiveOAuth2AuthorizedClientManager authorizedClientManager;
  866. @GetMapping("/resource")
  867. public Mono<String> resource(JwtAuthenticationToken jwtAuthentication, ServerWebExchange exchange) {
  868. OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
  869. .principal(jwtAuthentication)
  870. .build();
  871. return this.authorizedClientManager.authorize(authorizeRequest)
  872. .map(OAuth2AuthorizedClient::getAccessToken)
  873. ...
  874. }
  875. }
  876. ----
  877. Kotlin::
  878. +
  879. [source,kotlin,role="secondary"]
  880. ----
  881. class OAuth2ResourceServerController {
  882. @Autowired
  883. private lateinit var authorizedClientManager: ReactiveOAuth2AuthorizedClientManager
  884. @GetMapping("/resource")
  885. fun resource(jwtAuthentication: JwtAuthenticationToken, exchange: ServerWebExchange): Mono<String> {
  886. val authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
  887. .principal(jwtAuthentication)
  888. .build()
  889. return authorizedClientManager.authorize(authorizeRequest)
  890. .map { it.accessToken }
  891. ...
  892. }
  893. }
  894. ----
  895. ======
  896. [NOTE]
  897. `JwtBearerReactiveOAuth2AuthorizedClientProvider` resolves the `Jwt` assertion via `OAuth2AuthorizationContext.getPrincipal().getPrincipal()` by default, hence the use of `JwtAuthenticationToken` in the preceding example.
  898. [TIP]
  899. If you need to resolve the `Jwt` assertion from a different source, you can provide `JwtBearerReactiveOAuth2AuthorizedClientProvider.setJwtAssertionResolver()` with a custom `Function<OAuth2AuthorizationContext, Mono<Jwt>>`.
  900. [[oauth2Client-token-exchange-grant]]
  901. == Token Exchange
  902. [NOTE]
  903. Please refer to OAuth 2.0 Token Exchange for further details on the https://datatracker.ietf.org/doc/html/rfc8693[Token Exchange] grant.
  904. === Requesting an Access Token
  905. [NOTE]
  906. Please refer to the https://datatracker.ietf.org/doc/html/rfc8693#section-2[Token Exchange Request and Response] protocol flow for the Token Exchange grant.
  907. The default implementation of `ReactiveOAuth2AccessTokenResponseClient` for the Token Exchange grant is `WebClientReactiveTokenExchangeTokenResponseClient`, which uses a `WebClient` when requesting an access token at the Authorization Server’s Token Endpoint.
  908. The `WebClientReactiveTokenExchangeTokenResponseClient` is quite flexible as it allows you to customize the pre-processing of the Token Request and/or post-handling of the Token Response.
  909. === Customizing the Access Token Request
  910. If you need to customize the pre-processing of the Token Request, you can provide `WebClientReactiveTokenExchangeTokenResponseClient.setParametersConverter()` with a custom `Converter<TokenExchangeGrantRequest, MultiValueMap<String, String>>`.
  911. The default implementation builds a `MultiValueMap<String, String>` containing only the `grant_type` parameter of a standard https://tools.ietf.org/html/rfc6749#section-4.4.2[OAuth 2.0 Access Token Request] which is used to construct the request.
  912. Other parameters required by the Token Exchange grant are added directly to the body of the request by the `WebClientReactiveTokenExchangeTokenResponseClient`.
  913. However, providing a custom `Converter`, would allow you to extend the standard Token Request and add custom parameter(s).
  914. [TIP]
  915. If you prefer to only add additional parameters, you can instead provide `WebClientReactiveTokenExchangeTokenResponseClient.addParametersConverter()` with a custom `Converter<TokenExchangeGrantRequest, MultiValueMap<String, String>>` which constructs an aggregate `Converter`.
  916. IMPORTANT: The custom `Converter` must return valid parameters of an OAuth 2.0 Access Token Request that is understood by the intended OAuth 2.0 Provider.
  917. === Customizing the Access Token Response
  918. On the other end, if you need to customize the post-handling of the Token Response, you will need to provide `WebClientReactiveTokenExchangeTokenResponseClient.setBodyExtractor()` with a custom configured `BodyExtractor<Mono<OAuth2AccessTokenResponse>, ReactiveHttpInputMessage>` that is used for converting the OAuth 2.0 Access Token Response to an `OAuth2AccessTokenResponse`.
  919. The default implementation provided by `OAuth2BodyExtractors.oauth2AccessTokenResponse()` parses the response and handles errors accordingly.
  920. === Customizing the `WebClient`
  921. Alternatively, if your requirements are more advanced, you can take full control of the request/response by simply providing `WebClientReactiveTokenExchangeTokenResponseClient.setWebClient()` with a custom configured `WebClient`.
  922. Whether you customize `WebClientReactiveTokenExchangeTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example:
  923. [tabs]
  924. ======
  925. Java::
  926. +
  927. [source,java,role="primary"]
  928. ----
  929. // Customize
  930. ReactiveOAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> tokenExchangeTokenResponseClient = ...
  931. TokenExchangeReactiveOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider = new TokenExchangeReactiveOAuth2AuthorizedClientProvider();
  932. tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeTokenResponseClient);
  933. ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
  934. ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
  935. .provider(tokenExchangeAuthorizedClientProvider)
  936. .build();
  937. ...
  938. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
  939. ----
  940. Kotlin::
  941. +
  942. [source,kotlin,role="secondary"]
  943. ----
  944. // Customize
  945. val tokenExchangeTokenResponseClient: ReactiveOAuth2AccessTokenResponseClient<TokenExchangeGrantRequest> = ...
  946. val tokenExchangeAuthorizedClientProvider = TokenExchangeReactiveOAuth2AuthorizedClientProvider()
  947. tokenExchangeAuthorizedClientProvider.setAccessTokenResponseClient(tokenExchangeTokenResponseClient)
  948. val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
  949. .provider(tokenExchangeAuthorizedClientProvider)
  950. .build()
  951. ...
  952. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
  953. ----
  954. ======
  955. === Using the Access Token
  956. Given the following Spring Boot properties for an OAuth 2.0 Client registration:
  957. [source,yaml]
  958. ----
  959. spring:
  960. security:
  961. oauth2:
  962. client:
  963. registration:
  964. okta:
  965. client-id: okta-client-id
  966. client-secret: okta-client-secret
  967. authorization-grant-type: urn:ietf:params:oauth:grant-type:token-exchange
  968. scope: read
  969. provider:
  970. okta:
  971. token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token
  972. ----
  973. ...and the `OAuth2AuthorizedClientManager` `@Bean`:
  974. [tabs]
  975. ======
  976. Java::
  977. +
  978. [source,java,role="primary"]
  979. ----
  980. @Bean
  981. public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
  982. ReactiveClientRegistrationRepository clientRegistrationRepository,
  983. ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
  984. TokenExchangeReactiveOAuth2AuthorizedClientProvider tokenExchangeAuthorizedClientProvider =
  985. new TokenExchangeReactiveOAuth2AuthorizedClientProvider();
  986. ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
  987. ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
  988. .provider(tokenExchangeAuthorizedClientProvider)
  989. .build();
  990. DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
  991. new DefaultReactiveOAuth2AuthorizedClientManager(
  992. clientRegistrationRepository, authorizedClientRepository);
  993. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
  994. return authorizedClientManager;
  995. }
  996. ----
  997. Kotlin::
  998. +
  999. [source,kotlin,role="secondary"]
  1000. ----
  1001. @Bean
  1002. fun authorizedClientManager(
  1003. clientRegistrationRepository: ReactiveClientRegistrationRepository,
  1004. authorizedClientRepository: ServerOAuth2AuthorizedClientRepository): ReactiveOAuth2AuthorizedClientManager {
  1005. val tokenExchangeAuthorizedClientProvider = TokenExchangeReactiveOAuth2AuthorizedClientProvider()
  1006. val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
  1007. .provider(tokenExchangeAuthorizedClientProvider)
  1008. .build()
  1009. val authorizedClientManager = DefaultReactiveOAuth2AuthorizedClientManager(
  1010. clientRegistrationRepository, authorizedClientRepository)
  1011. authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
  1012. return authorizedClientManager
  1013. }
  1014. ----
  1015. ======
  1016. You may obtain the `OAuth2AccessToken` as follows:
  1017. [tabs]
  1018. ======
  1019. Java::
  1020. +
  1021. [source,java,role="primary"]
  1022. ----
  1023. @RestController
  1024. public class OAuth2ResourceServerController {
  1025. @Autowired
  1026. private ReactiveOAuth2AuthorizedClientManager authorizedClientManager;
  1027. @GetMapping("/resource")
  1028. public Mono<String> resource(JwtAuthenticationToken jwtAuthentication) {
  1029. OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
  1030. .principal(jwtAuthentication)
  1031. .build();
  1032. return this.authorizedClientManager.authorize(authorizeRequest)
  1033. .map(OAuth2AuthorizedClient::getAccessToken)
  1034. ...
  1035. }
  1036. }
  1037. ----
  1038. Kotlin::
  1039. +
  1040. [source,kotlin,role="secondary"]
  1041. ----
  1042. class OAuth2ResourceServerController {
  1043. @Autowired
  1044. private lateinit var authorizedClientManager: ReactiveOAuth2AuthorizedClientManager
  1045. @GetMapping("/resource")
  1046. fun resource(jwtAuthentication: JwtAuthenticationToken): Mono<String> {
  1047. val authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("okta")
  1048. .principal(jwtAuthentication)
  1049. .build()
  1050. return authorizedClientManager.authorize(authorizeRequest)
  1051. .map { it.accessToken }
  1052. ...
  1053. }
  1054. }
  1055. ----
  1056. ======
  1057. [NOTE]
  1058. `TokenExchangeReactiveOAuth2AuthorizedClientProvider` resolves the subject token (as an `OAuth2Token`) via `OAuth2AuthorizationContext.getPrincipal().getPrincipal()` by default, hence the use of `JwtAuthenticationToken` in the preceding example.
  1059. An actor token is not resolved by default.
  1060. [TIP]
  1061. If you need to resolve the subject token from a different source, you can provide `TokenExchangeReactiveOAuth2AuthorizedClientProvider.setSubjectTokenResolver()` with a custom `Function<OAuth2AuthorizationContext, Mono<OAuth2Token>>`.
  1062. [TIP]
  1063. If you need to resolve an actor token, you can provide `TokenExchangeReactiveOAuth2AuthorizedClientProvider.setActorTokenResolver()` with a custom `Function<OAuth2AuthorizationContext, Mono<OAuth2Token>>`.