advanced.adoc 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039
  1. [[oauth2login-advanced]]
  2. = Advanced Configuration
  3. `HttpSecurity.oauth2Login()` provides a number of configuration options for customizing OAuth 2.0 Login.
  4. The main configuration options are grouped into their protocol endpoint counterparts.
  5. For example, `oauth2Login().authorizationEndpoint()` allows configuring the _Authorization Endpoint_, whereas `oauth2Login().tokenEndpoint()` allows configuring the _Token Endpoint_.
  6. The following code shows an example:
  7. .Advanced OAuth2 Login Configuration
  8. [tabs]
  9. ======
  10. Java::
  11. +
  12. [source,java,role="primary"]
  13. ----
  14. @Configuration
  15. @EnableWebSecurity
  16. public class OAuth2LoginSecurityConfig {
  17. @Bean
  18. public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
  19. http
  20. .oauth2Login(oauth2 -> oauth2
  21. .authorizationEndpoint(authorization -> authorization
  22. ...
  23. )
  24. .redirectionEndpoint(redirection -> redirection
  25. ...
  26. )
  27. .tokenEndpoint(token -> token
  28. ...
  29. )
  30. .userInfoEndpoint(userInfo -> userInfo
  31. ...
  32. )
  33. );
  34. return http.build();
  35. }
  36. }
  37. ----
  38. Kotlin::
  39. +
  40. [source,kotlin,role="secondary"]
  41. ----
  42. @Configuration
  43. @EnableWebSecurity
  44. class OAuth2LoginSecurityConfig {
  45. @Bean
  46. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  47. http {
  48. oauth2Login {
  49. authorizationEndpoint {
  50. ...
  51. }
  52. redirectionEndpoint {
  53. ...
  54. }
  55. tokenEndpoint {
  56. ...
  57. }
  58. userInfoEndpoint {
  59. ...
  60. }
  61. }
  62. }
  63. return http.build()
  64. }
  65. }
  66. ----
  67. ======
  68. The main goal of the `oauth2Login()` DSL was to closely align with the naming, as defined in the specifications.
  69. The OAuth 2.0 Authorization Framework defines the https://tools.ietf.org/html/rfc6749#section-3[Protocol Endpoints] as follows:
  70. The authorization process uses two authorization server endpoints (HTTP resources):
  71. * Authorization Endpoint: Used by the client to obtain authorization from the resource owner through user-agent redirection.
  72. * Token Endpoint: Used by the client to exchange an authorization grant for an access token, typically with client authentication.
  73. The authorization process also uses one client endpoint:
  74. * Redirection Endpoint: Used by the authorization server to return responses that contain authorization credentials to the client through the resource owner user-agent.
  75. The OpenID Connect Core 1.0 specification defines the https://openid.net/specs/openid-connect-core-1_0.html#UserInfo[UserInfo Endpoint] as follows:
  76. The UserInfo Endpoint is an OAuth 2.0 Protected Resource that returns claims about the authenticated end-user.
  77. 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.
  78. These claims are normally represented by a JSON object that contains a collection of name-value pairs for the claims.
  79. The following code shows the complete configuration options available for the `oauth2Login()` DSL:
  80. .OAuth2 Login Configuration Options
  81. [tabs]
  82. ======
  83. Java::
  84. +
  85. [source,java,role="primary"]
  86. ----
  87. @Configuration
  88. @EnableWebSecurity
  89. public class OAuth2LoginSecurityConfig {
  90. @Bean
  91. public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
  92. http
  93. .oauth2Login(oauth2 -> oauth2
  94. .clientRegistrationRepository(this.clientRegistrationRepository())
  95. .authorizedClientRepository(this.authorizedClientRepository())
  96. .authorizedClientService(this.authorizedClientService())
  97. .loginPage("/login")
  98. .authorizationEndpoint(authorization -> authorization
  99. .baseUri(this.authorizationRequestBaseUri())
  100. .authorizationRequestRepository(this.authorizationRequestRepository())
  101. .authorizationRequestResolver(this.authorizationRequestResolver())
  102. )
  103. .redirectionEndpoint(redirection -> redirection
  104. .baseUri(this.authorizationResponseBaseUri())
  105. )
  106. .tokenEndpoint(token -> token
  107. .accessTokenResponseClient(this.accessTokenResponseClient())
  108. )
  109. .userInfoEndpoint(userInfo -> userInfo
  110. .userAuthoritiesMapper(this.userAuthoritiesMapper())
  111. .userService(this.oauth2UserService())
  112. .oidcUserService(this.oidcUserService())
  113. )
  114. );
  115. return http.build();
  116. }
  117. }
  118. ----
  119. Kotlin::
  120. +
  121. [source,kotlin,role="secondary"]
  122. ----
  123. @Configuration
  124. @EnableWebSecurity
  125. class OAuth2LoginSecurityConfig {
  126. @Bean
  127. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  128. http {
  129. oauth2Login {
  130. clientRegistrationRepository = clientRegistrationRepository()
  131. authorizedClientRepository = authorizedClientRepository()
  132. authorizedClientService = authorizedClientService()
  133. loginPage = "/login"
  134. authorizationEndpoint {
  135. baseUri = authorizationRequestBaseUri()
  136. authorizationRequestRepository = authorizationRequestRepository()
  137. authorizationRequestResolver = authorizationRequestResolver()
  138. }
  139. redirectionEndpoint {
  140. baseUri = authorizationResponseBaseUri()
  141. }
  142. tokenEndpoint {
  143. accessTokenResponseClient = accessTokenResponseClient()
  144. }
  145. userInfoEndpoint {
  146. userAuthoritiesMapper = userAuthoritiesMapper()
  147. userService = oauth2UserService()
  148. oidcUserService = oidcUserService()
  149. }
  150. }
  151. }
  152. return http.build()
  153. }
  154. }
  155. ----
  156. ======
  157. In addition to the `oauth2Login()` DSL, XML configuration is also supported.
  158. The following code shows the complete configuration options available in the xref:servlet/appendix/namespace/http.adoc#nsa-oauth2-login[ security namespace]:
  159. .OAuth2 Login XML Configuration Options
  160. [source,xml]
  161. ----
  162. <http>
  163. <oauth2-login client-registration-repository-ref="clientRegistrationRepository"
  164. authorized-client-repository-ref="authorizedClientRepository"
  165. authorized-client-service-ref="authorizedClientService"
  166. authorization-request-repository-ref="authorizationRequestRepository"
  167. authorization-request-resolver-ref="authorizationRequestResolver"
  168. access-token-response-client-ref="accessTokenResponseClient"
  169. user-authorities-mapper-ref="userAuthoritiesMapper"
  170. user-service-ref="oauth2UserService"
  171. oidc-user-service-ref="oidcUserService"
  172. login-processing-url="/login/oauth2/code/*"
  173. login-page="/login"
  174. authentication-success-handler-ref="authenticationSuccessHandler"
  175. authentication-failure-handler-ref="authenticationFailureHandler"
  176. jwt-decoder-factory-ref="jwtDecoderFactory"/>
  177. </http>
  178. ----
  179. The following sections go into more detail on each of the configuration options available:
  180. * <<oauth2login-advanced-login-page>>
  181. * <<oauth2login-advanced-redirection-endpoint>>
  182. * <<oauth2login-advanced-userinfo-endpoint>>
  183. * <<oauth2login-advanced-idtoken-verify>>
  184. * <<oauth2login-advanced-oidc-logout>>
  185. [[oauth2login-advanced-login-page]]
  186. == OAuth 2.0 Login Page
  187. By default, the OAuth 2.0 Login Page is auto-generated by the `DefaultLoginPageGeneratingFilter`.
  188. 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).
  189. [NOTE]
  190. ====
  191. For `DefaultLoginPageGeneratingFilter` to show links for configured OAuth Clients, the registered `ClientRegistrationRepository` needs to also implement `Iterable<ClientRegistration>`.
  192. See `InMemoryClientRegistrationRepository` for reference.
  193. ====
  194. The link's destination for each OAuth Client defaults to the following:
  195. `+OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/{registrationId}"+`
  196. The following line shows an example:
  197. [source,html]
  198. ----
  199. <a href="/oauth2/authorization/google">Google</a>
  200. ----
  201. To override the default login page, configure `oauth2Login().loginPage()` and (optionally) `oauth2Login().authorizationEndpoint().baseUri()`.
  202. The following listing shows an example:
  203. .OAuth2 Login Page Configuration
  204. [tabs]
  205. ======
  206. Java::
  207. +
  208. [source,java,role="primary"]
  209. ----
  210. @Configuration
  211. @EnableWebSecurity
  212. public class OAuth2LoginSecurityConfig {
  213. @Bean
  214. public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
  215. http
  216. .oauth2Login(oauth2 -> oauth2
  217. .loginPage("/login/oauth2")
  218. ...
  219. .authorizationEndpoint(authorization -> authorization
  220. .baseUri("/login/oauth2/authorization")
  221. ...
  222. )
  223. );
  224. return http.build();
  225. }
  226. }
  227. ----
  228. Kotlin::
  229. +
  230. [source,kotlin,role="secondary"]
  231. ----
  232. @Configuration
  233. @EnableWebSecurity
  234. class OAuth2LoginSecurityConfig {
  235. @Bean
  236. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  237. http {
  238. oauth2Login {
  239. loginPage = "/login/oauth2"
  240. authorizationEndpoint {
  241. baseUri = "/login/oauth2/authorization"
  242. }
  243. }
  244. }
  245. return http.build()
  246. }
  247. }
  248. ----
  249. Xml::
  250. +
  251. [source,xml,role="secondary"]
  252. ----
  253. <http>
  254. <oauth2-login login-page="/login/oauth2"
  255. ...
  256. />
  257. </http>
  258. ----
  259. ======
  260. [IMPORTANT]
  261. ====
  262. You need to provide a `@Controller` with a `@RequestMapping("/login/oauth2")` that is capable of rendering the custom login page.
  263. ====
  264. [TIP]
  265. =====
  266. As noted earlier, configuring `oauth2Login().authorizationEndpoint().baseUri()` is optional.
  267. However, if you choose to customize it, ensure the link to each OAuth Client matches the `authorizationEndpoint().baseUri()`.
  268. The following line shows an example:
  269. [source,html]
  270. ----
  271. <a href="/login/oauth2/authorization/google">Google</a>
  272. ----
  273. =====
  274. [[oauth2login-advanced-redirection-endpoint]]
  275. == Redirection Endpoint
  276. The Redirection Endpoint is used by the Authorization Server for returning the Authorization Response (which contains the authorization credentials) to the client through the Resource Owner user-agent.
  277. [TIP]
  278. ====
  279. OAuth 2.0 Login leverages the Authorization Code Grant.
  280. Therefore, the authorization credential is the authorization code.
  281. ====
  282. The default Authorization Response `baseUri` (redirection endpoint) is `*/login/oauth2/code/**`, which is defined in `OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI`.
  283. If you would like to customize the Authorization Response `baseUri`, configure it as follows:
  284. .Redirection Endpoint Configuration
  285. [tabs]
  286. ======
  287. Java::
  288. +
  289. [source,java,role="primary"]
  290. ----
  291. @Configuration
  292. @EnableWebSecurity
  293. public class OAuth2LoginSecurityConfig {
  294. @Bean
  295. public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
  296. http
  297. .oauth2Login(oauth2 -> oauth2
  298. .redirectionEndpoint(redirection -> redirection
  299. .baseUri("/login/oauth2/callback/*")
  300. ...
  301. )
  302. );
  303. return http.build();
  304. }
  305. }
  306. ----
  307. Kotlin::
  308. +
  309. [source,kotlin,role="secondary"]
  310. ----
  311. @Configuration
  312. @EnableWebSecurity
  313. class OAuth2LoginSecurityConfig {
  314. @Bean
  315. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  316. http {
  317. oauth2Login {
  318. redirectionEndpoint {
  319. baseUri = "/login/oauth2/callback/*"
  320. }
  321. }
  322. }
  323. return http.build()
  324. }
  325. }
  326. ----
  327. Xml::
  328. +
  329. [source,xml,role="secondary"]
  330. ----
  331. <http>
  332. <oauth2-login login-processing-url="/login/oauth2/callback/*"
  333. ...
  334. />
  335. </http>
  336. ----
  337. ======
  338. [IMPORTANT]
  339. =====
  340. You also need to ensure the `ClientRegistration.redirectUri` matches the custom Authorization Response `baseUri`.
  341. The following listing shows an example:
  342. [tabs]
  343. ======
  344. Java::
  345. +
  346. [source,java,role="primary",subs="-attributes"]
  347. ----
  348. return CommonOAuth2Provider.GOOGLE.getBuilder("google")
  349. .clientId("google-client-id")
  350. .clientSecret("google-client-secret")
  351. .redirectUri("{baseUrl}/login/oauth2/callback/{registrationId}")
  352. .build();
  353. ----
  354. Kotlin::
  355. +
  356. [source,kotlin,role="secondary",subs="-attributes"]
  357. ----
  358. return CommonOAuth2Provider.GOOGLE.getBuilder("google")
  359. .clientId("google-client-id")
  360. .clientSecret("google-client-secret")
  361. .redirectUri("{baseUrl}/login/oauth2/callback/{registrationId}")
  362. .build()
  363. ----
  364. ======
  365. =====
  366. [[oauth2login-advanced-userinfo-endpoint]]
  367. == UserInfo Endpoint
  368. The UserInfo Endpoint includes a number of configuration options, as described in the following sub-sections:
  369. * <<oauth2login-advanced-map-authorities>>
  370. * <<oauth2login-advanced-oauth2-user-service>>
  371. * <<oauth2login-advanced-oidc-user-service>>
  372. [[oauth2login-advanced-map-authorities]]
  373. === Mapping User Authorities
  374. 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_`.
  375. These granted authorities can be mapped to a new set of `GrantedAuthority` instances, which are supplied to `OAuth2AuthenticationToken` when completing the authentication.
  376. [TIP]
  377. `OAuth2AuthenticationToken.getAuthorities()` is used for authorizing requests, such as in `hasRole('USER')` or `hasRole('ADMIN')`.
  378. There are a couple of options to choose from when mapping user authorities:
  379. * <<oauth2login-advanced-map-authorities-grantedauthoritiesmapper>>
  380. * <<oauth2login-advanced-map-authorities-oauth2userservice>>
  381. [[oauth2login-advanced-map-authorities-grantedauthoritiesmapper]]
  382. ==== Using a GrantedAuthoritiesMapper
  383. 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`).
  384. Provide an implementation of `GrantedAuthoritiesMapper` and configure it, as follows:
  385. .Granted Authorities Mapper Configuration
  386. [tabs]
  387. ======
  388. Java::
  389. +
  390. [source,java,role="primary"]
  391. ----
  392. @Configuration
  393. @EnableWebSecurity
  394. public class OAuth2LoginSecurityConfig {
  395. @Bean
  396. public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
  397. http
  398. .oauth2Login(oauth2 -> oauth2
  399. .userInfoEndpoint(userInfo -> userInfo
  400. .userAuthoritiesMapper(this.userAuthoritiesMapper())
  401. ...
  402. )
  403. );
  404. return http.build();
  405. }
  406. private GrantedAuthoritiesMapper userAuthoritiesMapper() {
  407. return (authorities) -> {
  408. Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
  409. authorities.forEach(authority -> {
  410. if (OidcUserAuthority.class.isInstance(authority)) {
  411. OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority;
  412. OidcIdToken idToken = oidcUserAuthority.getIdToken();
  413. OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
  414. // Map the claims found in idToken and/or userInfo
  415. // to one or more GrantedAuthority's and add it to mappedAuthorities
  416. } else if (OAuth2UserAuthority.class.isInstance(authority)) {
  417. OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
  418. Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
  419. // Map the attributes found in userAttributes
  420. // to one or more GrantedAuthority's and add it to mappedAuthorities
  421. }
  422. });
  423. return mappedAuthorities;
  424. };
  425. }
  426. }
  427. ----
  428. Kotlin::
  429. +
  430. [source,kotlin,role="secondary"]
  431. ----
  432. @Configuration
  433. @EnableWebSecurity
  434. class OAuth2LoginSecurityConfig {
  435. @Bean
  436. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  437. http {
  438. oauth2Login {
  439. userInfoEndpoint {
  440. userAuthoritiesMapper = userAuthoritiesMapper()
  441. }
  442. }
  443. }
  444. return http.build()
  445. }
  446. private fun userAuthoritiesMapper(): GrantedAuthoritiesMapper = GrantedAuthoritiesMapper { authorities: Collection<GrantedAuthority> ->
  447. val mappedAuthorities = emptySet<GrantedAuthority>()
  448. authorities.forEach { authority ->
  449. if (authority is OidcUserAuthority) {
  450. val idToken = authority.idToken
  451. val userInfo = authority.userInfo
  452. // Map the claims found in idToken and/or userInfo
  453. // to one or more GrantedAuthority's and add it to mappedAuthorities
  454. } else if (authority is OAuth2UserAuthority) {
  455. val userAttributes = authority.attributes
  456. // Map the attributes found in userAttributes
  457. // to one or more GrantedAuthority's and add it to mappedAuthorities
  458. }
  459. }
  460. mappedAuthorities
  461. }
  462. }
  463. ----
  464. Xml::
  465. +
  466. [source,xml,role="secondary"]
  467. ----
  468. <http>
  469. <oauth2-login user-authorities-mapper-ref="userAuthoritiesMapper"
  470. ...
  471. />
  472. </http>
  473. ----
  474. ======
  475. Alternatively, you can register a `GrantedAuthoritiesMapper` `@Bean` to have it automatically applied to the configuration, as follows:
  476. .Granted Authorities Mapper Bean Configuration
  477. [tabs]
  478. ======
  479. Java::
  480. +
  481. [source,java,role="primary"]
  482. ----
  483. @Configuration
  484. @EnableWebSecurity
  485. public class OAuth2LoginSecurityConfig {
  486. @Bean
  487. public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
  488. http
  489. .oauth2Login(withDefaults());
  490. return http.build();
  491. }
  492. @Bean
  493. public GrantedAuthoritiesMapper userAuthoritiesMapper() {
  494. ...
  495. }
  496. }
  497. ----
  498. Kotlin::
  499. +
  500. [source,kotlin,role="secondary"]
  501. ----
  502. @Configuration
  503. @EnableWebSecurity
  504. class OAuth2LoginSecurityConfig {
  505. @Bean
  506. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  507. http {
  508. oauth2Login { }
  509. }
  510. return http.build()
  511. }
  512. @Bean
  513. fun userAuthoritiesMapper(): GrantedAuthoritiesMapper {
  514. ...
  515. }
  516. }
  517. ----
  518. ======
  519. [[oauth2login-advanced-map-authorities-oauth2userservice]]
  520. ==== Delegation-based Strategy with OAuth2UserService
  521. This strategy is advanced compared to using a `GrantedAuthoritiesMapper`. However, it is 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).
  522. The `OAuth2UserRequest` (and `OidcUserRequest`) provides you access to the associated `OAuth2AccessToken`, which is very useful in cases where the _delegator_ needs to fetch authority information from a protected resource before it can map the custom authorities for the user.
  523. The following example shows how to implement and configure a delegation-based strategy using an OpenID Connect 1.0 UserService:
  524. .OAuth2UserService Configuration
  525. [tabs]
  526. ======
  527. Java::
  528. +
  529. [source,java,role="primary"]
  530. ----
  531. @Configuration
  532. @EnableWebSecurity
  533. public class OAuth2LoginSecurityConfig {
  534. @Bean
  535. public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
  536. http
  537. .oauth2Login(oauth2 -> oauth2
  538. .userInfoEndpoint(userInfo -> userInfo
  539. .oidcUserService(this.oidcUserService())
  540. ...
  541. )
  542. );
  543. return http.build();
  544. }
  545. private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
  546. final OidcUserService delegate = new OidcUserService();
  547. return (userRequest) -> {
  548. // Delegate to the default implementation for loading a user
  549. OidcUser oidcUser = delegate.loadUser(userRequest);
  550. OAuth2AccessToken accessToken = userRequest.getAccessToken();
  551. Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
  552. // TODO
  553. // 1) Fetch the authority information from the protected resource using accessToken
  554. // 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities
  555. // 3) Create a copy of oidcUser but use the mappedAuthorities instead
  556. oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
  557. return oidcUser;
  558. };
  559. }
  560. }
  561. ----
  562. Kotlin::
  563. +
  564. [source,kotlin,role="secondary"]
  565. ----
  566. @Configuration
  567. @EnableWebSecurity
  568. class OAuth2LoginSecurityConfig {
  569. @Bean
  570. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  571. http {
  572. oauth2Login {
  573. userInfoEndpoint {
  574. oidcUserService = oidcUserService()
  575. }
  576. }
  577. }
  578. return http.build()
  579. }
  580. @Bean
  581. fun oidcUserService(): OAuth2UserService<OidcUserRequest, OidcUser> {
  582. val delegate = OidcUserService()
  583. return OAuth2UserService { userRequest ->
  584. // Delegate to the default implementation for loading a user
  585. var oidcUser = delegate.loadUser(userRequest)
  586. val accessToken = userRequest.accessToken
  587. val mappedAuthorities = HashSet<GrantedAuthority>()
  588. // TODO
  589. // 1) Fetch the authority information from the protected resource using accessToken
  590. // 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities
  591. // 3) Create a copy of oidcUser but use the mappedAuthorities instead
  592. oidcUser = DefaultOidcUser(mappedAuthorities, oidcUser.idToken, oidcUser.userInfo)
  593. oidcUser
  594. }
  595. }
  596. }
  597. ----
  598. Xml::
  599. +
  600. [source,xml,role="secondary"]
  601. ----
  602. <http>
  603. <oauth2-login oidc-user-service-ref="oidcUserService"
  604. ...
  605. />
  606. </http>
  607. ----
  608. ======
  609. [[oauth2login-advanced-oauth2-user-service]]
  610. === OAuth 2.0 UserService
  611. `DefaultOAuth2UserService` is an implementation of an `OAuth2UserService` that supports standard OAuth 2.0 Provider's.
  612. [NOTE]
  613. ====
  614. `OAuth2UserService` 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`.
  615. ====
  616. `DefaultOAuth2UserService` uses a `RestOperations` instance when requesting the user attributes at the UserInfo Endpoint.
  617. If you need to customize the pre-processing of the UserInfo Request, you can provide `DefaultOAuth2UserService.setRequestEntityConverter()` with a custom `Converter<OAuth2UserRequest, RequestEntity<?>>`.
  618. The default implementation `OAuth2UserRequestEntityConverter` builds a `RequestEntity` representation of a UserInfo Request that sets the `OAuth2AccessToken` in the `Authorization` header by default.
  619. On the other end, if you need to customize the post-handling of the UserInfo Response, you need to provide `DefaultOAuth2UserService.setRestOperations()` with a custom configured `RestOperations`.
  620. The default `RestOperations` is configured as follows:
  621. [source,java]
  622. ----
  623. RestTemplate restTemplate = new RestTemplate();
  624. restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
  625. ----
  626. `OAuth2ErrorResponseErrorHandler` is a `ResponseErrorHandler` that can handle an OAuth 2.0 Error (400 Bad Request).
  627. It uses an `OAuth2ErrorHttpMessageConverter` for converting the OAuth 2.0 Error parameters to an `OAuth2Error`.
  628. Whether you customize `DefaultOAuth2UserService` or provide your own implementation of `OAuth2UserService`, you need to configure it as follows:
  629. [tabs]
  630. ======
  631. Java::
  632. +
  633. [source,java,role="primary"]
  634. ----
  635. @Configuration
  636. @EnableWebSecurity
  637. public class OAuth2LoginSecurityConfig {
  638. @Bean
  639. public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
  640. http
  641. .oauth2Login(oauth2 -> oauth2
  642. .userInfoEndpoint(userInfo -> userInfo
  643. .userService(this.oauth2UserService())
  644. ...
  645. )
  646. );
  647. return http.build();
  648. }
  649. private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
  650. ...
  651. }
  652. }
  653. ----
  654. Kotlin::
  655. +
  656. [source,kotlin,role="secondary"]
  657. ----
  658. @Configuration
  659. @EnableWebSecurity
  660. class OAuth2LoginSecurityConfig {
  661. @Bean
  662. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  663. http {
  664. oauth2Login {
  665. userInfoEndpoint {
  666. userService = oauth2UserService()
  667. // ...
  668. }
  669. }
  670. }
  671. return http.build()
  672. }
  673. private fun oauth2UserService(): OAuth2UserService<OAuth2UserRequest, OAuth2User> {
  674. // ...
  675. }
  676. }
  677. ----
  678. ======
  679. [[oauth2login-advanced-oidc-user-service]]
  680. === OpenID Connect 1.0 UserService
  681. `OidcUserService` is an implementation of an `OAuth2UserService` that supports OpenID Connect 1.0 Provider's.
  682. The `OidcUserService` leverages the `DefaultOAuth2UserService` when requesting the user attributes at the UserInfo Endpoint.
  683. If you need to customize the pre-processing of the UserInfo Request or the post-handling of the UserInfo Response, you need to provide `OidcUserService.setOauth2UserService()` with a custom configured `DefaultOAuth2UserService`.
  684. Whether you customize `OidcUserService` or provide your own implementation of `OAuth2UserService` for OpenID Connect 1.0 Provider's, you need to configure it as follows:
  685. [tabs]
  686. ======
  687. Java::
  688. +
  689. [source,java,role="primary"]
  690. ----
  691. @Configuration
  692. @EnableWebSecurity
  693. public class OAuth2LoginSecurityConfig {
  694. @Bean
  695. public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
  696. http
  697. .oauth2Login(oauth2 -> oauth2
  698. .userInfoEndpoint(userInfo -> userInfo
  699. .oidcUserService(this.oidcUserService())
  700. ...
  701. )
  702. );
  703. return http.build();
  704. }
  705. private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
  706. ...
  707. }
  708. }
  709. ----
  710. Kotlin::
  711. +
  712. [source,kotlin,role="secondary"]
  713. ----
  714. @Configuration
  715. @EnableWebSecurity
  716. class OAuth2LoginSecurityConfig {
  717. @Bean
  718. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  719. http {
  720. oauth2Login {
  721. userInfoEndpoint {
  722. oidcUserService = oidcUserService()
  723. // ...
  724. }
  725. }
  726. }
  727. return http.build()
  728. }
  729. private fun oidcUserService(): OAuth2UserService<OidcUserRequest, OidcUser> {
  730. // ...
  731. }
  732. }
  733. ----
  734. ======
  735. [[oauth2login-advanced-idtoken-verify]]
  736. == ID Token Signature Verification
  737. 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.
  738. The ID Token is represented as a https://tools.ietf.org/html/rfc7519[JSON Web Token] (JWT) and MUST be signed by using https://tools.ietf.org/html/rfc7515[JSON Web Signature] (JWS).
  739. The `OidcIdTokenDecoderFactory` provides a `JwtDecoder` used for `OidcIdToken` signature verification. The default algorithm is `RS256` but may be different when assigned during client registration.
  740. For these cases, you can configure a resolver to return the expected JWS algorithm assigned for a specific client.
  741. The JWS algorithm resolver is a `Function` that accepts a `ClientRegistration` and returns the expected `JwsAlgorithm` for the client, such as `SignatureAlgorithm.RS256` or `MacAlgorithm.HS256`
  742. The following code shows how to configure the `OidcIdTokenDecoderFactory` `@Bean` to default to `MacAlgorithm.HS256` for all `ClientRegistration` instances:
  743. [tabs]
  744. ======
  745. Java::
  746. +
  747. [source,java,role="primary"]
  748. ----
  749. @Bean
  750. public JwtDecoderFactory<ClientRegistration> idTokenDecoderFactory() {
  751. OidcIdTokenDecoderFactory idTokenDecoderFactory = new OidcIdTokenDecoderFactory();
  752. idTokenDecoderFactory.setJwsAlgorithmResolver(clientRegistration -> MacAlgorithm.HS256);
  753. return idTokenDecoderFactory;
  754. }
  755. ----
  756. Kotlin::
  757. +
  758. [source,kotlin,role="secondary"]
  759. ----
  760. @Bean
  761. fun idTokenDecoderFactory(): JwtDecoderFactory<ClientRegistration?> {
  762. val idTokenDecoderFactory = OidcIdTokenDecoderFactory()
  763. idTokenDecoderFactory.setJwsAlgorithmResolver { MacAlgorithm.HS256 }
  764. return idTokenDecoderFactory
  765. }
  766. ----
  767. ======
  768. [NOTE]
  769. ====
  770. For MAC-based algorithms (such as `HS256`, `HS384`, or `HS512`), the `client-secret` that corresponds to the `client-id` is used as the symmetric key for signature verification.
  771. ====
  772. [TIP]
  773. ====
  774. 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.
  775. ====
  776. [[oauth2login-advanced-oidc-logout]]
  777. == OpenID Connect 1.0 Logout
  778. OpenID Connect Session Management 1.0 allows the ability to log out the end user at the Provider by using the Client.
  779. One of the strategies available is https://openid.net/specs/openid-connect-rpinitiated-1_0.html[RP-Initiated Logout].
  780. If the OpenID Provider supports both Session Management and https://openid.net/specs/openid-connect-discovery-1_0.html[Discovery], the client can obtain the `end_session_endpoint` `URL` from the OpenID Provider's https://openid.net/specs/openid-connect-session-1_0.html#OPMetadata[Discovery Metadata].
  781. You can do so by configuring the `ClientRegistration` with the `issuer-uri`, as follows:
  782. [source,yaml]
  783. ----
  784. spring:
  785. security:
  786. oauth2:
  787. client:
  788. registration:
  789. okta:
  790. client-id: okta-client-id
  791. client-secret: okta-client-secret
  792. ...
  793. provider:
  794. okta:
  795. issuer-uri: https://dev-1234.oktapreview.com
  796. ----
  797. Also, you can configure `OidcClientInitiatedLogoutSuccessHandler`, which implements RP-Initiated Logout, as follows:
  798. [tabs]
  799. ======
  800. Java::
  801. +
  802. [source,java,role="primary"]
  803. ----
  804. @Configuration
  805. @EnableWebSecurity
  806. public class OAuth2LoginSecurityConfig {
  807. @Autowired
  808. private ClientRegistrationRepository clientRegistrationRepository;
  809. @Bean
  810. public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
  811. http
  812. .authorizeHttpRequests(authorize -> authorize
  813. .anyRequest().authenticated()
  814. )
  815. .oauth2Login(withDefaults())
  816. .logout(logout -> logout
  817. .logoutSuccessHandler(oidcLogoutSuccessHandler())
  818. );
  819. return http.build();
  820. }
  821. private LogoutSuccessHandler oidcLogoutSuccessHandler() {
  822. OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler =
  823. new OidcClientInitiatedLogoutSuccessHandler(this.clientRegistrationRepository);
  824. // Sets the location that the End-User's User Agent will be redirected to
  825. // after the logout has been performed at the Provider
  826. oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");
  827. return oidcLogoutSuccessHandler;
  828. }
  829. }
  830. ----
  831. Kotlin::
  832. +
  833. [source,kotlin,role="secondary"]
  834. ----
  835. @Configuration
  836. @EnableWebSecurity
  837. class OAuth2LoginSecurityConfig {
  838. @Autowired
  839. private lateinit var clientRegistrationRepository: ClientRegistrationRepository
  840. @Bean
  841. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  842. http {
  843. authorizeRequests {
  844. authorize(anyRequest, authenticated)
  845. }
  846. oauth2Login { }
  847. logout {
  848. logoutSuccessHandler = oidcLogoutSuccessHandler()
  849. }
  850. }
  851. return http.build()
  852. }
  853. private fun oidcLogoutSuccessHandler(): LogoutSuccessHandler {
  854. val oidcLogoutSuccessHandler = OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository)
  855. // Sets the location that the End-User's User Agent will be redirected to
  856. // after the logout has been performed at the Provider
  857. oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}")
  858. return oidcLogoutSuccessHandler
  859. }
  860. }
  861. ----
  862. ======
  863. [NOTE]
  864. ====
  865. `OidcClientInitiatedLogoutSuccessHandler` supports the `+{baseUrl}+` placeholder.
  866. If used, the application's base URL, such as `https://app.example.org`, replaces it at request time.
  867. ====