2
0

advanced.adoc 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778
  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. ====
  17. .Java
  18. [source,java,role="primary"]
  19. ----
  20. @Configuration
  21. @EnableWebFluxSecurity
  22. public class OAuth2LoginSecurityConfig {
  23. @Bean
  24. SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
  25. http
  26. .oauth2Login(oauth2 -> oauth2
  27. .authenticationConverter(this.authenticationConverter())
  28. .authenticationMatcher(this.authenticationMatcher())
  29. .authenticationManager(this.authenticationManager())
  30. .authenticationSuccessHandler(this.authenticationSuccessHandler())
  31. .authenticationFailureHandler(this.authenticationFailureHandler())
  32. .clientRegistrationRepository(this.clientRegistrationRepository())
  33. .authorizedClientRepository(this.authorizedClientRepository())
  34. .authorizedClientService(this.authorizedClientService())
  35. .authorizationRequestResolver(this.authorizationRequestResolver())
  36. .authorizationRequestRepository(this.authorizationRequestRepository())
  37. .securityContextRepository(this.securityContextRepository())
  38. );
  39. return http.build();
  40. }
  41. }
  42. ----
  43. .Kotlin
  44. [source,kotlin,role="secondary"]
  45. ----
  46. @Configuration
  47. @EnableWebFluxSecurity
  48. class OAuth2LoginSecurityConfig {
  49. @Bean
  50. fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  51. http {
  52. oauth2Login {
  53. authenticationConverter = authenticationConverter()
  54. authenticationMatcher = authenticationMatcher()
  55. authenticationManager = authenticationManager()
  56. authenticationSuccessHandler = authenticationSuccessHandler()
  57. authenticationFailureHandler = authenticationFailureHandler()
  58. clientRegistrationRepository = clientRegistrationRepository()
  59. authorizedClientRepository = authorizedClientRepository()
  60. authorizedClientService = authorizedClientService()
  61. authorizationRequestResolver = authorizationRequestResolver()
  62. authorizationRequestRepository = authorizationRequestRepository()
  63. securityContextRepository = securityContextRepository()
  64. }
  65. }
  66. return http.build()
  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. ====
  95. .Java
  96. [source,java,role="primary",subs="-attributes"]
  97. ----
  98. @Configuration
  99. @EnableWebFluxSecurity
  100. public class OAuth2LoginSecurityConfig {
  101. @Bean
  102. public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  103. http
  104. .exceptionHandling(exceptionHandling -> exceptionHandling
  105. .authenticationEntryPoint(new RedirectServerAuthenticationEntryPoint("/login/oauth2"))
  106. )
  107. .oauth2Login(oauth2 -> oauth2
  108. .authorizationRequestResolver(this.authorizationRequestResolver())
  109. );
  110. return http.build();
  111. }
  112. private ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver() {
  113. ServerWebExchangeMatcher authorizationRequestMatcher =
  114. new PathPatternParserServerWebExchangeMatcher(
  115. "/login/oauth2/authorization/{registrationId}");
  116. return new DefaultServerOAuth2AuthorizationRequestResolver(
  117. this.clientRegistrationRepository(), authorizationRequestMatcher);
  118. }
  119. ...
  120. }
  121. ----
  122. .Kotlin
  123. [source,kotlin,role="secondary",subs="-attributes"]
  124. ----
  125. @Configuration
  126. @EnableWebFluxSecurity
  127. class OAuth2LoginSecurityConfig {
  128. @Bean
  129. fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  130. http {
  131. exceptionHandling {
  132. authenticationEntryPoint = RedirectServerAuthenticationEntryPoint("/login/oauth2")
  133. }
  134. oauth2Login {
  135. authorizationRequestResolver = authorizationRequestResolver()
  136. }
  137. }
  138. return http.build()
  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. ====
  174. .Java
  175. [source,java,role="primary",subs="-attributes"]
  176. ----
  177. @Configuration
  178. @EnableWebFluxSecurity
  179. public class OAuth2LoginSecurityConfig {
  180. @Bean
  181. public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  182. http
  183. .oauth2Login(oauth2 -> oauth2
  184. .authenticationMatcher(new PathPatternParserServerWebExchangeMatcher("/login/oauth2/callback/{registrationId}"))
  185. );
  186. return http.build();
  187. }
  188. }
  189. ----
  190. .Kotlin
  191. [source,kotlin,role="secondary",subs="-attributes"]
  192. ----
  193. @Configuration
  194. @EnableWebFluxSecurity
  195. class OAuth2LoginSecurityConfig {
  196. @Bean
  197. fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  198. http {
  199. oauth2Login {
  200. authenticationMatcher = PathPatternParserServerWebExchangeMatcher("/login/oauth2/callback/{registrationId}")
  201. }
  202. }
  203. return http.build()
  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. .Java
  213. [source,java,role="primary",subs="-attributes"]
  214. ----
  215. return CommonOAuth2Provider.GOOGLE.getBuilder("google")
  216. .clientId("google-client-id")
  217. .clientSecret("google-client-secret")
  218. .redirectUri("{baseUrl}/login/oauth2/callback/{registrationId}")
  219. .build();
  220. ----
  221. .Kotlin
  222. [source,kotlin,role="secondary",subs="-attributes"]
  223. ----
  224. return CommonOAuth2Provider.GOOGLE.getBuilder("google")
  225. .clientId("google-client-id")
  226. .clientSecret("google-client-secret")
  227. .redirectUri("{baseUrl}/login/oauth2/callback/{registrationId}")
  228. .build()
  229. ----
  230. ====
  231. [[webflux-oauth2-login-advanced-userinfo-endpoint]]
  232. == UserInfo Endpoint
  233. The UserInfo Endpoint includes a number of configuration options, as described in the following sub-sections:
  234. * <<webflux-oauth2-login-advanced-map-authorities, Mapping User Authorities>>
  235. * <<webflux-oauth2-login-advanced-oauth2-user-service, OAuth 2.0 UserService>>
  236. * <<webflux-oauth2-login-advanced-oidc-user-service, OpenID Connect 1.0 UserService>>
  237. [[webflux-oauth2-login-advanced-map-authorities]]
  238. === Mapping User Authorities
  239. After the user successfully authenticates with the OAuth 2.0 Provider, the `OAuth2User.getAuthorities()` (or `OidcUser.getAuthorities()`) contains a list of granted authorities populated from `OAuth2UserRequest.getAccessToken().getScopes()` and prefixed with `SCOPE_`.
  240. These granted authorities may be mapped to a new set of `GrantedAuthority` instances, which will be supplied to `OAuth2AuthenticationToken` when completing the authentication.
  241. [TIP]
  242. `OAuth2AuthenticationToken.getAuthorities()` is used for authorizing requests, such as in `hasRole('USER')` or `hasRole('ADMIN')`.
  243. There are a couple of options to choose from when mapping user authorities:
  244. * <<webflux-oauth2-login-advanced-map-authorities-grantedauthoritiesmapper, Using a GrantedAuthoritiesMapper>>
  245. * <<webflux-oauth2-login-advanced-map-authorities-reactiveoauth2userservice, Delegation-based strategy with ReactiveOAuth2UserService>>
  246. [[webflux-oauth2-login-advanced-map-authorities-grantedauthoritiesmapper]]
  247. ==== Using a GrantedAuthoritiesMapper
  248. The `GrantedAuthoritiesMapper` is given a list of granted authorities which contains a special authority of type `OAuth2UserAuthority` and the authority string `OAUTH2_USER` (or `OidcUserAuthority` and the authority string `OIDC_USER`).
  249. Register a `GrantedAuthoritiesMapper` `@Bean` to have it automatically applied to the configuration, as shown in the following example:
  250. .Granted Authorities Mapper Configuration
  251. ====
  252. .Java
  253. [source,java,role="primary"]
  254. ----
  255. @Configuration
  256. @EnableWebFluxSecurity
  257. public class OAuth2LoginSecurityConfig {
  258. @Bean
  259. public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  260. http
  261. ...
  262. .oauth2Login(withDefaults());
  263. return http.build();
  264. }
  265. @Bean
  266. public GrantedAuthoritiesMapper userAuthoritiesMapper() {
  267. return (authorities) -> {
  268. Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
  269. authorities.forEach(authority -> {
  270. if (OidcUserAuthority.class.isInstance(authority)) {
  271. OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority;
  272. OidcIdToken idToken = oidcUserAuthority.getIdToken();
  273. OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
  274. // Map the claims found in idToken and/or userInfo
  275. // to one or more GrantedAuthority's and add it to mappedAuthorities
  276. } else if (OAuth2UserAuthority.class.isInstance(authority)) {
  277. OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
  278. Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
  279. // Map the attributes found in userAttributes
  280. // to one or more GrantedAuthority's and add it to mappedAuthorities
  281. }
  282. });
  283. return mappedAuthorities;
  284. };
  285. }
  286. }
  287. ----
  288. .Kotlin
  289. [source,kotlin,role="secondary"]
  290. ----
  291. @Configuration
  292. @EnableWebFluxSecurity
  293. class OAuth2LoginSecurityConfig {
  294. @Bean
  295. fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  296. http {
  297. oauth2Login { }
  298. }
  299. return http.build()
  300. }
  301. @Bean
  302. fun userAuthoritiesMapper(): GrantedAuthoritiesMapper = GrantedAuthoritiesMapper { authorities: Collection<GrantedAuthority> ->
  303. val mappedAuthorities = emptySet<GrantedAuthority>()
  304. authorities.forEach { authority ->
  305. if (authority is OidcUserAuthority) {
  306. val idToken = authority.idToken
  307. val userInfo = authority.userInfo
  308. // Map the claims found in idToken and/or userInfo
  309. // to one or more GrantedAuthority's and add it to mappedAuthorities
  310. } else if (authority is OAuth2UserAuthority) {
  311. val userAttributes = authority.attributes
  312. // Map the attributes found in userAttributes
  313. // to one or more GrantedAuthority's and add it to mappedAuthorities
  314. }
  315. }
  316. mappedAuthorities
  317. }
  318. }
  319. ----
  320. ====
  321. [[webflux-oauth2-login-advanced-map-authorities-reactiveoauth2userservice]]
  322. ==== Delegation-based strategy with ReactiveOAuth2UserService
  323. 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).
  324. 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.
  325. The following example shows how to implement and configure a delegation-based strategy using an OpenID Connect 1.0 UserService:
  326. .ReactiveOAuth2UserService Configuration
  327. ====
  328. .Java
  329. [source,java,role="primary"]
  330. ----
  331. @Configuration
  332. @EnableWebFluxSecurity
  333. public class OAuth2LoginSecurityConfig {
  334. @Bean
  335. public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  336. http
  337. ...
  338. .oauth2Login(withDefaults());
  339. return http.build();
  340. }
  341. @Bean
  342. public ReactiveOAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
  343. final OidcReactiveOAuth2UserService delegate = new OidcReactiveOAuth2UserService();
  344. return (userRequest) -> {
  345. // Delegate to the default implementation for loading a user
  346. return delegate.loadUser(userRequest)
  347. .flatMap((oidcUser) -> {
  348. OAuth2AccessToken accessToken = userRequest.getAccessToken();
  349. Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
  350. // TODO
  351. // 1) Fetch the authority information from the protected resource using accessToken
  352. // 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities
  353. // 3) Create a copy of oidcUser but use the mappedAuthorities instead
  354. oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
  355. return Mono.just(oidcUser);
  356. });
  357. };
  358. }
  359. }
  360. ----
  361. .Kotlin
  362. [source,kotlin,role="secondary"]
  363. ----
  364. @Configuration
  365. @EnableWebFluxSecurity
  366. class OAuth2LoginSecurityConfig {
  367. @Bean
  368. fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  369. http {
  370. oauth2Login { }
  371. }
  372. return http.build()
  373. }
  374. @Bean
  375. fun oidcUserService(): ReactiveOAuth2UserService<OidcUserRequest, OidcUser> {
  376. val delegate = OidcReactiveOAuth2UserService()
  377. return ReactiveOAuth2UserService { userRequest ->
  378. // Delegate to the default implementation for loading a user
  379. delegate.loadUser(userRequest)
  380. .flatMap { oidcUser ->
  381. val accessToken = userRequest.accessToken
  382. val mappedAuthorities = mutableSetOf<GrantedAuthority>()
  383. // TODO
  384. // 1) Fetch the authority information from the protected resource using accessToken
  385. // 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities
  386. // 3) Create a copy of oidcUser but use the mappedAuthorities instead
  387. val mappedOidcUser = DefaultOidcUser(mappedAuthorities, oidcUser.idToken, oidcUser.userInfo)
  388. Mono.just(mappedOidcUser)
  389. }
  390. }
  391. }
  392. }
  393. ----
  394. ====
  395. [[webflux-oauth2-login-advanced-oauth2-user-service]]
  396. === OAuth 2.0 UserService
  397. `DefaultReactiveOAuth2UserService` is an implementation of a `ReactiveOAuth2UserService` that supports standard OAuth 2.0 Provider's.
  398. [NOTE]
  399. `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`.
  400. `DefaultReactiveOAuth2UserService` uses a `WebClient` when requesting the user attributes at the UserInfo Endpoint.
  401. 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`.
  402. Whether you customize `DefaultReactiveOAuth2UserService` or provide your own implementation of `ReactiveOAuth2UserService`, you'll need to configure it as shown in the following example:
  403. ====
  404. .Java
  405. [source,java,role="primary"]
  406. ----
  407. @Configuration
  408. @EnableWebFluxSecurity
  409. public class OAuth2LoginSecurityConfig {
  410. @Bean
  411. public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  412. http
  413. ...
  414. .oauth2Login(withDefaults());
  415. return http.build();
  416. }
  417. @Bean
  418. public ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
  419. ...
  420. }
  421. }
  422. ----
  423. .Kotlin
  424. [source,kotlin,role="secondary"]
  425. ----
  426. @Configuration
  427. @EnableWebFluxSecurity
  428. class OAuth2LoginSecurityConfig {
  429. @Bean
  430. fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  431. http {
  432. oauth2Login { }
  433. }
  434. return http.build()
  435. }
  436. @Bean
  437. fun oauth2UserService(): ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> {
  438. // ...
  439. }
  440. }
  441. ----
  442. ====
  443. [[webflux-oauth2-login-advanced-oidc-user-service]]
  444. === OpenID Connect 1.0 UserService
  445. `OidcReactiveOAuth2UserService` is an implementation of a `ReactiveOAuth2UserService` that supports OpenID Connect 1.0 Provider's.
  446. The `OidcReactiveOAuth2UserService` leverages the `DefaultReactiveOAuth2UserService` when requesting the user attributes at the UserInfo Endpoint.
  447. 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`.
  448. 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:
  449. ====
  450. .Java
  451. [source,java,role="primary"]
  452. ----
  453. @Configuration
  454. @EnableWebFluxSecurity
  455. public class OAuth2LoginSecurityConfig {
  456. @Bean
  457. public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  458. http
  459. ...
  460. .oauth2Login(withDefaults());
  461. return http.build();
  462. }
  463. @Bean
  464. public ReactiveOAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
  465. ...
  466. }
  467. }
  468. ----
  469. .Kotlin
  470. [source,kotlin,role="secondary"]
  471. ----
  472. @Configuration
  473. @EnableWebFluxSecurity
  474. class OAuth2LoginSecurityConfig {
  475. @Bean
  476. fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  477. http {
  478. oauth2Login { }
  479. }
  480. return http.build()
  481. }
  482. @Bean
  483. fun oidcUserService(): ReactiveOAuth2UserService<OidcUserRequest, OidcUser> {
  484. // ...
  485. }
  486. }
  487. ----
  488. ====
  489. [[webflux-oauth2-login-advanced-idtoken-verify]]
  490. == ID Token Signature Verification
  491. 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.
  492. 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).
  493. The `ReactiveOidcIdTokenDecoderFactory` provides a `ReactiveJwtDecoder` used for `OidcIdToken` signature verification. The default algorithm is `RS256` but may be different when assigned during client registration.
  494. For these cases, a resolver may be configured to return the expected JWS algorithm assigned for a specific client.
  495. 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`
  496. The following code shows how to configure the `OidcIdTokenDecoderFactory` `@Bean` to default to `MacAlgorithm.HS256` for all `ClientRegistration`:
  497. ====
  498. .Java
  499. [source,java,role="primary"]
  500. ----
  501. @Bean
  502. public ReactiveJwtDecoderFactory<ClientRegistration> idTokenDecoderFactory() {
  503. ReactiveOidcIdTokenDecoderFactory idTokenDecoderFactory = new ReactiveOidcIdTokenDecoderFactory();
  504. idTokenDecoderFactory.setJwsAlgorithmResolver(clientRegistration -> MacAlgorithm.HS256);
  505. return idTokenDecoderFactory;
  506. }
  507. ----
  508. .Kotlin
  509. [source,kotlin,role="secondary"]
  510. ----
  511. @Bean
  512. fun idTokenDecoderFactory(): ReactiveJwtDecoderFactory<ClientRegistration> {
  513. val idTokenDecoderFactory = ReactiveOidcIdTokenDecoderFactory()
  514. idTokenDecoderFactory.setJwsAlgorithmResolver { MacAlgorithm.HS256 }
  515. return idTokenDecoderFactory
  516. }
  517. ----
  518. ====
  519. [NOTE]
  520. 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.
  521. [TIP]
  522. 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.
  523. [[webflux-oauth2-login-advanced-oidc-logout]]
  524. == OpenID Connect 1.0 Logout
  525. OpenID Connect Session Management 1.0 allows the ability to log out the End-User at the Provider using the Client.
  526. One of the strategies available is https://openid.net/specs/openid-connect-rpinitiated-1_0.html[RP-Initiated Logout].
  527. 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].
  528. This can be achieved by configuring the `ClientRegistration` with the `issuer-uri`, as in the following example:
  529. [source,yaml]
  530. ----
  531. spring:
  532. security:
  533. oauth2:
  534. client:
  535. registration:
  536. okta:
  537. client-id: okta-client-id
  538. client-secret: okta-client-secret
  539. ...
  540. provider:
  541. okta:
  542. issuer-uri: https://dev-1234.oktapreview.com
  543. ----
  544. ...and the `OidcClientInitiatedServerLogoutSuccessHandler`, which implements RP-Initiated Logout, may be configured as follows:
  545. ====
  546. .Java
  547. [source,java,role="primary",subs="-attributes"]
  548. ----
  549. @Configuration
  550. @EnableWebFluxSecurity
  551. public class OAuth2LoginSecurityConfig {
  552. @Autowired
  553. private ReactiveClientRegistrationRepository clientRegistrationRepository;
  554. @Bean
  555. public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
  556. http
  557. .authorizeExchange(authorize -> authorize
  558. .anyExchange().authenticated()
  559. )
  560. .oauth2Login(withDefaults())
  561. .logout(logout -> logout
  562. .logoutSuccessHandler(oidcLogoutSuccessHandler())
  563. );
  564. return http.build();
  565. }
  566. private ServerLogoutSuccessHandler oidcLogoutSuccessHandler() {
  567. OidcClientInitiatedServerLogoutSuccessHandler oidcLogoutSuccessHandler =
  568. new OidcClientInitiatedServerLogoutSuccessHandler(this.clientRegistrationRepository);
  569. // Sets the location that the End-User's User Agent will be redirected to
  570. // after the logout has been performed at the Provider
  571. oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");
  572. return oidcLogoutSuccessHandler;
  573. }
  574. }
  575. ----
  576. .Kotlin
  577. [source,kotlin,role="secondary",subs="-attributes"]
  578. ----
  579. @Configuration
  580. @EnableWebFluxSecurity
  581. class OAuth2LoginSecurityConfig {
  582. @Autowired
  583. private lateinit var clientRegistrationRepository: ReactiveClientRegistrationRepository
  584. @Bean
  585. fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  586. http {
  587. authorizeExchange {
  588. authorize(anyExchange, authenticated)
  589. }
  590. oauth2Login { }
  591. logout {
  592. logoutSuccessHandler = oidcLogoutSuccessHandler()
  593. }
  594. }
  595. return http.build()
  596. }
  597. private fun oidcLogoutSuccessHandler(): ServerLogoutSuccessHandler {
  598. val oidcLogoutSuccessHandler = OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository)
  599. // Sets the location that the End-User's User Agent will be redirected to
  600. // after the logout has been performed at the Provider
  601. oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}")
  602. return oidcLogoutSuccessHandler
  603. }
  604. }
  605. ----
  606. ====
  607. NOTE: `OidcClientInitiatedServerLogoutSuccessHandler` supports the `+{baseUrl}+` placeholder.
  608. If used, the application's base URL, like `https://app.example.org`, will replace it at request time.