advanced.adoc 26 KB

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