advanced.adoc 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775
  1. [[webflux-oauth2-login-advanced]]
  2. = Advanced Configuration
  3. The OAuth 2.0 Authorization Framework defines the https://tools.ietf.org/html/rfc6749#section-3[Protocol Endpoints] as follows:
  4. The authorization process utilizes two authorization server endpoints (HTTP resources):
  5. * Authorization Endpoint: Used by the client to obtain authorization from the resource owner via user-agent redirection.
  6. * Token Endpoint: Used by the client to exchange an authorization grant for an access token, typically with client authentication.
  7. As well as one client endpoint:
  8. * Redirection Endpoint: Used by the authorization server to return responses containing authorization credentials to the client via the resource owner user-agent.
  9. The OpenID Connect Core 1.0 specification defines the https://openid.net/specs/openid-connect-core-1_0.html#UserInfo[UserInfo Endpoint] as follows:
  10. The UserInfo Endpoint is an OAuth 2.0 Protected Resource that returns claims about the authenticated end-user.
  11. To obtain the requested claims about the end-user, the client makes a request to the UserInfo Endpoint by using an access token obtained through OpenID Connect Authentication.
  12. These claims are normally represented by a JSON object that contains a collection of name-value pairs for the claims.
  13. `ServerHttpSecurity.oauth2Login()` provides a number of configuration options for customizing OAuth 2.0 Login.
  14. The following code shows the complete configuration options available for the `oauth2Login()` DSL:
  15. .OAuth2 Login Configuration Options
  16. [tabs]
  17. ======
  18. Java::
  19. +
  20. [source,java,role="primary"]
  21. ----
  22. @EnableWebFluxSecurity
  23. public class OAuth2LoginSecurityConfig {
  24. @Bean
  25. SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
  26. http
  27. .oauth2Login(oauth2 -> oauth2
  28. .authenticationConverter(this.authenticationConverter())
  29. .authenticationMatcher(this.authenticationMatcher())
  30. .authenticationManager(this.authenticationManager())
  31. .authenticationSuccessHandler(this.authenticationSuccessHandler())
  32. .authenticationFailureHandler(this.authenticationFailureHandler())
  33. .clientRegistrationRepository(this.clientRegistrationRepository())
  34. .authorizedClientRepository(this.authorizedClientRepository())
  35. .authorizedClientService(this.authorizedClientService())
  36. .authorizationRequestResolver(this.authorizationRequestResolver())
  37. .authorizationRequestRepository(this.authorizationRequestRepository())
  38. .securityContextRepository(this.securityContextRepository())
  39. );
  40. return http.build();
  41. }
  42. }
  43. ----
  44. Kotlin::
  45. +
  46. [source,kotlin,role="secondary"]
  47. ----
  48. @EnableWebFluxSecurity
  49. class OAuth2LoginSecurityConfig {
  50. @Bean
  51. fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  52. return http {
  53. oauth2Login {
  54. authenticationConverter = authenticationConverter()
  55. authenticationMatcher = authenticationMatcher()
  56. authenticationManager = authenticationManager()
  57. authenticationSuccessHandler = authenticationSuccessHandler()
  58. authenticationFailureHandler = authenticationFailureHandler()
  59. clientRegistrationRepository = clientRegistrationRepository()
  60. authorizedClientRepository = authorizedClientRepository()
  61. authorizedClientService = authorizedClientService()
  62. authorizationRequestResolver = authorizationRequestResolver()
  63. authorizationRequestRepository = authorizationRequestRepository()
  64. securityContextRepository = securityContextRepository()
  65. }
  66. }
  67. }
  68. }
  69. ----
  70. ======
  71. The following sections go into more detail on each of the configuration options available:
  72. * <<webflux-oauth2-login-advanced-login-page, OAuth 2.0 Login Page>>
  73. * <<webflux-oauth2-login-advanced-redirection-endpoint, Redirection Endpoint>>
  74. * <<webflux-oauth2-login-advanced-userinfo-endpoint, UserInfo Endpoint>>
  75. * <<webflux-oauth2-login-advanced-idtoken-verify, ID Token Signature Verification>>
  76. * <<webflux-oauth2-login-advanced-oidc-logout, OpenID Connect 1.0 Logout>>
  77. [[webflux-oauth2-login-advanced-login-page]]
  78. == OAuth 2.0 Login Page
  79. By default, the OAuth 2.0 Login Page is auto-generated by the `LoginPageGeneratingWebFilter`.
  80. The default login page shows each configured OAuth Client with its `ClientRegistration.clientName` as a link, which is capable of initiating the Authorization Request (or OAuth 2.0 Login).
  81. [NOTE]
  82. In order for `LoginPageGeneratingWebFilter` to show links for configured OAuth Clients, the registered `ReactiveClientRegistrationRepository` needs to also implement `Iterable<ClientRegistration>`.
  83. See `InMemoryReactiveClientRegistrationRepository` for reference.
  84. The link's destination for each OAuth Client defaults to the following:
  85. `+"/oauth2/authorization/{registrationId}"+`
  86. The following line shows an example:
  87. [source,html]
  88. ----
  89. <a href="/oauth2/authorization/google">Google</a>
  90. ----
  91. To override the default login page, configure the `exceptionHandling().authenticationEntryPoint()` and (optionally) `oauth2Login().authorizationRequestResolver()`.
  92. The following listing shows an example:
  93. .OAuth2 Login Page Configuration
  94. [tabs]
  95. ======
  96. Java::
  97. +
  98. [source,java,role="primary"]
  99. ----
  100. @EnableWebFluxSecurity
  101. public class OAuth2LoginSecurityConfig {
  102. @Bean
  103. public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  104. http
  105. .exceptionHandling(exceptionHandling -> exceptionHandling
  106. .authenticationEntryPoint(new RedirectServerAuthenticationEntryPoint("/login/oauth2"))
  107. )
  108. .oauth2Login(oauth2 -> oauth2
  109. .authorizationRequestResolver(this.authorizationRequestResolver())
  110. );
  111. return http.build();
  112. }
  113. private ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver() {
  114. ServerWebExchangeMatcher authorizationRequestMatcher =
  115. new PathPatternParserServerWebExchangeMatcher(
  116. "/login/oauth2/authorization/{registrationId}");
  117. return new DefaultServerOAuth2AuthorizationRequestResolver(
  118. this.clientRegistrationRepository(), authorizationRequestMatcher);
  119. }
  120. ...
  121. }
  122. ----
  123. Kotlin::
  124. +
  125. [source,kotlin,role="secondary"]
  126. ----
  127. @EnableWebFluxSecurity
  128. class OAuth2LoginSecurityConfig {
  129. @Bean
  130. fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  131. return http {
  132. exceptionHandling {
  133. authenticationEntryPoint = RedirectServerAuthenticationEntryPoint("/login/oauth2")
  134. }
  135. oauth2Login {
  136. authorizationRequestResolver = authorizationRequestResolver()
  137. }
  138. }
  139. }
  140. private fun authorizationRequestResolver(): ServerOAuth2AuthorizationRequestResolver {
  141. val authorizationRequestMatcher: ServerWebExchangeMatcher = PathPatternParserServerWebExchangeMatcher(
  142. "/login/oauth2/authorization/{registrationId}"
  143. )
  144. return DefaultServerOAuth2AuthorizationRequestResolver(
  145. clientRegistrationRepository(), authorizationRequestMatcher
  146. )
  147. }
  148. ...
  149. }
  150. ----
  151. ======
  152. [IMPORTANT]
  153. You need to provide a `@Controller` with a `@RequestMapping("/login/oauth2")` that is capable of rendering the custom login page.
  154. [TIP]
  155. ====
  156. As noted earlier, configuring `oauth2Login().authorizationRequestResolver()` is optional.
  157. However, if you choose to customize it, ensure the link to each OAuth Client matches the pattern provided through the `ServerWebExchangeMatcher`.
  158. The following line shows an example:
  159. [source,html]
  160. ----
  161. <a href="/login/oauth2/authorization/google">Google</a>
  162. ----
  163. ====
  164. [[webflux-oauth2-login-advanced-redirection-endpoint]]
  165. == Redirection Endpoint
  166. The Redirection Endpoint is used by the Authorization Server for returning the Authorization Response (which contains the authorization credentials) to the client via the Resource Owner user-agent.
  167. [TIP]
  168. OAuth 2.0 Login leverages the Authorization Code Grant.
  169. Therefore, the authorization credential is the authorization code.
  170. The default Authorization Response redirection endpoint is `+/login/oauth2/code/{registrationId}+`.
  171. If you would like to customize the Authorization Response redirection endpoint, configure it as shown in the following example:
  172. .Redirection Endpoint Configuration
  173. [tabs]
  174. ======
  175. Java::
  176. +
  177. [source,java,role="primary"]
  178. ----
  179. @EnableWebFluxSecurity
  180. public class OAuth2LoginSecurityConfig {
  181. @Bean
  182. public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  183. http
  184. .oauth2Login(oauth2 -> oauth2
  185. .authenticationMatcher(new PathPatternParserServerWebExchangeMatcher("/login/oauth2/callback/{registrationId}"))
  186. );
  187. return http.build();
  188. }
  189. }
  190. ----
  191. Kotlin::
  192. +
  193. [source,kotlin,role="secondary"]
  194. ----
  195. @EnableWebFluxSecurity
  196. class OAuth2LoginSecurityConfig {
  197. @Bean
  198. fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  199. return http {
  200. oauth2Login {
  201. authenticationMatcher = PathPatternParserServerWebExchangeMatcher("/login/oauth2/callback/{registrationId}")
  202. }
  203. }
  204. }
  205. }
  206. ----
  207. ======
  208. [IMPORTANT]
  209. ====
  210. You also need to ensure the `ClientRegistration.redirectUri` matches the custom Authorization Response redirection endpoint.
  211. The following listing shows an example:
  212. [tabs]
  213. ======
  214. Java::
  215. +
  216. [source,java,role="primary",attrs="-attributes"]
  217. ----
  218. return CommonOAuth2Provider.GOOGLE.getBuilder("google")
  219. .clientId("google-client-id")
  220. .clientSecret("google-client-secret")
  221. .redirectUri("{baseUrl}/login/oauth2/callback/{registrationId}")
  222. .build();
  223. ----
  224. Kotlin::
  225. +
  226. [source,kotlin,role="secondary",attrs="-attributes"]
  227. ----
  228. return CommonOAuth2Provider.GOOGLE.getBuilder("google")
  229. .clientId("google-client-id")
  230. .clientSecret("google-client-secret")
  231. .redirectUri("{baseUrl}/login/oauth2/callback/{registrationId}")
  232. .build()
  233. ----
  234. ======
  235. ====
  236. [[webflux-oauth2-login-advanced-userinfo-endpoint]]
  237. == UserInfo Endpoint
  238. The UserInfo Endpoint includes a number of configuration options, as described in the following sub-sections:
  239. * <<webflux-oauth2-login-advanced-map-authorities, Mapping User Authorities>>
  240. * <<webflux-oauth2-login-advanced-oauth2-user-service, OAuth 2.0 UserService>>
  241. * <<webflux-oauth2-login-advanced-oidc-user-service, OpenID Connect 1.0 UserService>>
  242. [[webflux-oauth2-login-advanced-map-authorities]]
  243. === Mapping User Authorities
  244. After the user successfully authenticates with the OAuth 2.0 Provider, the `OAuth2User.getAuthorities()` (or `OidcUser.getAuthorities()`) may be mapped to a new set of `GrantedAuthority` instances, which will be supplied to `OAuth2AuthenticationToken` when completing the authentication.
  245. [TIP]
  246. `OAuth2AuthenticationToken.getAuthorities()` is used for authorizing requests, such as in `hasRole('USER')` or `hasRole('ADMIN')`.
  247. There are a couple of options to choose from when mapping user authorities:
  248. * <<webflux-oauth2-login-advanced-map-authorities-grantedauthoritiesmapper, Using a GrantedAuthoritiesMapper>>
  249. * <<webflux-oauth2-login-advanced-map-authorities-reactiveoauth2userservice, Delegation-based strategy with ReactiveOAuth2UserService>>
  250. [[webflux-oauth2-login-advanced-map-authorities-grantedauthoritiesmapper]]
  251. ==== Using a GrantedAuthoritiesMapper
  252. Register a `GrantedAuthoritiesMapper` `@Bean` to have it automatically applied to the configuration, as shown in the following example:
  253. .Granted Authorities Mapper Configuration
  254. [tabs]
  255. ======
  256. Java::
  257. +
  258. [source,java,role="primary"]
  259. ----
  260. @EnableWebFluxSecurity
  261. public class OAuth2LoginSecurityConfig {
  262. @Bean
  263. public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  264. http
  265. ...
  266. .oauth2Login(withDefaults());
  267. return http.build();
  268. }
  269. @Bean
  270. public GrantedAuthoritiesMapper userAuthoritiesMapper() {
  271. return (authorities) -> {
  272. Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
  273. authorities.forEach(authority -> {
  274. if (OidcUserAuthority.class.isInstance(authority)) {
  275. OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority;
  276. OidcIdToken idToken = oidcUserAuthority.getIdToken();
  277. OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
  278. // Map the claims found in idToken and/or userInfo
  279. // to one or more GrantedAuthority's and add it to mappedAuthorities
  280. } else if (OAuth2UserAuthority.class.isInstance(authority)) {
  281. OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
  282. Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
  283. // Map the attributes found in userAttributes
  284. // to one or more GrantedAuthority's and add it to mappedAuthorities
  285. }
  286. });
  287. return mappedAuthorities;
  288. };
  289. }
  290. }
  291. ----
  292. Kotlin::
  293. +
  294. [source,kotlin,role="secondary"]
  295. ----
  296. @EnableWebFluxSecurity
  297. class OAuth2LoginSecurityConfig {
  298. @Bean
  299. fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  300. return http {
  301. oauth2Login { }
  302. }
  303. }
  304. @Bean
  305. fun userAuthoritiesMapper(): GrantedAuthoritiesMapper = GrantedAuthoritiesMapper { authorities: Collection<GrantedAuthority> ->
  306. val mappedAuthorities = emptySet<GrantedAuthority>()
  307. authorities.forEach { authority ->
  308. if (authority is OidcUserAuthority) {
  309. val idToken = authority.idToken
  310. val userInfo = authority.userInfo
  311. // Map the claims found in idToken and/or userInfo
  312. // to one or more GrantedAuthority's and add it to mappedAuthorities
  313. } else if (authority is OAuth2UserAuthority) {
  314. val userAttributes = authority.attributes
  315. // Map the attributes found in userAttributes
  316. // to one or more GrantedAuthority's and add it to mappedAuthorities
  317. }
  318. }
  319. mappedAuthorities
  320. }
  321. }
  322. ----
  323. ======
  324. [[webflux-oauth2-login-advanced-map-authorities-reactiveoauth2userservice]]
  325. ==== Delegation-based strategy with ReactiveOAuth2UserService
  326. This strategy is advanced compared to using a `GrantedAuthoritiesMapper`, however, it's also more flexible as it gives you access to the `OAuth2UserRequest` and `OAuth2User` (when using an OAuth 2.0 UserService) or `OidcUserRequest` and `OidcUser` (when using an OpenID Connect 1.0 UserService).
  327. The `OAuth2UserRequest` (and `OidcUserRequest`) provides you access to the associated `OAuth2AccessToken`, which is very useful in the cases where the _delegator_ needs to fetch authority information from a protected resource before it can map the custom authorities for the user.
  328. The following example shows how to implement and configure a delegation-based strategy using an OpenID Connect 1.0 UserService:
  329. .ReactiveOAuth2UserService Configuration
  330. [tabs]
  331. ======
  332. Java::
  333. +
  334. [source,java,role="primary"]
  335. ----
  336. @EnableWebFluxSecurity
  337. public class OAuth2LoginSecurityConfig {
  338. @Bean
  339. public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  340. http
  341. ...
  342. .oauth2Login(withDefaults());
  343. return http.build();
  344. }
  345. @Bean
  346. public ReactiveOAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
  347. final OidcReactiveOAuth2UserService delegate = new OidcReactiveOAuth2UserService();
  348. return (userRequest) -> {
  349. // Delegate to the default implementation for loading a user
  350. return delegate.loadUser(userRequest)
  351. .flatMap((oidcUser) -> {
  352. OAuth2AccessToken accessToken = userRequest.getAccessToken();
  353. Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
  354. // TODO
  355. // 1) Fetch the authority information from the protected resource using accessToken
  356. // 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities
  357. // 3) Create a copy of oidcUser but use the mappedAuthorities instead
  358. oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
  359. return Mono.just(oidcUser);
  360. });
  361. };
  362. }
  363. }
  364. ----
  365. Kotlin::
  366. +
  367. [source,kotlin,role="secondary"]
  368. ----
  369. @EnableWebFluxSecurity
  370. class OAuth2LoginSecurityConfig {
  371. @Bean
  372. fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  373. return http {
  374. oauth2Login { }
  375. }
  376. }
  377. @Bean
  378. fun oidcUserService(): ReactiveOAuth2UserService<OidcUserRequest, OidcUser> {
  379. val delegate = OidcReactiveOAuth2UserService()
  380. return ReactiveOAuth2UserService { userRequest ->
  381. // Delegate to the default implementation for loading a user
  382. delegate.loadUser(userRequest)
  383. .flatMap { oidcUser ->
  384. val accessToken = userRequest.accessToken
  385. val mappedAuthorities = mutableSetOf<GrantedAuthority>()
  386. // TODO
  387. // 1) Fetch the authority information from the protected resource using accessToken
  388. // 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities
  389. // 3) Create a copy of oidcUser but use the mappedAuthorities instead
  390. val mappedOidcUser = DefaultOidcUser(mappedAuthorities, oidcUser.idToken, oidcUser.userInfo)
  391. Mono.just(mappedOidcUser)
  392. }
  393. }
  394. }
  395. }
  396. ----
  397. ======
  398. [[webflux-oauth2-login-advanced-oauth2-user-service]]
  399. === OAuth 2.0 UserService
  400. `DefaultReactiveOAuth2UserService` is an implementation of a `ReactiveOAuth2UserService` that supports standard OAuth 2.0 Provider's.
  401. [NOTE]
  402. `ReactiveOAuth2UserService` obtains the user attributes of the end-user (the resource owner) from the UserInfo Endpoint (by using the access token granted to the client during the authorization flow) and returns an `AuthenticatedPrincipal` in the form of an `OAuth2User`.
  403. `DefaultReactiveOAuth2UserService` uses a `WebClient` when requesting the user attributes at the UserInfo Endpoint.
  404. If you need to customize the pre-processing of the UserInfo Request and/or the post-handling of the UserInfo Response, you will need to provide `DefaultReactiveOAuth2UserService.setWebClient()` with a custom configured `WebClient`.
  405. Whether you customize `DefaultReactiveOAuth2UserService` or provide your own implementation of `ReactiveOAuth2UserService`, you'll need to configure it as shown in the following example:
  406. [tabs]
  407. ======
  408. Java::
  409. +
  410. [source,java,role="primary"]
  411. ----
  412. @EnableWebFluxSecurity
  413. public class OAuth2LoginSecurityConfig {
  414. @Bean
  415. public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  416. http
  417. ...
  418. .oauth2Login(withDefaults());
  419. return http.build();
  420. }
  421. @Bean
  422. public ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
  423. ...
  424. }
  425. }
  426. ----
  427. Kotlin::
  428. +
  429. [source,kotlin,role="secondary"]
  430. ----
  431. @EnableWebFluxSecurity
  432. class OAuth2LoginSecurityConfig {
  433. @Bean
  434. fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  435. return http {
  436. oauth2Login { }
  437. }
  438. }
  439. @Bean
  440. fun oauth2UserService(): ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> {
  441. // ...
  442. }
  443. }
  444. ----
  445. ======
  446. [[webflux-oauth2-login-advanced-oidc-user-service]]
  447. === OpenID Connect 1.0 UserService
  448. `OidcReactiveOAuth2UserService` is an implementation of a `ReactiveOAuth2UserService` that supports OpenID Connect 1.0 Provider's.
  449. The `OidcReactiveOAuth2UserService` leverages the `DefaultReactiveOAuth2UserService` when requesting the user attributes at the UserInfo Endpoint.
  450. If you need to customize the pre-processing of the UserInfo Request and/or the post-handling of the UserInfo Response, you will need to provide `OidcReactiveOAuth2UserService.setOauth2UserService()` with a custom configured `ReactiveOAuth2UserService`.
  451. Whether you customize `OidcReactiveOAuth2UserService` or provide your own implementation of `ReactiveOAuth2UserService` for OpenID Connect 1.0 Provider's, you'll need to configure it as shown in the following example:
  452. [tabs]
  453. ======
  454. Java::
  455. +
  456. [source,java,role="primary"]
  457. ----
  458. @EnableWebFluxSecurity
  459. public class OAuth2LoginSecurityConfig {
  460. @Bean
  461. public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  462. http
  463. ...
  464. .oauth2Login(withDefaults());
  465. return http.build();
  466. }
  467. @Bean
  468. public ReactiveOAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
  469. ...
  470. }
  471. }
  472. ----
  473. Kotlin::
  474. +
  475. [source,kotlin,role="secondary"]
  476. ----
  477. @EnableWebFluxSecurity
  478. class OAuth2LoginSecurityConfig {
  479. @Bean
  480. fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  481. return http {
  482. oauth2Login { }
  483. }
  484. }
  485. @Bean
  486. fun oidcUserService(): ReactiveOAuth2UserService<OidcUserRequest, OidcUser> {
  487. // ...
  488. }
  489. }
  490. ----
  491. ======
  492. [[webflux-oauth2-login-advanced-idtoken-verify]]
  493. == ID Token Signature Verification
  494. OpenID Connect 1.0 Authentication introduces the https://openid.net/specs/openid-connect-core-1_0.html#IDToken[ID Token], which is a security token that contains Claims about the Authentication of an End-User by an Authorization Server when used by a Client.
  495. The ID Token is represented as a https://tools.ietf.org/html/rfc7519[JSON Web Token] (JWT) and MUST be signed using https://tools.ietf.org/html/rfc7515[JSON Web Signature] (JWS).
  496. The `ReactiveOidcIdTokenDecoderFactory` provides a `ReactiveJwtDecoder` used for `OidcIdToken` signature verification. The default algorithm is `RS256` but may be different when assigned during client registration.
  497. For these cases, a resolver may be configured to return the expected JWS algorithm assigned for a specific client.
  498. The JWS algorithm resolver is a `Function` that accepts a `ClientRegistration` and returns the expected `JwsAlgorithm` for the client, eg. `SignatureAlgorithm.RS256` or `MacAlgorithm.HS256`
  499. The following code shows how to configure the `OidcIdTokenDecoderFactory` `@Bean` to default to `MacAlgorithm.HS256` for all `ClientRegistration`:
  500. [tabs]
  501. ======
  502. Java::
  503. +
  504. [source,java,role="primary"]
  505. ----
  506. @Bean
  507. public ReactiveJwtDecoderFactory<ClientRegistration> idTokenDecoderFactory() {
  508. ReactiveOidcIdTokenDecoderFactory idTokenDecoderFactory = new ReactiveOidcIdTokenDecoderFactory();
  509. idTokenDecoderFactory.setJwsAlgorithmResolver(clientRegistration -> MacAlgorithm.HS256);
  510. return idTokenDecoderFactory;
  511. }
  512. ----
  513. Kotlin::
  514. +
  515. [source,kotlin,role="secondary"]
  516. ----
  517. @Bean
  518. fun idTokenDecoderFactory(): ReactiveJwtDecoderFactory<ClientRegistration> {
  519. val idTokenDecoderFactory = ReactiveOidcIdTokenDecoderFactory()
  520. idTokenDecoderFactory.setJwsAlgorithmResolver { MacAlgorithm.HS256 }
  521. return idTokenDecoderFactory
  522. }
  523. ----
  524. ======
  525. [NOTE]
  526. For MAC based algorithms such as `HS256`, `HS384` or `HS512`, the `client-secret` corresponding to the `client-id` is used as the symmetric key for signature verification.
  527. [TIP]
  528. If more than one `ClientRegistration` is configured for OpenID Connect 1.0 Authentication, the JWS algorithm resolver may evaluate the provided `ClientRegistration` to determine which algorithm to return.
  529. [[webflux-oauth2-login-advanced-oidc-logout]]
  530. == OpenID Connect 1.0 Logout
  531. OpenID Connect Session Management 1.0 allows the ability to log out the End-User at the Provider using the Client.
  532. One of the strategies available is https://openid.net/specs/openid-connect-rpinitiated-1_0.html[RP-Initiated Logout].
  533. If the OpenID Provider supports both Session Management and https://openid.net/specs/openid-connect-discovery-1_0.html[Discovery], the client may obtain the `end_session_endpoint` `URL` from the OpenID Provider's https://openid.net/specs/openid-connect-session-1_0.html#OPMetadata[Discovery Metadata].
  534. This can be achieved by configuring the `ClientRegistration` with the `issuer-uri`, as in the following example:
  535. [source,yaml]
  536. ----
  537. spring:
  538. security:
  539. oauth2:
  540. client:
  541. registration:
  542. okta:
  543. client-id: okta-client-id
  544. client-secret: okta-client-secret
  545. ...
  546. provider:
  547. okta:
  548. issuer-uri: https://dev-1234.oktapreview.com
  549. ----
  550. ...and the `OidcClientInitiatedServerLogoutSuccessHandler`, which implements RP-Initiated Logout, may be configured as follows:
  551. [tabs]
  552. ======
  553. Java::
  554. +
  555. [source,java,role="primary"]
  556. ----
  557. @EnableWebFluxSecurity
  558. public class OAuth2LoginSecurityConfig {
  559. @Autowired
  560. private ReactiveClientRegistrationRepository clientRegistrationRepository;
  561. @Bean
  562. public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  563. http
  564. .authorizeExchange(authorize -> authorize
  565. .anyExchange().authenticated()
  566. )
  567. .oauth2Login(withDefaults())
  568. .logout(logout -> logout
  569. .logoutSuccessHandler(oidcLogoutSuccessHandler())
  570. );
  571. return http.build();
  572. }
  573. private ServerLogoutSuccessHandler oidcLogoutSuccessHandler() {
  574. OidcClientInitiatedServerLogoutSuccessHandler oidcLogoutSuccessHandler =
  575. new OidcClientInitiatedServerLogoutSuccessHandler(this.clientRegistrationRepository);
  576. // Sets the location that the End-User's User Agent will be redirected to
  577. // after the logout has been performed at the Provider
  578. oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");
  579. return oidcLogoutSuccessHandler;
  580. }
  581. }
  582. ----
  583. Kotlin::
  584. +
  585. [source,kotlin,role="secondary"]
  586. ----
  587. @EnableWebFluxSecurity
  588. class OAuth2LoginSecurityConfig {
  589. @Autowired
  590. private lateinit var clientRegistrationRepository: ReactiveClientRegistrationRepository
  591. @Bean
  592. fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  593. return http {
  594. authorizeExchange {
  595. authorize(anyExchange, authenticated)
  596. }
  597. oauth2Login { }
  598. logout {
  599. logoutSuccessHandler = oidcLogoutSuccessHandler()
  600. }
  601. }
  602. }
  603. private fun oidcLogoutSuccessHandler(): ServerLogoutSuccessHandler {
  604. val oidcLogoutSuccessHandler = OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository)
  605. // Sets the location that the End-User's User Agent will be redirected to
  606. // after the logout has been performed at the Provider
  607. oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}")
  608. return oidcLogoutSuccessHandler
  609. }
  610. }
  611. ----
  612. ======
  613. NOTE: `OidcClientInitiatedServerLogoutSuccessHandler` supports the `+{baseUrl}+` placeholder.
  614. If used, the application's base URL, like `https://app.example.org`, will replace it at request time.