advanced.adoc 30 KB

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