260 Commits
0.1.2 ... 0.3.1

Author SHA1 Message Date
Joe Grandja
931aaa4e94 Release 0.3.1 2022-06-17 11:56:51 -04:00
Daniel Garnier-Moiroux
ec7ab5c956 Add authenticationDetailsSource to AuthorizationEndpointFilter
Closes gh-768
2022-06-16 16:27:39 -04:00
Joe Grandja
fdf0a2f94c Access token is available when customizing ID Token
Closes gh-744
2022-06-16 10:38:09 -04:00
Joe Grandja
b37d4dd31e Describe error message when redirect_uri contains localhost
Closes gh-680
2022-06-15 17:38:54 -04:00
Joe Grandja
4199ab0172 Unsupported code_challenge_method parameter should return invalid_grant
Issue gh-770
2022-06-15 09:24:42 -04:00
Joe Grandja
7dfdcf3a27 Validate code_challenge_method parameter
Issue gh-756

Closes gh-770
2022-06-15 09:09:05 -04:00
Joe Grandja
0cae3c693e OpenID Provider Configuration response returns introspection_endpoint
Closes gh-779
2022-06-10 12:04:31 -04:00
Gyeongwon, Do
d6ff0f3fc7 Add token revocation endpoint to OIDC Provider Configuration endpoint
Closes gh-687
2022-06-10 11:47:22 -04:00
Joe Grandja
c75b8a1cb9 Build PR with Java 8
Issue gh-761
2022-06-10 10:45:53 -04:00
Steve Riesenberg
77d665fe97 Build with Java 8, 11 or 17
Issue gh-761
2022-06-08 18:08:41 -05:00
Steve Riesenberg
dc79172a4b Downgrade to hsqldb 2.5.2
Closes gh-771
2022-06-08 17:40:56 -05:00
Steve Riesenberg
e204b6bced Downgrade to Java 8
Closes gh-761
2022-06-08 17:40:33 -05:00
Joe Grandja
d46bdfc80b Update link to feature list 2022-05-25 16:24:17 -04:00
Steve Riesenberg
421a9723ea Next Development Version 2022-05-25 10:45:22 -05:00
Steve Riesenberg
d09e5348c4 Release 0.3.0 2022-05-25 10:25:10 -05:00
Steve Riesenberg
5778192cbf Polish How-to Customize UserInfo response
Remove another extraneous numbered callout.

Issue gh-537
2022-05-25 10:23:19 -05:00
Steve Riesenberg
2c1132c6ee Polish How-to Customize UserInfo response
Remove unneeded `issuer()` in `ProviderSettings` (found one more).

Issue gh-537
2022-05-25 10:08:58 -05:00
Steve Riesenberg
991b641189 Polish How-to Customize UserInfo response
Remove unneeded `issuer()` in `ProviderSettings`.

Issue gh-537
2022-05-25 09:58:36 -05:00
Steve Riesenberg
8ee1c3473a Polish How-to Customize UserInfo response
Remove extraneous numbered callout.

Issue gh-537
2022-05-25 09:46:56 -05:00
Steve Riesenberg
d5e822c7d4 Include java sources as resources in asciidoctor task
Issue gh-690
2022-05-25 09:43:33 -05:00
Joe Grandja
a49a5dbcad Polish How-to Customize UserInfo response
Issue gh-537
2022-05-25 09:52:57 -04:00
Joe Grandja
4cf7dd538a Polish "Getting Started" in ref doc
Issue gh-669
2022-05-25 04:47:27 -04:00
Joe Grandja
ca2ffb0756 Remove support for "plain" code_challenge_method parameter
Closes gh-756
2022-05-24 20:25:13 -04:00
Joe Grandja
c4406cda67 Remove temporary HttpSessionSecurityContextRepository
Issue gh-482
2022-05-24 15:56:00 -04:00
Steve Riesenberg
d8421d5e3b Simplify authorization server filter chain in samples
Closes gh-707
2022-05-24 12:15:41 -05:00
Steve Riesenberg
ed786c541c Remove quotes from How-to in ref doc
Issue gh-499
2022-05-24 11:58:08 -05:00
Steve Riesenberg
2fc0c5efb8 Update to Spring Boot 2.7.0 in ref doc examples
Issue gh-749
2022-05-24 11:35:39 -05:00
Steve Riesenberg
7edbe2a30d Polish "How-to: Implement core services with JPA"
Issue gh-545
2022-05-24 11:26:47 -05:00
Steve Riesenberg
5bd2968dac Polish ref doc
Issue gh-499
2022-05-24 11:26:46 -05:00
Joe Grandja
76212f1018 Update to com.squareup.okhttp3:4.9.3
Closes gh-755
2022-05-24 10:20:53 -04:00
Joe Grandja
90e09594d7 Update to mockito-core:4.5.1
Closes gh-754
2022-05-24 10:20:39 -04:00
Joe Grandja
7be02ceda0 Update to nimbus-jose-jwt:9.22
Closes gh-753
2022-05-24 10:20:23 -04:00
Joe Grandja
4defb0ef87 Update to jackson-bom:2.13.3
Closes gh-752
2022-05-24 10:20:11 -04:00
Joe Grandja
8a18f20ba8 Update snapshot_tests to use Spring Security 5.7.+
Issue gh-751
2022-05-24 10:19:52 -04:00
Joe Grandja
8a9ce86710 Update to Spring Security 5.7.1
Closes gh-751
2022-05-24 10:19:37 -04:00
Joe Grandja
c2ba98ee7d Update to Spring Framework 5.3.20
Closes gh-750
2022-05-24 10:19:16 -04:00
Joe Grandja
c4a5015f8b Update to Spring Boot 2.7.0
Closes gh-749
2022-05-24 10:18:22 -04:00
Joe Grandja
bb1bd6b0b8 Reorganize the feature list in ref doc
Closes gh-708
2022-05-24 07:32:33 -04:00
Joe Grandja
6bec78cf6b Polish Protocol Endpoints in ref doc
Issue gh-672
2022-05-23 16:11:37 -04:00
Steve Riesenberg
51d00742f3 Polish tests using AuthorizationCodeGrantFlow 2022-05-19 12:29:37 -05:00
Steve Riesenberg
9171006952 Add "How-to: Customize the OpenID Connect 1.0 UserInfo response"
Closes gh-537
2022-05-19 12:29:37 -05:00
Steve Riesenberg
ecda4e0a15 Polish section id prefixes in docs
See https://github.com/spring-projects/spring-authorization-server/pull/736#discussion_r869493261

Issue gh-667
Issue gh-668
Issue gh-545
2022-05-18 23:20:14 -05:00
Steve Riesenberg
14cedd7895 Add Client Registration Endpoint in ref doc
Closes gh-672
2022-05-18 17:08:40 -05:00
Steve Riesenberg
4443312739 Add UserInfo Endpoint in ref doc
Issue gh-672
2022-05-18 17:08:28 -05:00
Steve Riesenberg
eb0ac3fa16 Add Provider Configuration Endpoint in ref doc
Issue gh-672
2022-05-18 17:08:28 -05:00
Steve Riesenberg
170159cc2c Add JWK Set Endpoint in ref doc
Issue gh-672
2022-05-18 17:07:24 -05:00
Steve Riesenberg
4c54c5976f Add OAuthorization Server Metadata Endpoint in ref doc
Issue gh-672
2022-05-18 17:07:23 -05:00
Steve Riesenberg
e687970f2b Add Token Revocation Endpoint in ref doc
Issue gh-672
2022-05-18 17:07:23 -05:00
Steve Riesenberg
8bc7b77ed7 Add Token Introspection Endpoint in ref doc
Issue gh-672
2022-05-18 17:07:23 -05:00
Steve Riesenberg
1a115793ac Add copyright notice to index footer of docs
Closes gh-742
2022-05-17 12:48:59 -05:00
Joe Grandja
fc05e22bce Add Authorization and Token endpoint in ref doc
Issue gh-672
2022-05-11 14:33:19 -04:00
Joe Grandja
9d744045ce Add Configuration Model in ref doc
Closes gh-670
2022-05-11 09:30:54 -04:00
邓超
abd376ef1e Fix typo in CONTRIBUTING.adoc 2022-05-06 14:47:55 -05:00
Steve Riesenberg
c86a5f9735 Polish "How-to Guides" 2022-05-06 14:45:22 -05:00
Steve Riesenberg
6dd34f3afb Remove appendix 2022-05-06 14:22:53 -05:00
Joe Grandja
08d37771e3 Polish gh-649 2022-05-06 08:46:14 -04:00
Fang Xia
d0bb94b887 Enhance validation for configured issuer
Closes gh-649
2022-05-06 08:17:32 -04:00
Joe Grandja
b991e1adc1 Use OAuth2Token instead of AbstractOAuth2Token
Closes gh-733
2022-05-05 13:26:29 -04:00
Joe Grandja
627ae61785 Remove deprecations
Closes gh-732
2022-05-05 10:25:32 -04:00
Joe Grandja
b6f477b1d3 Remove OAuth2TokenClaimsContext.Builder.claims()
Issue gh-500

Closes gh-731
2022-05-05 10:24:53 -04:00
Joe Grandja
fff62db117 Move OAuth2TokenCustomizer to token package
Closes gh-730
2022-05-04 15:16:41 -04:00
Joe Grandja
9c1d224843 Update snapshot_tests to use Spring Security 5.6+
Issue gh-720
2022-05-04 11:13:38 -04:00
Joe Grandja
f8d613c22b Change interface that only contain constants to final class
Closes gh-728
2022-05-04 10:35:07 -04:00
Joe Grandja
30c17a5e49 Use OAuth2ErrorCodes.INVALID_REDIRECT_URI
Closes gh-727
2022-05-04 10:34:46 -04:00
Joe Grandja
6470c71e77 Remove temporary OAuth2AccessTokenResponseHttpMessageConverter
Issue gh-321

Closes gh-726
2022-05-04 10:34:19 -04:00
Joe Grandja
5652e022ab Remove OAuth2TokenIntrospectionClaimAccessor
Issue gh-597

Closes gh-725
2022-05-04 10:33:58 -04:00
Joe Grandja
6b0c2e3276 Polish OAuth2TokenCustomizer in ref doc
Issue gh-714
2022-05-04 10:33:58 -04:00
Joe Grandja
a1fefd937b Remove JwtEncoder and associated classes
Issue gh-596

Closes gh-724
2022-05-04 10:33:37 -04:00
Joe Grandja
47717a8163 Update to mockito-core 4.0.0
Closes gh-723
2022-05-04 10:33:20 -04:00
Joe Grandja
1d34ceb05d Update to nimbus-jose-jwt 9.14
Closes gh-722
2022-05-04 10:32:49 -04:00
Joe Grandja
71915331d8 Update to jackson-bom 2.13.2.1
Closes gh-721
2022-05-04 10:32:32 -04:00
Joe Grandja
a43abd4d5e Update to Spring Security 5.6.3
Closes gh-720
2022-05-04 10:32:11 -04:00
Joe Grandja
8968187ea4 Update to Spring Boot 2.6.7
Closes gh-719
2022-05-04 10:30:43 -04:00
Steve Riesenberg
3cc89f925e Polish "How-to: Implement core services with JPA"
Issue gh-690
Issue gh-714
2022-05-03 10:46:22 -05:00
Joe Grandja
38e37ae3eb Add Core Model / Components in ref doc
Closes gh-671
2022-05-03 10:25:42 -04:00
Steve Riesenberg
8458d1249e Add overview, getting help, and getting started
Closes gh-667
Closes gh-668
Closes gh-669
2022-05-02 15:10:25 -05:00
Steve Riesenberg
a8974c6cca Fix javadoc search in Java 11
Closes gh-711
2022-04-29 13:59:09 -05:00
Steve Riesenberg
51de75bc01 Preserve scopes in JpaOAuth2AuthorizationService (how-to)
Closes gh-697
2022-04-21 15:40:01 -05:00
Steve Riesenberg
b05bf74a82 Enable jobs to deploy artifacts and docs
Closes gh-695
2022-04-20 15:36:29 -05:00
Steve Riesenberg
a1e1f49606 Add missing environment variables for artifactory
Issue gh-691
2022-04-20 15:35:16 -05:00
Steve Riesenberg
2601dc7e79 Upgrade to Java 11
Closes gh-694
2022-04-20 14:51:22 -05:00
Joe Grandja
4d2a675272 Remove JUnit 5 dependencies
Issue gh-674
2022-04-20 14:01:19 -04:00
Gyeongwon, Do
10859ffc09 Update jdk version in Prerequisities
Issue gh-572
2022-04-20 12:48:26 -05:00
Steve Riesenberg
3ec61890e1 Remove antora and add asciidoctor
Closes gh-690
2022-04-19 17:10:32 -05:00
Steve Riesenberg
005cd2a130 Switch from Jenkins to GitHub Actions
Closes gh-691
2022-04-19 16:53:48 -05:00
Steve Riesenberg
82e28a15f4 Build with Java 11 only 2022-04-19 13:17:27 -05:00
Steve Riesenberg
7845b6a369 Temporarily exclude project from checkFormat task 2022-04-19 13:17:27 -05:00
Steve Riesenberg
72c2e15487 Upgrade build to Gradle 7
Closes gh-572
2022-04-19 13:17:27 -05:00
Steve Riesenberg
00faf44554 Add buildSrc with migrated conventions plugins 2022-04-19 13:17:27 -05:00
Steve Riesenberg
718b86c983 Add "How-to: Implement core services with JPA"
Closes gh-545
2022-03-24 10:03:31 -04:00
Steve Riesenberg
2c4639ce88 Add docs outline with Antora skeleton
Closes gh-554
2022-03-24 10:03:31 -04:00
Joe Grandja
3e6f8ff42a Next Development Version 2022-03-24 07:29:53 -04:00
Joe Grandja
a5c5b7ea43 Release 0.2.3 2022-03-24 07:21:09 -04:00
Joe Grandja
06f1f3726d Update to Reactor 2020.0.16
Closes gh-661
2022-03-24 07:14:23 -04:00
Joe Grandja
b1d5a7a1f8 Update to Spring Security 5.5.5
Closes gh-660
2022-03-24 07:14:02 -04:00
Joe Grandja
166958364a Update to Spring Framework 5.3.16
Closes gh-659
2022-03-24 07:13:42 -04:00
Joe Grandja
16d1d871eb Update to Spring Boot 2.5.10
Closes gh-658
2022-03-24 07:13:09 -04:00
Joe Grandja
8354aaa3cc Dynamic client registration does not generate client_secret for private_key_jwt
Closes gh-657
2022-03-24 06:41:11 -04:00
Joe Grandja
586c7daf2a Apply default settings for public client type
Closes gh-656
2022-03-24 06:03:14 -04:00
Joe Grandja
12ae92b366 Polish gh-630 2022-03-24 04:50:18 -04:00
Gaurav Tiwari
7160290aaf Allow Token Introspection to be customized
Closes gh-493
2022-03-24 04:48:51 -04:00
Joe Grandja
5ab82f83cb Polish gh-628 2022-03-23 11:30:33 -04:00
Joe Grandja
a13df8a1af Move OAuth2TokenGenerator to token package
Issue gh-414
Issue gh-638
Issue gh-639
Issue gh-500
2022-03-23 05:21:32 -04:00
Steve Riesenberg
3fe6f86659 Federated Identity sample
Issue gh-538
Issue gh-499
Issue gh-106
2022-03-22 15:23:33 -04:00
Joe Grandja
ccf4a2de6e Fix expected @Transient Authentication at provider configuration endpoint
Closes gh-632
2022-03-22 15:01:06 -04:00
Joe Grandja
c2db8926df Decompose OAuth2ClientAuthenticationProvider
Closes gh-655
2022-03-22 11:30:23 -04:00
Joe Grandja
5b7d900424 Optimize InMemoryOAuth2AuthorizationService
Closes gh-654
2022-03-22 11:15:07 -04:00
Steve Riesenberg
c3402b0b12 Replaced auth-server with localhost 2022-03-17 14:59:35 -05:00
Joe Grandja
32414451f5 Add support for opaque access tokens
Closes gh-500
2022-03-03 13:30:11 -05:00
Joe Grandja
a661e1cdb7 Use OAuth2TokenGenerator for OAuth2AuthorizationCode
Closes gh-639
2022-02-25 11:28:32 -05:00
Joe Grandja
cdb48f510e Add OAuth2RefreshTokenGenerator
Closes gh-638
2022-02-25 06:03:02 -05:00
Joe Grandja
c799261a72 Introduce OAuth2TokenGenerator
Closes gh-414
2022-02-22 12:37:53 -05:00
Glen Mazza
8b32ace9e5 Add Assert.notNull() for AuthenticationProvider additions
Closes gh-530
2022-02-17 07:58:42 -05:00
Joe Grandja
3812c0e56e Next Development Version 2022-01-26 12:08:10 -05:00
Joe Grandja
cb1b23a5b2 Release 0.2.2 2022-01-26 11:53:49 -05:00
Joe Grandja
d4f539cd7a Update to Jackson 2.12.6
Closes gh-609
2022-01-26 11:46:15 -05:00
Joe Grandja
609b4a56a9 Update to Spring Boot 2.5.9
Closes gh-608
2022-01-26 11:45:47 -05:00
Joe Grandja
0b11d88a62 Update to Reactor 2020.0.15
Closes gh-607
2022-01-26 11:45:30 -05:00
Joe Grandja
37e68803b9 Update to Spring Security 5.5.4
Closes gh-606
2022-01-26 11:45:09 -05:00
Joe Grandja
22ecfd267c Update to Spring Framework 5.3.15
Closes gh-605
2022-01-26 11:44:39 -05:00
Joe Grandja
4fbe06d121 Fix inconsistent state when authorization consent is denied
Closes gh-595
2022-01-26 10:23:06 -05:00
Joe Grandja
58bac49f97 JdbcOAuth2AuthorizationService improves support for large data columns
Closes gh-604
2022-01-26 08:00:24 -05:00
Joe Grandja
f8fdcd7ae9 Polish gh-491 2022-01-25 12:55:56 -05:00
Ovidiu Popa
66bc5a0e65 Support clob and text datatype for token columns
Closes gh-480
2022-01-25 12:50:03 -05:00
Joe Grandja
e175f4fda3 Revert "Increase size for attributes column"
This reverts commit 14cb58df2b.
2022-01-25 11:00:23 -05:00
Daniel Garnier-Moiroux
a1e513b35d Throw invalid_grant when invalid token request with PKCE
Closes gh-581
2022-01-24 03:35:51 -05:00
Joe Grandja
4d5b288116 Polish token revocation
Issue gh-490
2022-01-21 14:36:16 -05:00
Joe Grandja
362c947df1 OidcProviderConfigurationHttpMessageConverter converts userinfo_endpoint
Issue gh-489
2022-01-21 09:37:15 -05:00
Joe Grandja
209248d96d Revert "Add docs outline with Antora skeleton"
This reverts commit edd7cf2434.
2022-01-21 01:47:15 -05:00
Joe Grandja
104d273ba5 Revert "Add "How-to: Implement core services with JPA""
This reverts commit 525eca63d2.
2022-01-21 01:43:02 -05:00
Joe Grandja
dce3f02f66 Revert "Use Java 8 for examples project"
This reverts commit 7b333150a2.
2022-01-21 01:41:30 -05:00
Joe Grandja
e73bef58a3 Revert "Polish "How-to: Implement core services with JPA""
This reverts commit 36d18312b0.
2022-01-21 01:39:33 -05:00
Steve Riesenberg
5412f10ff8 Polish gh-489 2022-01-20 15:40:52 -06:00
Martin Grossi
4081d460a2 Adds userinfo_endpoint to oidc provider configuration
Closes gh-488
2022-01-20 15:40:52 -06:00
Joe Grandja
30d7846122 Deprecate OAuth2TokenIntrospectionClaimAccessor
Closes gh-597
2022-01-20 06:20:18 -05:00
Joe Grandja
1476676cec Deprecate JwtEncoder and associated classes
Closes gh-596
2022-01-20 06:05:31 -05:00
Joe Grandja
165d290374 Use repo.spring.io/release for plugin repository
Issue gh-578
2022-01-18 06:49:58 -05:00
Joe Grandja
9614a252c9 Upgrade io.spring.ge.conventions to 0.0.9
Closes gh-578
2022-01-18 05:51:50 -05:00
Joe Grandja
d302444650 Introduce ProviderContext
Closes gh-479
2022-01-17 15:07:57 -05:00
Bernard Joseph Jean Bruno
1370f7e51a Update gradle enterprise 2022-01-10 13:36:44 -06:00
Steve Riesenberg
36d18312b0 Polish "How-to: Implement core services with JPA" 2022-01-10 13:34:52 -06:00
Steve Riesenberg
7b333150a2 Use Java 8 for examples project 2022-01-07 17:18:27 -06:00
Steve Riesenberg
a20f321a19 Remove docs/manual 2022-01-07 16:50:29 -06:00
Steve Riesenberg
525eca63d2 Add "How-to: Implement core services with JPA" 2022-01-07 16:50:29 -06:00
Steve Riesenberg
edd7cf2434 Add docs outline with Antora skeleton 2022-01-07 16:37:24 -06:00
Joe Grandja
f1a01597d9 Polish gh-293 2021-12-16 12:23:35 -05:00
Rafal Lewczuk
16e4f5130b Client authentication with JWT assertion
Closes gh-59
2021-12-16 06:32:25 -05:00
Steve Riesenberg
13d3567eb4 Polish gh-490 2021-12-14 17:15:59 -06:00
arfatbk
a846e936e9 Allow Token Revocation to be customized
Signed-off-by: arfatbk <arfatbk@gmail.com>
2021-12-14 12:25:32 +05:30
Joe Grandja
d0e1107f36 HttpSessionSecurityContextRepository does not persist @Transient Authentication
Related https://github.com/spring-projects/spring-security/pull/9993

Closes gh-482
2021-12-07 08:02:45 -05:00
Joe Grandja
42095a6da5 Next Development Version 2021-12-01 09:12:04 -05:00
Joe Grandja
28ac43bd50 Release 0.2.1 2021-12-01 08:23:54 -05:00
Joe Grandja
5e684fedbe Authorization Consent request state parameter is validated
Closes gh-503
2021-12-01 07:55:17 -05:00
Joe Grandja
086a3b01a1 Update to jackson-bom 2.12.5
Closes gh-517
2021-11-30 15:38:36 -05:00
Joe Grandja
d10fe36ec0 Update to Spring Boot 2.5.7
Closes gh-516
2021-11-30 15:38:14 -05:00
Joe Grandja
cd2a990df5 Update Reactor to 2020.0.13
Closes gh-515
2021-11-30 15:37:55 -05:00
Joe Grandja
dc77bcdd5a Update to Spring Security 5.5.3
Closes gh-514
2021-11-30 15:37:43 -05:00
Joe Grandja
c0ac18d53a Update to Spring Framework 5.3.13
Closes gh-513
2021-11-30 15:36:40 -05:00
Joe Grandja
14cb58df2b Increase size for attributes column
Issue gh-304
2021-11-30 13:33:48 -05:00
Ovidiu Popa
2e2c9ea286 Fix registration access token cannot be deserialized
Change the authorized scopes Set from SingletonSet to UnmodifiableSet as there is no mixin registered for SingletonSet

Closes gh-495
2021-11-30 13:17:06 -05:00
Joe Grandja
82e4f3a345 Introduce OidcUserInfoAuthenticationContext
Issue gh-441
2021-11-30 11:56:08 -05:00
Joe Grandja
9b60ed23e1 Polish OAuth2AuthorizationConsentAuthenticationContext
Issue gh-470
2021-11-30 06:16:01 -05:00
Joe Grandja
332d1cc318 Polish gh-492 2021-11-30 05:27:32 -05:00
Joe Grandja
8defe2eb3a ProviderSettings @Bean is required
Issue gh-373
2021-11-29 02:22:21 -05:00
Joe Grandja
830f55e538 Revert "Support resolving issuer from current request"
This reverts commit 666d569b48.
2021-11-29 01:49:26 -05:00
Joe Grandja
c418306fd9 Polish Authorization Consent Deny Request
Issue gh-470
2021-11-26 06:46:05 -05:00
Joe Grandja
9053e3188d Fix broken documentation links in README
Closes gh-494
2021-11-17 05:31:46 -05:00
Joe Grandja
f0b19f30d1 Deprecate PasswordEncoder in JdbcRegisteredClientRepository
Closes gh-496
2021-11-16 08:54:50 -05:00
Joe Grandja
666d569b48 Support resolving issuer from current request
Closes gh-479
2021-11-15 15:17:51 -05:00
Joe Grandja
646ea00db2 Add @since
Issue gh-427
2021-11-15 11:26:54 -05:00
Joe Grandja
d4357197c9 Polish gh-470 2021-11-12 11:54:59 -05:00
Steve Riesenberg
4ce999c014 Customize OAuth2AuthorizationConsent prior to saving
Closes gh-436
2021-11-12 11:54:59 -05:00
Joe Grandja
5fa1e8e3b1 Allow subclassing OAuth2AuthenticationContext
Closes gh-492
2021-11-12 11:44:57 -05:00
Joe Grandja
5982d2285c Restructure samples
Closes gh-485
2021-11-09 06:01:51 -05:00
Joe Grandja
b455268fa1 Polish OAuth2ClientAuthenticationProviderTests 2021-11-03 12:31:38 -04:00
Joe Grandja
25c4a7d541 Polish test gh-448 2021-11-03 07:56:18 -04:00
Daniel Garnier-Moiroux
088d9a8e34 Require code_verifier if code_challenge provided
Closes gh-453
2021-11-03 06:52:20 -04:00
Steve Riesenberg
defd1f90b8 Polish gh-448 2021-11-02 21:35:03 -05:00
figozhang
c9954af084 Customize authenticationDetailsSource of OAuth2TokenEndpointFilter
Closes gh-431
2021-11-02 21:32:52 -05:00
Josh Long
dc0ca22f5e Update README.adoc 2021-11-02 15:38:14 -04:00
Joe Grandja
c7f01f0795 Polish gh-427 2021-10-27 19:47:38 -04:00
Ovidiu Popa
37e45619ae Implement Client Configuration Endpoint
See: https://openid.net/specs/openid-connect-registration-1_0.html#ClientConfigurationEndpoint

Generate registration_client_uri and registration_access_token when registering a new client (see: https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration)

Closes gh-355
2021-10-25 15:00:39 -04:00
Joe Grandja
72c5e24ab8 Polish gh-441 2021-10-25 12:26:53 -04:00
Steve Riesenberg
8e8e6d1b17 Implement User Info Endpoint
Closes gh-176
2021-10-25 12:26:28 -04:00
Ido Salomon
9667229429 Initial implementation of User Info Endpoint
Issue gh-176
2021-10-25 12:25:31 -04:00
Alexey Makarov
33bac0f7c2 JdbcOAuth2AuthorizationService now uses LobCreator in findBy method
Closes gh-455
2021-10-21 19:54:41 -04:00
Joe Grandja
63c248440c Fix PKCE tests in OAuth2ClientAuthenticationProviderTests 2021-10-21 18:21:15 -04:00
Steve Riesenberg
71be32b245 Add support for deserializing LinkedHashSet
This is needed because OAuth2ClientCredentialsAuthenticationProvider stores authorized scopes in a LinkedHashSet.

Closes gh-457
2021-10-21 11:49:54 -05:00
Sarah McAlear
4b19637fbb Update RegisteredClient.Builder to use getters
- Since the class is not final, it is possible to extend it.
  Because the Builder was directly accessing the properties
  rather than using the getters, it was not possible to use
  the .from(id) constructor in the extended class.

Closes gh-451
2021-10-14 15:27:10 -04:00
Daniel Garnier-Moiroux
26f15b99bb Make OAuth2ClientAuthenticationToken @Transient 2021-10-14 15:17:57 -04:00
Joe Grandja
f3c17b3de0 Specify Jenkins user in Jenkinsfile 2021-09-23 16:00:14 -04:00
Ovidiu Popa
298ebc7c01 Avoid client secret double encoding when updating a registered client
This might have to be revisited at a later point, but to check if a value is encoded or not is quite tricky. The decision was to remove client_secret and client_secret_expires_at from the update statement

Closes gh-389
2021-09-23 13:51:56 -04:00
Joe Grandja
0735abdaad Polish gh-411 2021-09-23 12:01:42 -04:00
Dmitriy Dubson
0dfe5cb44a Fix cancel consent functionality on default consent page
- Fix also applies to custom consent sample

Closes gh-393
2021-09-23 08:17:54 -04:00
Joe Grandja
4ccdd2baf4 OAuth2TokenIntrospectionAuthenticationProvider checks for null issuer
Closes gh-438
2021-09-23 06:25:07 -04:00
Joe Grandja
e4ce97b887 Access token is active after revoke then refresh
Closes gh-432
2021-09-22 10:34:55 -04:00
Kirat Kumar
aaeca70b4c Removed an empty statement 2021-09-21 15:45:04 -04:00
Joe Grandja
8e8979af60 Next Development Version 2021-08-18 21:01:07 -04:00
Joe Grandja
725c300db2 Release 0.2.0 2021-08-18 20:14:55 -04:00
Joe Grandja
f3f69b300f Authorization failure does not clear current Authentication
Closes gh-409
2021-08-18 20:00:02 -04:00
Joe Grandja
c926884049 Update to nimbus-jose-jwt 9.10.1
Closes gh-408
2021-08-18 16:13:00 -04:00
Joe Grandja
0e99adc72e Update to jackson-bom 2.12.4
Closes gh-407
2021-08-18 16:12:47 -04:00
Joe Grandja
c444544a27 Update to Spring Boot 2.5.3
Closes gh-406
2021-08-18 16:12:20 -04:00
Joe Grandja
84c24b344f Update Reactor to 2020.0.10
Closes gh-405
2021-08-18 16:12:03 -04:00
Joe Grandja
34d4131968 Update to Spring Security 5.5.2
Closes gh-404
2021-08-18 16:11:23 -04:00
Joe Grandja
d3f25dd6ea Update to Spring Framework 5.3.9
Closes gh-403
2021-08-18 16:10:54 -04:00
Joe Grandja
f3c29bd545 Use OAuth2AuthenticationException(String errorCode)
Closes gh-402
2021-08-18 15:23:45 -04:00
Joe Grandja
ea1f95b4ed Replace stream usage with for loops
Closes gh-401
2021-08-18 13:42:08 -04:00
Joe Grandja
42d611828a Polish OAuth2TokenCustomizer 2021-08-18 11:26:12 -04:00
Joe Grandja
9388002158 Add javadoc for OAuth2TokenCustomizer
Issue gh-199
2021-08-18 10:58:05 -04:00
Anoop Garlapati
1d4dcddc11 Polish loopback address validation in DefaultRedirectUriOAuth2AuthenticationValidator
Changed loopback address validation from regex to explicit
validation using IPv4 loopback address range and IPv6 address.

Issue gh-243
2021-08-17 15:00:42 -04:00
Joe Grandja
3ee47efff7 Disable Oidc client registration by default
Closes gh-398
2021-08-17 10:04:19 -04:00
Joe Grandja
fe27e39c5d Extract configurer for OpenID Connect 1.0 support
Issue gh-398
2021-08-17 10:03:54 -04:00
Joe Grandja
57aadceb17 Remove references to experimental 2021-08-17 05:58:41 -04:00
Joe Grandja
5484931892 Update CONTRIBUTING 2021-08-17 05:42:56 -04:00
Joe Grandja
b5db5ffe54 Update README 2021-08-17 05:14:43 -04:00
Joe Grandja
9312c1807b Add support policy 2021-08-16 14:45:49 -04:00
Joe Grandja
7680505eed Move OAuth2AuthorizationCode
Closes gh-395
2021-08-13 04:56:24 -04:00
Joe Grandja
d15a68514d Polish OAuth2Authorization 2021-08-13 04:24:34 -04:00
Joe Grandja
53ed5b8481 Polish OAuth2TokenContext 2021-08-13 04:11:31 -04:00
Joe Grandja
c89f2f3819 Polish PublicClientAuthenticationConverter 2021-08-13 03:15:04 -04:00
Joe Grandja
ebecb2a7f6 Polish ClientSecretPostAuthenticationConverter 2021-08-13 03:14:25 -04:00
Joe Grandja
4995acc825 Polish ClientSecretBasicAuthenticationConverter 2021-08-13 03:13:34 -04:00
Joe Grandja
a4a61fcf50 Polish ConfigurationSettingNames 2021-08-13 02:37:03 -04:00
Joe Grandja
86997bc0ac Polish RegisteredClient 2021-08-12 17:06:13 -04:00
Joe Grandja
a740e819ae Polish OAuth2TokenRevocationAuthenticationToken 2021-08-12 16:53:29 -04:00
Joe Grandja
c7815939d2 Validate redirect_uri on dynamic client registration
Closes gh-392
2021-08-10 09:28:26 -04:00
Joe Grandja
2c8d5a19ac Remove comment in OAuth2AuthorizationCodeRequestAuthenticationProvider 2021-08-10 05:20:59 -04:00
Joe Grandja
6b5d9f0fe5 Polish JwtEncoder APIs
Closes gh-391
2021-08-10 04:49:27 -04:00
Joe Grandja
ea7c68997f Polish gh-381 2021-08-09 09:27:51 -04:00
Steve Riesenberg
115a78d5f5 Add post processor to register ProviderSettings Bean
Closes gh-373
2021-07-30 11:58:42 -04:00
Ovidiu Popa
1929e3a80a JdbcRegisteredClientRepository hashes client secret on save
Closes gh-378
2021-07-30 11:11:32 -04:00
Joe Grandja
7546d18a40 Polish gh-379 2021-07-30 09:55:29 -04:00
Steve Riesenberg
83915e8421 Do not issue refresh token to public client
Closes gh-296
2021-07-30 09:55:29 -04:00
Joe Grandja
0493bbf1d1 OAuth2ClientAuthenticationToken supports any type of credentials
Closes gh-382
2021-07-30 09:54:56 -04:00
Ovidiu Popa
41f8c9cd00 Add update support in JdbcRegisteredClientRepository
Closes gh-356
2021-07-29 11:10:36 -04:00
Joe Grandja
3d4df8807d Provide configuration for client authentication
Closes gh-380
2021-07-29 10:24:00 -04:00
Joe Grandja
850bd76aee Polish OAuth2ClientAuthenticationFilter 2021-07-29 05:55:37 -04:00
Joe Grandja
7f294abfbb Polish gh-376 2021-07-28 06:07:52 -04:00
Joe Grandja
3ea7d8c9b6 Provide configuration for refresh token generator
Closes gh-377
2021-07-28 06:02:56 -04:00
Joe Grandja
06ad211fce Provide configuration for authorization code generator
Closes gh-376
2021-07-28 04:56:05 -04:00
Joe Grandja
84e53f635c Remove Context.of()
Closes gh-375
2021-07-27 05:02:19 -04:00
Joe Grandja
f6c4d49b9f Introduce OAuth2AuthenticationValidator
Closes gh-374
2021-07-27 04:28:23 -04:00
Joe Grandja
06f2845ac0 Extract constants from Settings implementations
Closes gh-369
2021-07-23 10:50:18 -04:00
Joe Grandja
a3b14a97d6 Remove OAuth2ErrorCodes2
Closes gh-368
2021-07-23 06:25:50 -04:00
Joe Grandja
0723936b8a Remove OAuth2RefreshToken2
Closes gh-367
2021-07-23 06:18:30 -04:00
Joe Grandja
0d7727a7d4 Make Settings implementations immutable
Closes gh-366
2021-07-23 05:50:17 -04:00
Joe Grandja
79fd004346 Use OAuth2Token in OAuth2Authorization
Closes gh-364
2021-07-22 04:04:29 -04:00
Joe Grandja
70142f3705 Rename ClientSettings.requireUserConsent() to requireAuthorizationConsent()
Closes gh-363
2021-07-21 14:37:34 -04:00
Joe Grandja
c42f80c280 Remove deprecated code
Closes gh-362
2021-07-21 13:56:10 -04:00
Joe Grandja
a7feab605b Remove OAuth2ParameterNames2
Closes gh-361
2021-07-21 13:35:59 -04:00
Joe Grandja
51966d52d5 Make AuthenticationProvider implementations final
Closes gh-360
2021-07-21 11:26:57 -04:00
Joe Grandja
20d47ecaa0 Make Filter implementations final
Closes gh-359
2021-07-21 11:08:38 -04:00
Joe Grandja
d85ce0a6dd Reduce visibility of default endpoint URI constants
Closes gh-358
2021-07-21 10:42:59 -04:00
Joe Grandja
5593208e61 Move AuthenticationConverter's to web.authentication package
Closes gh-357
2021-07-21 09:32:24 -04:00
Joe Grandja
beb1233358 Rename OAuth2TokenIntrospectionClaimAccessor.getScope() to getScopes()
Closes gh-354
2021-07-21 06:34:03 -04:00
Joe Grandja
75d649578a Polish gh-350 2021-07-20 06:31:52 -04:00
Bibibiu
bd98031036 Remove use of deprecated ClientAuthenticationMethod's
Closes gh-346
2021-07-20 05:38:01 -04:00
Steve Riesenberg
687f03f047 Fix windows test failures 2021-07-14 11:11:56 -05:00
Joe Grandja
3dfcbfe136 Next Development Version 2021-07-09 08:35:47 -04:00
349 changed files with 23091 additions and 5910 deletions

View File

@@ -1,4 +1,4 @@
name: CI
name: Build and Deploy
on:
push:
@@ -7,48 +7,83 @@ on:
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC
env:
RUN_JOBS: ${{ github.repository == 'spring-projects/spring-authorization-server' }}
jobs:
prerequisites:
name: Pre-requisites for building
runs-on: ubuntu-latest
outputs:
runjobs: ${{ steps.continue.outputs.runjobs }}
project_version: ${{ steps.continue.outputs.project_version }}
steps:
- uses: actions/checkout@v2
- id: continue
name: Determine if should continue
if: env.RUN_JOBS == 'true'
run: |
# Run jobs if in upstream repository
echo "::set-output name=runjobs::true"
# Extract version from gradle.properties
version=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}')
echo "::set-output name=project_version::$version"
build:
name: Build
runs-on: ${{ matrix.os }}
needs: [prerequisites]
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
jdk: [8,11,12]
os: [ubuntu-latest, windows-latest]
jdk: [8,11,17]
fail-fast: false
runs-on: ${{ matrix.os }}
if: needs.prerequisites.outputs.runjobs
steps:
- uses: actions/checkout@v2
- name: Set up JDK ${{ matrix.jdk }}
uses: actions/setup-java@v1
with:
java-version: ${{ matrix.jdk }}
- name: Setup gradle user name
run: |
mkdir -p ~/.gradle
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
env:
GRADLE_USER_HOME: ~/.gradle
- name: Build with Gradle
run: ./gradlew clean build
env:
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
run: ./gradlew clean build --continue -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD"
snapshot_tests:
name: Test against snapshots
needs: [prerequisites]
runs-on: ubuntu-latest
if: needs.prerequisites.outputs.runjobs
steps:
- uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: 8
- name: Test
run: echo Testing against snapshots
sonar:
name: Static Code Analysis
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: 8
- name: Sonar
run: echo Running Sonarqube static code analysis
artifacts:
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Snapshot Tests
env:
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
run: ./gradlew test --refresh-dependencies -Duser.name=spring-builds+github -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PforceMavenRepositories=snapshot -PspringFrameworkVersion='5.3.+' -PspringSecurityVersion='5.7.+' -PlocksDisabled --stacktrace
deploy_artifacts:
name: Deploy Artifacts
needs: [build, snapshot_tests, sonar]
needs: [build, snapshot_tests]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -56,11 +91,23 @@ jobs:
uses: actions/setup-java@v1
with:
java-version: 8
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Deploy Artifacts
run: echo Deploying Artifacts
docs:
env:
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.GPG_PRIVATE_KEY }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.GPG_PASSPHRASE }}
OSSRH_TOKEN_USERNAME: ${{ secrets.OSSRH_S01_TOKEN_USERNAME }}
OSSRH_TOKEN_PASSWORD: ${{ secrets.OSSRH_S01_TOKEN_PASSWORD }}
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
run: ./gradlew publishArtifacts finalizeDeployArtifacts -Duser.name=spring-builds+github -PossrhUsername="$OSSRH_TOKEN_USERNAME" -PossrhPassword="$OSSRH_TOKEN_PASSWORD" -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" --stacktrace
deploy_docs:
name: Deploy Docs
needs: [build, snapshot_tests, sonar]
needs: [build, snapshot_tests]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -68,17 +115,14 @@ jobs:
uses: actions/setup-java@v1
with:
java-version: 8
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Deploy Docs
run: echo Deploying Docs
schema:
name: Deploy Schema
needs: [build, snapshot_tests, sonar]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: 8
- name: Deploy Schema
run: echo Deploying Schema
env:
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }}
DOCS_SSH_KEY: ${{ secrets.DOCS_SSH_KEY }}
DOCS_HOST: ${{ secrets.DOCS_HOST }}
run: ./gradlew deployDocs -PdeployDocsSshKey="$DOCS_SSH_KEY" -PdeployDocsSshUsername="$DOCS_USERNAME" -PdeployDocsHost="$DOCS_HOST" --stacktrace

View File

@@ -11,8 +11,8 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
jdk: [8,11,12]
os: [ubuntu-latest, windows-latest]
jdk: [8]
fail-fast: false
steps:
- uses: actions/checkout@v2

View File

@@ -1,7 +1,7 @@
= Contributing to Spring Authorization Server
Spring Authorization Server is released under the Apache 2.0 license.
If you would like to contribute something, or simply want to hack on the code this document should help you https://github.com/spring-projects-experimental/spring-authorization-server#getting-started[get started].
If you would like to contribute something, or simply want to hack on the code this document should help you https://github.com/spring-projects/spring-authorization-server#getting-started[get started].
== Code of Conduct
This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code of conduct].
@@ -18,7 +18,7 @@ Ideally, that would include a https://stackoverflow.com/help/minimal-reproducibl
== Submitting Pull Requests
This project uses https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests[pull requests] for the community to suggest changes to the project.
There are a few imporant things to keep in mind when submitting a pull request:
There are a few important things to keep in mind when submitting a pull request:
* Expect feedback and to make changes to your contributions.
* Unless it is a minor change:
@@ -32,7 +32,7 @@ That may mean using an external library directly in a `Filter`.
== Reporting Security Vulnerabilities
If you think you have found a security vulnerability please *DO NOT* disclose it publicly until we've had a chance to fix it.
Please don't report security vulnerabilities using GitHub issues, instead head over to https://pivotal.io/security and learn how to disclose them responsibly.
Please don't report security vulnerabilities using GitHub issues, instead head over to https://spring.io/security-policy and learn how to disclose them responsibly.
== Sign the Contributor License Agreement
Before we accept a non-trivial patch or pull request we will need you to https://cla.pivotal.io/sign/spring[sign the Contributor License Agreement].
@@ -45,7 +45,7 @@ Please add the Apache License header to all new classes, for example:
```java
/*
* Copyright 2020 the original author or authors.
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

104
Jenkinsfile vendored
View File

@@ -1,104 +0,0 @@
def projectProperties = [
[$class: 'BuildDiscarderProperty',
strategy: [$class: 'LogRotator', numToKeepStr: '5']],
pipelineTriggers([cron('@daily')])
]
properties(projectProperties)
def SUCCESS = hudson.model.Result.SUCCESS.toString()
currentBuild.result = SUCCESS
def GRADLE_ENTERPRISE_CACHE_USER = usernamePassword(credentialsId: 'gradle_enterprise_cache_user',
passwordVariable: 'GRADLE_ENTERPRISE_CACHE_PASSWORD',
usernameVariable: 'GRADLE_ENTERPRISE_CACHE_USERNAME')
def GRADLE_ENTERPRISE_SECRET_ACCESS_KEY = string(credentialsId: 'gradle_enterprise_secret_access_key',
variable: 'GRADLE_ENTERPRISE_ACCESS_KEY')
def SPRING_SIGNING_SECRING = file(credentialsId: 'spring-signing-secring.gpg', variable: 'SIGNING_KEYRING_FILE')
def SPRING_GPG_PASSPHRASE = string(credentialsId: 'spring-gpg-passphrase', variable: 'SIGNING_PASSWORD')
def OSSRH_S01_CREDENTIALS = usernamePassword(credentialsId: 'oss-s01-token', passwordVariable: 'OSSRH_S01_TOKEN_PASSWORD', usernameVariable: 'OSSRH_S01_TOKEN_USERNAME')
def ARTIFACTORY_CREDENTIALS = usernamePassword(credentialsId: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD')
def JENKINS_PRIVATE_SSH_KEY = file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')
def SONAR_LOGIN_CREDENTIALS = string(credentialsId: 'spring-sonar.login', variable: 'SONAR_LOGIN')
def jdkEnv(String jdk = 'jdk8') {
def jdkTool = tool(jdk)
return "JAVA_HOME=${ jdkTool }"
}
try {
parallel check: {
stage('Check') {
node {
checkout scm
sh "git clean -dfx"
try {
withCredentials([ARTIFACTORY_CREDENTIALS,
GRADLE_ENTERPRISE_CACHE_USER,
GRADLE_ENTERPRISE_SECRET_ACCESS_KEY]) {
withEnv([jdkEnv(),
"GRADLE_ENTERPRISE_CACHE_USERNAME=${GRADLE_ENTERPRISE_CACHE_USERNAME}",
"GRADLE_ENTERPRISE_CACHE_PASSWORD=${GRADLE_ENTERPRISE_CACHE_PASSWORD}",
"GRADLE_ENTERPRISE_ACCESS_KEY=${GRADLE_ENTERPRISE_ACCESS_KEY}"]) {
sh "./gradlew check -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --stacktrace"
}
}
} catch(Exception e) {
currentBuild.result = 'FAILED: check'
throw e
} finally {
junit '**/build/test-results/*/*.xml'
}
}
}
}
if(currentBuild.result == 'SUCCESS') {
parallel artifacts: {
stage('Deploy Artifacts') {
node {
checkout scm
sh "git clean -dfx"
withCredentials([SPRING_SIGNING_SECRING,
SPRING_GPG_PASSPHRASE,
OSSRH_S01_CREDENTIALS,
ARTIFACTORY_CREDENTIALS,
GRADLE_ENTERPRISE_CACHE_USER,
GRADLE_ENTERPRISE_SECRET_ACCESS_KEY]) {
withEnv([jdkEnv(),
"GRADLE_ENTERPRISE_CACHE_USERNAME=${GRADLE_ENTERPRISE_CACHE_USERNAME}",
"GRADLE_ENTERPRISE_CACHE_PASSWORD=${GRADLE_ENTERPRISE_CACHE_PASSWORD}",
"GRADLE_ENTERPRISE_ACCESS_KEY=${GRADLE_ENTERPRISE_ACCESS_KEY}"]) {
sh "./gradlew deployArtifacts finalizeDeployArtifacts -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password='$SIGNING_PASSWORD' -PossrhTokenUsername=$OSSRH_S01_TOKEN_USERNAME -PossrhTokenPassword=$OSSRH_S01_TOKEN_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --stacktrace"
}
}
}
}
}
}
} catch(Exception e) {
currentBuild.result = 'FAILED: deploys'
throw e
} finally {
def buildStatus = currentBuild.result
def buildNotSuccess = !SUCCESS.equals(buildStatus)
def lastBuildNotSuccess = !SUCCESS.equals(currentBuild.previousBuild?.result)
if(buildNotSuccess || lastBuildNotSuccess) {
stage('Notifiy') {
node {
final def RECIPIENTS = [[$class: 'DevelopersRecipientProvider'], [$class: 'RequesterRecipientProvider']]
def subject = "${buildStatus}: Build ${env.JOB_NAME} ${env.BUILD_NUMBER} status is now ${buildStatus}"
def details = """The build status changed to ${buildStatus}. For details see ${env.BUILD_URL}"""
emailext (
subject: subject,
body: details,
recipientProviders: RECIPIENTS,
to: "$SPRING_SECURITY_TEAM_EMAILS"
)
}
}
}
}

View File

@@ -1,27 +1,26 @@
image::https://badges.gitter.im/Join%20Chat.svg[Gitter,link=https://gitter.im/spring-projects/spring-security?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge]
image:https://github.com/spring-projects-experimental/spring-authorization-server/workflows/CI/badge.svg?branch=main["Build Status", link="https://github.com/spring-projects-experimental/spring-authorization-server/actions?query=workflow%3ACI"]
image:https://github.com/spring-projects/spring-authorization-server/workflows/CI/badge.svg?branch=main["Build Status", link="https://github.com/spring-projects/spring-authorization-server/actions?query=workflow%3ACI"]
= Spring Authorization Server
Spring Authorization Server is a community-driven project led by the https://spring.io/projects/spring-security/[Spring Security] team and is focused on delivering https://tools.ietf.org/html/rfc6749#section-1.1[OAuth 2.0 Authorization Server] support to the Spring community.
The Spring Authorization Server project, led by the https://spring.io/projects/spring-security/[Spring Security] team, is focused on delivering https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-1.1[OAuth 2.1 Authorization Server] support to the Spring community.
The project will start in Spring's experimental projects as an independent project so that it can evolve more rapidly.
The ultimate goal of this project is to replace the Authorization Server support provided by https://spring.io/projects/spring-security-oauth/[Spring Security OAuth].
With the much needed help from our community, this project will grow in the same way that the original Spring Security OAuth project did.
This project replaces the Authorization Server support provided by https://spring.io/projects/spring-security-oauth/[Spring Security OAuth].
== Feature Planning
This project uses https://www.zenhub.com/[ZenHub] to prioritize the feature roadmap and help organize the project plan.
The project board can be accessed https://app.zenhub.com/workspaces/authorization-server-5e8f3182b5e8f5841bfc4902/board?repos=248032165[here].
It is recommended to install the ZenHub https://www.zenhub.com/extension[browser extension] as it integrates natively within GitHub's user interface.
The completed and upcoming feature list can be viewed in the https://github.com/spring-projects-experimental/spring-authorization-server/wiki/Feature-List[wiki].
The feature list can be viewed in the https://docs.spring.io/spring-authorization-server/docs/current/reference/html/overview.html#feature-list[reference documentation].
== Support Policy
The Spring Authorization Server project provides software support and is documented in its link:SUPPORT_POLICY.adoc[support policy].
== Getting Started
The first place to start is to read the https://tools.ietf.org/html/rfc6749[OAuth 2.0 Authorization Framework] to gain an in-depth understanding on how to build an Authorization Server.
It is a critically important first step as the implementation must conform to the specification defined in the OAuth 2.0 Authorization Framework and the https://github.com/spring-projects-experimental/spring-authorization-server/wiki/OAuth-2.0-Specifications[related specifications].
The first place to start is to read the https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01[OAuth 2.1 Authorization Framework] to gain an in-depth understanding on how to build an Authorization Server.
It is a critically important first step as the implementation must conform to the specification defined in the OAuth 2.1 Authorization Framework and the https://github.com/spring-projects/spring-authorization-server/wiki/OAuth-2.0-Specifications[related specifications].
The second place to start is to become very familiar with the codebase in the following Spring Security modules:
@@ -36,7 +35,7 @@ The goal is to leverage all the knowledge learned thus far and apply the same to
Submitted work via pull requests should follow the same coding style/conventions and adopt the same or similar design patterns that have been established in Spring Security's OAuth 2.0 support.
== Documentation
Be sure to read the https://docs.spring.io/spring-security/site/docs/current/reference/html5/[Spring Security Reference], as well as the https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2[OAuth 2.0 Reference], which describes the Client and Resource Server features available.
Be sure to read the https://docs.spring.io/spring-security/reference[Spring Security Reference], as well as the https://docs.spring.io/spring-security/reference/servlet/oauth2/index.html[OAuth 2.0 Reference], which describes the Client and Resource Server features available.
Extensive JavaDoc for the Spring Security code is also available in the https://docs.spring.io/spring-security/site/docs/current/api/[Spring Security API Documentation].
@@ -60,7 +59,7 @@ Be sure that your `JAVA_HOME` environment variable points to the `jdk1.8.0` fold
=== Check out sources
[indent=0]
----
git clone git@github.com:spring-projects-experimental/spring-authorization-server.git
git clone git@github.com:spring-projects/spring-authorization-server.git
----

21
SUPPORT_POLICY.adoc Normal file
View File

@@ -0,0 +1,21 @@
= Spring Authorization Server Support Policy
The Spring Authorization Server support offering provides the following support terms:
* Releases are currently in the format of 0.x.y, where:
** “x” contains new features and potentially breaking changes.
** “y” contains new features and bug fixes and provides backward compatibility.
* The Spring Authorization Server project will be supported for at least 3 years after the most recent 0.x.0 release is made available for download.
* Security fixes will be provided for at least one year after the 0.x.0 release is made available for download. Security fixes will not be provided for updating versions to third-party libraries.
* Feature support and bug fixes, excluding “Security fixes”, will be provided only for the latest 0.x.y release.
* This support policy starts with version 0.2.0.
* We will switch to the standard https://tanzu.vmware.com/support/oss[Spring OSS support policy] when the Spring Authorization Server project reaches version 1.0.0.
An example can help us understand all of these points.
Assume that 0.2.0 is released in August of 2021.
This means that the Spring Authorization Server project is supported until at least August of 2024.
If 0.3.0 is then released in May of 2022, the Spring Authorization Server project is supported until at least May of 2025.
The 0.3.0 release may contain breaking changes from 0.2.0.
If a bug is found, only 0.3.0 will be patched in a 0.3.1 release.
If a security vulnerability is found, a 0.2.4 (assume 0.2.3 is latest) and 0.3.1 release will be provided to fix the security vulnerability.
However, a vulnerability found in September of 2022 would be fixed in the 0.3.1 release but not the 0.2.3 release, because the vulnerability was discovered more than a year after the 0.2.0 release date.

View File

@@ -1,47 +1,13 @@
buildscript {
dependencies {
classpath 'io.spring.gradle:spring-build-conventions:0.0.38'
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
classpath 'io.spring.nohttp:nohttp-gradle:0.0.8'
}
repositories {
maven {
url = 'https://repo.spring.io/plugins-snapshot'
if (project.hasProperty('artifactoryUsername')) {
credentials {
username "$artifactoryUsername"
password "$artifactoryPassword"
}
}
}
maven { url 'https://plugins.gradle.org/m2/' }
}
plugins {
id "io.spring.convention.root"
}
apply plugin: 'io.spring.nohttp'
apply plugin: 'locks'
apply plugin: 'io.spring.convention.root'
group = "org.springframework.security"
description = "Spring Authorization Server"
group = 'org.springframework.security.experimental'
description = 'Spring Authorization Server'
ext.snapshotBuild = version.contains("SNAPSHOT")
repositories {
mavenCentral()
}
dependencyManagementExport.projects = subprojects.findAll { !it.name.contains('-boot') }
subprojects {
plugins.withType(JavaPlugin) {
project.sourceCompatibility = "1.8"
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
if (hasProperty("buildScan")) {
buildScan {
termsOfServiceUrl = "https://gradle.com/terms-of-service"
termsOfServiceAgree = "yes"
}
}
nohttp {
allowlistFile = project.file("etc/nohttp/allowlist.lines")
}

View File

@@ -1,14 +1,27 @@
apply plugin: "java-gradle-plugin"
plugins {
id "java-gradle-plugin"
id "java"
id "groovy"
}
sourceCompatibility = JavaVersion.VERSION_1_8
repositories {
gradlePluginPortal()
mavenCentral()
maven { url "https://repo.spring.io/plugins-release/" }
}
gradlePlugin {
plugins {
locks {
id = "locks"
implementationClass = "lock.GlobalLockPlugin"
}
}
dependencies {
implementation "com.github.ben-manes:gradle-versions-plugin:0.38.0"
implementation "io.github.gradle-nexus:publish-plugin:1.1.0"
implementation "io.spring.javaformat:spring-javaformat-gradle-plugin:0.0.31"
implementation "io.spring.nohttp:nohttp-gradle:0.0.10"
implementation "org.asciidoctor:asciidoctor-gradle-jvm:3.3.2"
implementation "org.asciidoctor:asciidoctor-gradle-jvm-pdf:3.3.2"
implementation "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10"
implementation "org.hidetake:gradle-ssh-plugin:2.10.1"
implementation "org.jfrog.buildinfo:build-info-extractor-gradle:4.26.1"
implementation "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1"
implementation "org.springframework:spring-core:5.3.17"
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.gradle.docs
import org.gradle.api.Plugin
import org.gradle.api.Project
class SpringDeployDocsPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.getPluginManager().apply('org.hidetake.ssh')
project.ssh.settings {
knownHosts = allowAnyHosts
}
project.remotes {
docs {
role 'docs'
if (project.hasProperty('deployDocsHost')) {
host = project.findProperty('deployDocsHost')
}
retryCount = 5 // retry 5 times (default is 0)
retryWaitSec = 10 // wait 10 seconds between retries (default is 0)
user = project.findProperty('deployDocsSshUsername')
if (project.hasProperty('deployDocsSshKeyPath')) {
identity = project.file(project.findProperty('deployDocsSshKeyPath'))
} else if (project.hasProperty('deployDocsSshKey')) {
identity = project.findProperty('deployDocsSshKey')
}
if (project.hasProperty('deployDocsSshPassphrase')) {
passphrase = project.findProperty('deployDocsSshPassphrase')
}
}
}
project.task('deployDocs') {
dependsOn 'docsZip'
doFirst {
project.ssh.run {
session(project.remotes.docs) {
def now = System.currentTimeMillis()
def name = project.rootProject.name
def version = project.rootProject.version
def tempPath = "/tmp/${name}-${now}-docs/".replaceAll(' ', '_')
execute "mkdir -p $tempPath"
project.tasks.docsZip.outputs.each { o ->
put from: o.files, into: tempPath
}
execute "unzip $tempPath*.zip -d $tempPath"
def extractPath = "/var/www/domains/spring.io/docs/htdocs/autorepo/docs/${name}/${version}/"
execute "rm -rf $extractPath"
execute "mkdir -p $extractPath"
execute "mv $tempPath/docs/* $extractPath"
execute "chmod -R g+w $extractPath"
}
}
}
}
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.gradle.convention;
import java.io.File;
import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.file.DuplicatesStrategy;
import org.gradle.api.plugins.BasePlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.PluginManager;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.bundling.Zip;
import org.springframework.gradle.docs.SpringAsciidoctorPlugin;
import org.springframework.gradle.docs.SpringJavadocApiPlugin;
import org.springframework.gradle.docs.SpringJavadocOptionsPlugin;
import org.springframework.gradle.management.SpringManagementConfigurationPlugin;
import org.springframework.gradle.maven.SpringRepositoryPlugin;
/**
* Aggregates asciidoc, javadoc, and deploying of the docs into a single plugin.
*
* @author Steve Riesenberg
*/
public class SpringDocsPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
// Apply default plugins
PluginManager pluginManager = project.getPluginManager();
pluginManager.apply(BasePlugin.class);
pluginManager.apply(JavaPlugin.class);
pluginManager.apply(SpringManagementConfigurationPlugin.class);
pluginManager.apply(SpringRepositoryPlugin.class);
pluginManager.apply(SpringAsciidoctorPlugin.class);
// Note: Applying plugin via id since it requires groovy compilation
pluginManager.apply("org.springframework.gradle.deploy-docs");
pluginManager.apply(SpringJavadocApiPlugin.class);
pluginManager.apply(SpringJavadocOptionsPlugin.class);
TaskContainer tasks = project.getTasks();
project.configure(tasks.withType(AbstractAsciidoctorTask.class), (task) -> {
File destination = new File(project.getBuildDir(), "docs");
task.setOutputDir(destination);
task.sources((patternSet) -> {
patternSet.include("**/*.adoc");
patternSet.exclude("_*/**");
});
});
// Add task to create documentation archive
Zip docsZip = tasks.create("docsZip", Zip.class, (zip) -> {
zip.dependsOn(tasks.getByName("api"), tasks.getByName("asciidoctor")/*, tasks.getByName("asciidoctorPdf")*/);
zip.setGroup("Distribution");
zip.getArchiveBaseName().set(project.getRootProject().getName());
zip.getArchiveClassifier().set("docs");
zip.setDescription("Builds -docs archive containing all " +
"Docs for deployment at docs.spring.io");
zip.into("docs");
zip.setDuplicatesStrategy(DuplicatesStrategy.EXCLUDE);
});
// Add task to aggregate documentation
Task docs = tasks.create("docs", (task) -> {
task.dependsOn(docsZip);
task.setGroup("Documentation");
task.setDescription("An aggregator task to generate all the documentation");
});
// Wire docs task into the build
tasks.getByName("assemble").dependsOn(docs);
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.gradle.convention;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaLibraryPlugin;
import org.gradle.api.plugins.PluginManager;
import org.springframework.gradle.SpringJavaPlugin;
import org.springframework.gradle.SpringMavenPlugin;
import org.springframework.gradle.classpath.SpringCheckClasspathForProhibitedDependenciesPlugin;
/**
* @author Steve Riesenberg
*/
public class SpringModulePlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
// Apply default plugins
PluginManager pluginManager = project.getPluginManager();
pluginManager.apply(JavaLibraryPlugin.class);
pluginManager.apply(SpringJavaPlugin.class);
pluginManager.apply(SpringMavenPlugin.class);
pluginManager.apply(SpringCheckClasspathForProhibitedDependenciesPlugin.class);
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.gradle.convention;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.plugins.BasePlugin;
import org.gradle.api.plugins.PluginManager;
import org.springframework.gradle.classpath.SpringCheckProhibitedDependenciesLifecyclePlugin;
import org.springframework.gradle.maven.SpringNexusPlugin;
import org.springframework.gradle.nohttp.SpringNoHttpPlugin;
import org.springframework.gradle.sonarqube.SpringSonarQubePlugin;
/**
* @author Steve Riesenberg
*/
public class SpringRootProjectPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
// Apply default plugins
PluginManager pluginManager = project.getPluginManager();
pluginManager.apply(BasePlugin.class);
pluginManager.apply(SpringNoHttpPlugin.class);
pluginManager.apply(SpringNexusPlugin.class);
pluginManager.apply(SpringCheckProhibitedDependenciesLifecyclePlugin.class);
pluginManager.apply(SpringSonarQubePlugin.class);
// Apply default repositories
project.getRepositories().mavenCentral();
}
}

View File

@@ -1,16 +0,0 @@
package lock;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
/**
* @author Rob Winch
*/
public class GlobalLockPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getTasks().register("writeLocks", GlobalLockTask.class, writeAll -> {
writeAll.setDescription("Writes the locks for all projects");
});
}
}

View File

@@ -1,40 +0,0 @@
package lock;
import org.gradle.api.Action;
import org.gradle.api.DefaultTask;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.tasks.TaskAction;
import java.util.function.Consumer;
/**
* @author Rob Winch
*/
public class GlobalLockTask extends DefaultTask {
@TaskAction
public void lock() {
Project taskProject = getProject();
if (!taskProject.getGradle().getStartParameter().isWriteDependencyLocks()) {
throw new IllegalStateException("You just specify --write-locks argument");
}
writeLocksFor(taskProject);
taskProject.getSubprojects().forEach(new Consumer<Project>() {
@Override
public void accept(Project subproject) {
writeLocksFor(subproject);
}
});
}
private void writeLocksFor(Project project) {
project.getConfigurations().configureEach(new Action<Configuration>() {
@Override
public void execute(Configuration configuration) {
if (configuration.isCanBeResolved()) {
configuration.resolve();
}
}
});
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle;
import org.gradle.api.Project;
/**
* @author Steve Riesenberg
*/
public class ProjectUtils {
private ProjectUtils() {
}
public static String getProjectName(Project project) {
String projectName = project.getRootProject().getName();
if (projectName.endsWith("-build")) {
projectName = projectName.substring(0, projectName.length() - "-build".length());
}
return projectName;
}
public static boolean isSnapshot(Project project) {
String projectVersion = projectVersion(project);
return projectVersion.matches("^.*([.-]BUILD)?-SNAPSHOT$");
}
public static boolean isMilestone(Project project) {
String projectVersion = projectVersion(project);
return projectVersion.matches("^.*[.-]M\\d+$") || projectVersion.matches("^.*[.-]RC\\d+$");
}
public static boolean isRelease(Project project) {
return !(isSnapshot(project) || isMilestone(project));
}
private static String projectVersion(Project project) {
return String.valueOf(project.getVersion());
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle;
import java.util.HashMap;
import java.util.Map;
import io.spring.javaformat.gradle.SpringJavaFormatPlugin;
import org.gradle.api.JavaVersion;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.plugins.GroovyPlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.plugins.PluginManager;
import org.gradle.api.tasks.compile.CompileOptions;
import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.jvm.tasks.Jar;
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper;
import org.springframework.gradle.checkstyle.SpringJavaCheckstylePlugin;
import org.springframework.gradle.docs.SpringJavadocOptionsPlugin;
import org.springframework.gradle.jacoco.SpringJacocoPlugin;
import org.springframework.gradle.management.SpringManagementConfigurationPlugin;
import org.springframework.gradle.maven.SpringRepositoryPlugin;
import org.springframework.gradle.propdeps.SpringPropDepsEclipsePlugin;
import org.springframework.gradle.propdeps.SpringPropDepsIdeaPlugin;
import org.springframework.gradle.properties.SpringCopyPropertiesPlugin;
/**
* @author Steve Riesenberg
*/
public class SpringJavaPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
// Apply default plugins
PluginManager pluginManager = project.getPluginManager();
pluginManager.apply(JavaPlugin.class);
pluginManager.apply(SpringManagementConfigurationPlugin.class);
if (project.file("src/main/groovy").exists()
|| project.file("src/test/groovy").exists()
|| project.file("src/integration-test/groovy").exists()) {
pluginManager.apply(GroovyPlugin.class);
}
if (project.file("src/main/kotlin").exists()
|| project.file("src/test/kotlin").exists()
|| project.file("src/integration-test/kotlin").exists()
|| project.getBuildFile().getName().endsWith(".kts")) {
pluginManager.apply(KotlinPluginWrapper.class);
}
pluginManager.apply(SpringRepositoryPlugin.class);
pluginManager.apply(SpringPropDepsEclipsePlugin.class);
pluginManager.apply(SpringPropDepsIdeaPlugin.class);
pluginManager.apply(SpringJavadocOptionsPlugin.class);
pluginManager.apply(SpringJavaFormatPlugin.class);
pluginManager.apply(SpringJavaCheckstylePlugin.class);
pluginManager.apply(SpringCopyPropertiesPlugin.class);
pluginManager.apply(SpringJacocoPlugin.class);
// Apply Java source compatibility version
JavaPluginExtension java = project.getExtensions().getByType(JavaPluginExtension.class);
java.setTargetCompatibility(JavaVersion.VERSION_1_8);
// Configure Java tasks
project.getTasks().withType(JavaCompile.class, (javaCompile) -> {
CompileOptions options = javaCompile.getOptions();
options.setEncoding("UTF-8");
options.getCompilerArgs().add("-parameters");
if (JavaVersion.current().isJava11Compatible()) {
options.getRelease().set(8);
}
});
project.getTasks().withType(Jar.class, (jar) -> jar.manifest((manifest) -> {
Map<String, String> attributes = new HashMap<>();
attributes.put("Created-By", String.format("%s (%s)", System.getProperty("java.version"), System.getProperty("java.specification.vendor")));
attributes.put("Implementation-Title", project.getName());
attributes.put("Implementation-Version", project.getVersion().toString());
attributes.put("Automatic-Module-Name", project.getName().replace("-", "."));
manifest.attributes(attributes);
}));
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.plugins.PluginManager;
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
import org.springframework.gradle.maven.SpringArtifactoryPlugin;
import org.springframework.gradle.maven.SpringMavenPublishingConventionsPlugin;
import org.springframework.gradle.maven.SpringPublishAllJavaComponentsPlugin;
import org.springframework.gradle.maven.SpringPublishArtifactsPlugin;
import org.springframework.gradle.maven.SpringPublishLocalPlugin;
import org.springframework.gradle.maven.SpringSigningPlugin;
/**
* @author Steve Riesenberg
*/
public class SpringMavenPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
// Apply default plugins
PluginManager pluginManager = project.getPluginManager();
pluginManager.apply(MavenPublishPlugin.class);
pluginManager.apply(SpringSigningPlugin.class);
pluginManager.apply(SpringMavenPublishingConventionsPlugin.class);
pluginManager.apply(SpringPublishAllJavaComponentsPlugin.class);
pluginManager.apply(SpringPublishLocalPlugin.class);
pluginManager.apply(SpringPublishArtifactsPlugin.class);
pluginManager.apply(SpringArtifactoryPlugin.class);
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.checkstyle;
import java.io.File;
import java.util.Objects;
import javax.annotation.Nullable;
import io.spring.javaformat.gradle.tasks.CheckFormat;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.quality.CheckstyleExtension;
import org.gradle.api.plugins.quality.CheckstylePlugin;
/**
* Adds and configures Checkstyle plugin.
*
* @author Vedran Pavic
* @author Steve Riesenberg
*/
public class SpringJavaCheckstylePlugin implements Plugin<Project> {
private static final String CHECKSTYLE_DIR = "etc/checkstyle";
private static final String SPRING_JAVAFORMAT_VERSION_PROPERTY = "springJavaformatVersion";
private static final String DEFAULT_SPRING_JAVAFORMAT_VERSION = "0.0.31";
private static final String NOHTTP_CHECKSTYLE_VERSION_PROPERTY = "nohttpCheckstyleVersion";
private static final String DEFAULT_NOHTTP_CHECKSTYLE_VERSION = "0.0.10";
private static final String CHECKSTYLE_TOOL_VERSION_PROPERTY = "checkstyleToolVersion";
private static final String DEFAULT_CHECKSTYLE_TOOL_VERSION = "8.34";
private static final String SPRING_JAVAFORMAT_EXCLUDE_PACKAGES_PROPERTY = "springJavaformatExcludePackages";
@Override
public void apply(Project project) {
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
File checkstyleDir = project.getRootProject().file(CHECKSTYLE_DIR);
if (checkstyleDir.exists() && checkstyleDir.isDirectory()) {
project.getPluginManager().apply(CheckstylePlugin.class);
// NOTE: See gradle.properties#springJavaformatVersion for actual version number
project.getDependencies().add("checkstyle", "io.spring.javaformat:spring-javaformat-checkstyle:" + getSpringJavaformatVersion(project));
// NOTE: See gradle.properties#nohttpCheckstyleVersion for actual version number
project.getDependencies().add("checkstyle", "io.spring.nohttp:nohttp-checkstyle:" + getNohttpCheckstyleVersion(project));
CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class);
checkstyle.getConfigDirectory().set(checkstyleDir);
// NOTE: See gradle.properties#checkstyleToolVersion for actual version number
checkstyle.setToolVersion(getCheckstyleToolVersion(project));
}
// Configure checkFormat task
project.getTasks().withType(CheckFormat.class, (checkFormat) -> {
// NOTE: See gradle.properties#springJavaformatExcludePackages for excluded packages
String[] springJavaformatExcludePackages = getSpringJavaformatExcludePackages(project);
if (springJavaformatExcludePackages != null) {
checkFormat.exclude(springJavaformatExcludePackages);
}
});
});
}
private static String getSpringJavaformatVersion(Project project) {
String springJavaformatVersion = DEFAULT_SPRING_JAVAFORMAT_VERSION;
if (project.hasProperty(SPRING_JAVAFORMAT_VERSION_PROPERTY)) {
springJavaformatVersion = Objects.requireNonNull(project.findProperty(SPRING_JAVAFORMAT_VERSION_PROPERTY)).toString();
}
return springJavaformatVersion;
}
private static String getNohttpCheckstyleVersion(Project project) {
String nohttpCheckstyleVersion = DEFAULT_NOHTTP_CHECKSTYLE_VERSION;
if (project.hasProperty(NOHTTP_CHECKSTYLE_VERSION_PROPERTY)) {
nohttpCheckstyleVersion = Objects.requireNonNull(project.findProperty(NOHTTP_CHECKSTYLE_VERSION_PROPERTY)).toString();
}
return nohttpCheckstyleVersion;
}
private static String getCheckstyleToolVersion(Project project) {
String checkstyleToolVersion = DEFAULT_CHECKSTYLE_TOOL_VERSION;
if (project.hasProperty(CHECKSTYLE_TOOL_VERSION_PROPERTY)) {
checkstyleToolVersion = Objects.requireNonNull(project.findProperty(CHECKSTYLE_TOOL_VERSION_PROPERTY)).toString();
}
return checkstyleToolVersion;
}
@Nullable
private String[] getSpringJavaformatExcludePackages(Project project) {
String springJavaformatExcludePackages = (String) project.findProperty(SPRING_JAVAFORMAT_EXCLUDE_PACKAGES_PROPERTY);
return (springJavaformatExcludePackages != null) ? springJavaformatExcludePackages.split(" ") : null;
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.classpath;
import java.io.IOException;
import java.util.TreeSet;
import java.util.stream.Collectors;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ModuleVersionIdentifier;
import org.gradle.api.artifacts.ResolvedConfiguration;
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.TaskAction;
/**
* A {@link Task} for checking the classpath for prohibited dependencies.
*
* @author Andy Wilkinson
*/
public class CheckClasspathForProhibitedDependencies extends DefaultTask {
private Configuration classpath;
public CheckClasspathForProhibitedDependencies() {
getOutputs().upToDateWhen((task) -> true);
}
public void setClasspath(Configuration classpath) {
this.classpath = classpath;
}
@Classpath
public FileCollection getClasspath() {
return this.classpath;
}
@TaskAction
public void checkForProhibitedDependencies() throws IOException {
ResolvedConfiguration resolvedConfiguration = this.classpath.getResolvedConfiguration();
TreeSet<String> prohibited = resolvedConfiguration.getResolvedArtifacts().stream()
.map((artifact) -> artifact.getModuleVersion().getId()).filter(this::prohibited)
.map((id) -> id.getGroup() + ":" + id.getName()).collect(Collectors.toCollection(TreeSet::new));
if (!prohibited.isEmpty()) {
StringBuilder message = new StringBuilder(String.format("Found prohibited dependencies in '%s':%n", this.classpath.getName()));
for (String dependency : prohibited) {
message.append(String.format(" %s%n", dependency));
}
throw new GradleException(message.toString());
}
}
private boolean prohibited(ModuleVersionIdentifier id) {
String group = id.getGroup();
if (group.equals("javax.batch")) {
return false;
}
if (group.equals("javax.cache")) {
return false;
}
if (group.equals("javax.money")) {
return false;
}
// TODO: Uncomment the following lines when upgrading to Spring Framework 6
// if (group.startsWith("javax")) {
// return true;
// }
if (group.equals("commons-logging")) {
return true;
}
if (group.equals("org.slf4j") && id.getName().equals("jcl-over-slf4j")) {
return true;
}
if (group.startsWith("org.jboss.spec")) {
return true;
}
if (group.equals("org.apache.geronimo.specs")) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.classpath;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.language.base.plugins.LifecycleBasePlugin;
import org.springframework.util.StringUtils;
/**
* @author Andy Wilkinson
* @author Rob Winch
*/
public class SpringCheckClasspathForProhibitedDependenciesPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPlugins().apply(SpringCheckProhibitedDependenciesLifecyclePlugin.class);
project.getPlugins().withType(JavaBasePlugin.class, (javaBasePlugin) ->
configureProhibitedDependencyChecks(project));
}
private void configureProhibitedDependencyChecks(Project project) {
SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
sourceSets.all((sourceSet) -> createProhibitedDependenciesChecks(project,
sourceSet.getCompileClasspathConfigurationName(), sourceSet.getRuntimeClasspathConfigurationName()));
}
private void createProhibitedDependenciesChecks(Project project, String... configurationNames) {
ConfigurationContainer configurations = project.getConfigurations();
for (String configurationName : configurationNames) {
Configuration configuration = configurations.getByName(configurationName);
createProhibitedDependenciesCheck(configuration, project);
}
}
private void createProhibitedDependenciesCheck(Configuration classpath, Project project) {
String taskName = "check" + StringUtils.capitalize(classpath.getName() + "ForProhibitedDependencies");
TaskProvider<CheckClasspathForProhibitedDependencies> checkClasspathTask = project.getTasks().register(taskName,
CheckClasspathForProhibitedDependencies.class, (checkClasspath) -> {
checkClasspath.setGroup(LifecycleBasePlugin.CHECK_TASK_NAME);
checkClasspath.setDescription("Checks " + classpath.getName() + " for prohibited dependencies");
checkClasspath.setClasspath(classpath);
});
project.getTasks().named(SpringCheckProhibitedDependenciesLifecyclePlugin.CHECK_PROHIBITED_DEPENDENCIES_TASK_NAME, (checkProhibitedTask) -> checkProhibitedTask.dependsOn(checkClasspathTask));
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.classpath;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.tasks.TaskProvider;
/**
* @author Rob Winch
*/
public class SpringCheckProhibitedDependenciesLifecyclePlugin implements Plugin<Project> {
public static final String CHECK_PROHIBITED_DEPENDENCIES_TASK_NAME = "checkForProhibitedDependencies";
@Override
public void apply(Project project) {
TaskProvider<Task> checkProhibitedDependencies = project.getTasks().register(SpringCheckProhibitedDependenciesLifecyclePlugin.CHECK_PROHIBITED_DEPENDENCIES_TASK_NAME, (task) -> {
task.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
task.setDescription("Checks both the compile/runtime classpath of every SourceSet for prohibited dependencies");
});
project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME, (checkTask) -> {
checkTask.dependsOn(checkProhibitedDependencies);
});
}
}

View File

@@ -0,0 +1,143 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.docs;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask;
import org.asciidoctor.gradle.jvm.AsciidoctorJExtension;
import org.asciidoctor.gradle.jvm.AsciidoctorJPlugin;
import org.asciidoctor.gradle.jvm.AsciidoctorTask;
import org.asciidoctor.gradle.jvm.pdf.AsciidoctorJPdfPlugin;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
/**
* Conventions that are applied in the presence of the {@link AsciidoctorJPlugin}. When
* the plugin is applied:
*
* <ul>
* <li>All warnings are made fatal.
* <li>A task is created to resolve and unzip our documentation resources (CSS and
* Javascript).
* <li>For each {@link AsciidoctorTask} (HTML only):
* <ul>
* <li>A configuration named asciidoctorExtensions is used to add the
* <a href="https://github.com/spring-io/spring-asciidoctor-extensions#block-switch">block
* switch</a> extension
* <li>{@code doctype} {@link AsciidoctorTask#options(Map) option} is configured.
* <li>{@link AsciidoctorTask#attributes(Map) Attributes} are configured for syntax
* highlighting, CSS styling, docinfo, etc.
* </ul>
* <li>For each {@link AbstractAsciidoctorTask} (HTML and PDF):
* <ul>
* <li>{@link AsciidoctorTask#attributes(Map) Attributes} are configured to enable
* warnings for references to missing attributes, the year is added as @{code today-year},
* etc
* <li>{@link AbstractAsciidoctorTask#baseDirFollowsSourceDir() baseDirFollowsSourceDir()}
* is enabled.
* </ul>
* </ul>
*
* @author Andy Wilkinson
* @author Rob Winch
* @author Steve Riesenberg
*/
public class SpringAsciidoctorPlugin implements Plugin<Project> {
private static final String ASCIIDOCTORJ_VERSION = "2.4.3";
private static final String EXTENSIONS_CONFIGURATION_NAME = "asciidoctorExtensions";
@Override
public void apply(Project project) {
// Apply asciidoctor plugin
project.getPluginManager().apply(AsciidoctorJPlugin.class);
project.getPluginManager().apply(AsciidoctorJPdfPlugin.class);
// Configure asciidoctor
project.getPlugins().withType(AsciidoctorJPlugin.class, (asciidoctorPlugin) -> {
configureDocumentationDependenciesRepository(project);
makeAllWarningsFatal(project);
upgradeAsciidoctorJVersion(project);
createAsciidoctorExtensionsConfiguration(project);
project.getTasks().withType(AbstractAsciidoctorTask.class, this::configureAsciidoctorExtension);
});
}
private void configureDocumentationDependenciesRepository(Project project) {
project.getRepositories().maven((mavenRepo) -> {
mavenRepo.setUrl(URI.create("https://repo.spring.io/release"));
mavenRepo.mavenContent((mavenContent) -> {
mavenContent.includeGroup("io.spring.asciidoctor");
mavenContent.includeGroup("io.spring.asciidoctor.backends");
mavenContent.includeGroup("io.spring.docresources");
});
});
}
private void makeAllWarningsFatal(Project project) {
project.getExtensions().getByType(AsciidoctorJExtension.class).fatalWarnings(".*");
}
private void upgradeAsciidoctorJVersion(Project project) {
project.getExtensions().getByType(AsciidoctorJExtension.class).setVersion(ASCIIDOCTORJ_VERSION);
}
private void createAsciidoctorExtensionsConfiguration(Project project) {
project.getConfigurations().create(EXTENSIONS_CONFIGURATION_NAME, (configuration) -> {
project.getConfigurations().matching((candidate) -> "management".equals(candidate.getName()))
.all(configuration::extendsFrom);
configuration.getDependencies().add(project.getDependencies()
.create("io.spring.asciidoctor.backends:spring-asciidoctor-backends:0.0.3"));
configuration.getDependencies()
.add(project.getDependencies().create("org.asciidoctor:asciidoctorj-pdf:1.5.3"));
});
}
private void configureAsciidoctorExtension(AbstractAsciidoctorTask asciidoctorTask) {
asciidoctorTask.configurations(EXTENSIONS_CONFIGURATION_NAME);
configureCommonAttributes(asciidoctorTask);
configureOptions(asciidoctorTask);
asciidoctorTask.baseDirFollowsSourceDir();
asciidoctorTask.resources((resourcesSpec) -> {
resourcesSpec.from(asciidoctorTask.getSourceDir(), (resourcesSrcDirSpec) -> {
// Not using intermediateWorkDir.
// See https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/523
resourcesSrcDirSpec.include("images/*.png", "css/**", "js/**", "**/*.java");
});
});
if (asciidoctorTask instanceof AsciidoctorTask) {
boolean pdf = asciidoctorTask.getName().toLowerCase().contains("pdf");
String backend = (!pdf) ? "spring-html" : "spring-pdf";
((AsciidoctorTask) asciidoctorTask).outputOptions((outputOptions) ->
outputOptions.backends(backend));
}
}
private void configureCommonAttributes(AbstractAsciidoctorTask asciidoctorTask) {
Map<String, Object> attributes = new HashMap<>();
attributes.put("attribute-missing", "warn");
attributes.put("revnumber", null);
asciidoctorTask.attributes(attributes);
}
private void configureOptions(AbstractAsciidoctorTask asciidoctorTask) {
asciidoctorTask.options(Collections.singletonMap("doctype", "book"));
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.docs;
import java.io.File;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
import io.spring.gradle.convention.SpringModulePlugin;
import org.gradle.api.Action;
import org.gradle.api.JavaVersion;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.javadoc.Javadoc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Rob Winch
* @author Steve Riesenberg
*/
public class SpringJavadocApiPlugin implements Plugin<Project> {
private final Logger logger = LoggerFactory.getLogger(getClass());
private Set<Pattern> excludes = Collections.singleton(Pattern.compile("test"));
@Override
public void apply(Project project) {
// Create task to generate aggregated docs
Javadoc api = project.getTasks().create("api", Javadoc.class, (javadoc) -> {
javadoc.setGroup("Documentation");
javadoc.setDescription("Generates aggregated Javadoc API documentation.");
});
// Note: The following action cannot be a lambda, for groovy compatibility
api.doLast(new Action<Task>() {
@Override
public void execute(Task task) {
if (JavaVersion.current().isJava8Compatible()) {
project.copy((copy) -> copy.from(api.getDestinationDir())
.into(api.getDestinationDir())
.include("element-list")
.rename("element-list", "package-list"));
}
}
});
Set<Project> subprojects = project.getRootProject().getSubprojects();
for (Project subproject : subprojects) {
addProject(api, subproject);
}
if (subprojects.isEmpty()) {
addProject(api, project);
}
api.setMaxMemory("1024m");
api.setDestinationDir(new File(project.getBuildDir(), "api"));
}
public void setExcludes(String... excludes) {
if (excludes == null) {
this.excludes = Collections.emptySet();
}
this.excludes = new HashSet<>(excludes.length);
for (String exclude : excludes) {
this.excludes.add(Pattern.compile(exclude));
}
}
private void addProject(Javadoc api, Project project) {
for (Pattern exclude : excludes) {
if (exclude.matcher(project.getName()).matches()) {
logger.info("Skipping {} because it is excluded by {}", project, exclude);
return;
}
}
logger.info("Try add sources for {}", project);
project.getPlugins().withType(SpringModulePlugin.class, (plugin) -> {
logger.info("Added sources for {}", project);
JavaPluginExtension java = project.getExtensions().getByType(JavaPluginExtension.class);
SourceSet mainSourceSet = java.getSourceSets().getByName("main");
api.setSource(api.getSource().plus(mainSourceSet.getAllJava()));
project.getTasks().withType(Javadoc.class).all((projectJavadoc) ->
api.setClasspath(api.getClasspath().plus(projectJavadoc.getClasspath())));
});
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.docs;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.tasks.javadoc.Javadoc;
import org.gradle.external.javadoc.StandardJavadocDocletOptions;
/**
* @author Steve Riesenberg
*/
public class SpringJavadocOptionsPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getTasks().withType(Javadoc.class, (javadoc) -> {
StandardJavadocDocletOptions options = (StandardJavadocDocletOptions) javadoc.getOptions();
options.addStringOption("Xdoclint:none", "-quiet");
});
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.jacoco;
import java.util.Objects;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.testing.jacoco.plugins.JacocoPlugin;
import org.gradle.testing.jacoco.plugins.JacocoPluginExtension;
/**
* Adds a version of jacoco to use and makes check depend on jacocoTestReport.
*
* @author Rob Winch
* @author Steve Riesenberg
*/
public class SpringJacocoPlugin implements Plugin<Project> {
private static final String JACOCO_TOOL_VERSION_PROPERTY = "jacocoToolVersion";
private static final String DEFAULT_JACOCO_TOOL_VERSION = "0.8.7";
@Override
public void apply(Project project) {
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
project.getPluginManager().apply(JacocoPlugin.class);
project.getTasks().getByName("check").dependsOn(project.getTasks().getByName("jacocoTestReport"));
JacocoPluginExtension jacoco = project.getExtensions().getByType(JacocoPluginExtension.class);
// NOTE: See gradle.properties#jacocoToolVersion for actual version number
jacoco.setToolVersion(getJacocoToolVersion(project));
});
}
private static String getJacocoToolVersion(Project project) {
String jacocoToolVersion = DEFAULT_JACOCO_TOOL_VERSION;
if (project.hasProperty(JACOCO_TOOL_VERSION_PROPERTY)) {
jacocoToolVersion = Objects.requireNonNull(project.findProperty(JACOCO_TOOL_VERSION_PROPERTY)).toString();
}
return jacocoToolVersion;
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.management;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaTestFixturesPlugin;
import org.gradle.api.plugins.PluginContainer;
import org.gradle.api.publish.PublishingExtension;
import org.gradle.api.publish.VariantVersionMappingStrategy;
import org.gradle.api.publish.maven.MavenPublication;
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
/**
* Creates a Management configuration that is appropriate for adding a platform to that is not exposed externally. If
* the JavaPlugin is applied, the compileClasspath, runtimeClasspath, testCompileClasspath, and testRuntimeClasspath
* will extend from it.
* @author Rob Winch
* @author Steve Riesenberg
*/
public class SpringManagementConfigurationPlugin implements Plugin<Project> {
public static final String MANAGEMENT_CONFIGURATION_NAME = "management";
@Override
public void apply(Project project) {
ConfigurationContainer configurations = project.getConfigurations();
configurations.create(MANAGEMENT_CONFIGURATION_NAME, (management) -> {
management.setVisible(false);
management.setCanBeConsumed(false);
management.setCanBeResolved(false);
PluginContainer plugins = project.getPlugins();
plugins.withType(JavaPlugin.class, (javaPlugin) -> {
configurations.getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME).extendsFrom(management);
configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME).extendsFrom(management);
configurations.getByName(JavaPlugin.TEST_COMPILE_CLASSPATH_CONFIGURATION_NAME).extendsFrom(management);
configurations.getByName(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME).extendsFrom(management);
});
plugins.withType(JavaTestFixturesPlugin.class, (javaTestFixturesPlugin) -> {
configurations.getByName("testFixturesCompileClasspath").extendsFrom(management);
configurations.getByName("testFixturesRuntimeClasspath").extendsFrom(management);
});
plugins.withType(MavenPublishPlugin.class, (mavenPublish) -> {
PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class);
publishing.getPublications().withType(MavenPublication.class, (mavenPublication) ->
mavenPublication.versionMapping((versions) ->
versions.allVariants(VariantVersionMappingStrategy::fromResolutionResult)));
});
});
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.maven;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.jfrog.gradle.plugin.artifactory.ArtifactoryPlugin;
import org.jfrog.gradle.plugin.artifactory.dsl.ArtifactoryPluginConvention;
import org.springframework.gradle.ProjectUtils;
/**
* @author Steve Riesenberg
*/
public class SpringArtifactoryPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
// Apply base plugin
project.getPlugins().apply(ArtifactoryPlugin.class);
// Apply artifactory repository configuration
boolean isSnapshot = ProjectUtils.isSnapshot(project);
boolean isMilestone = ProjectUtils.isMilestone(project);
@SuppressWarnings("deprecation")
ArtifactoryPluginConvention artifactoryExtension = project.getConvention().getPlugin(ArtifactoryPluginConvention.class);
artifactoryExtension.artifactory((artifactory) -> {
artifactory.setContextUrl("https://repo.spring.io");
artifactory.publish((publish) -> {
publish.repository((repository) -> {
String repoKey = isSnapshot ? "libs-snapshot-local" : isMilestone ? "libs-milestone-local" : "libs-release-local";
repository.setRepoKey(repoKey);
if (project.hasProperty("artifactoryUsername")) {
repository.setUsername(project.findProperty("artifactoryUsername"));
repository.setPassword(project.findProperty("artifactoryPassword"));
}
});
publish.defaults((defaults) -> defaults.publications("mavenJava"));
});
});
}
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.gradle.maven;
import java.util.Collections;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.publish.PublishingExtension;
import org.gradle.api.publish.maven.MavenPom;
import org.gradle.api.publish.maven.MavenPomDeveloperSpec;
import org.gradle.api.publish.maven.MavenPomIssueManagement;
import org.gradle.api.publish.maven.MavenPomLicenseSpec;
import org.gradle.api.publish.maven.MavenPomOrganization;
import org.gradle.api.publish.maven.MavenPomScm;
import org.gradle.api.publish.maven.MavenPublication;
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
/**
* @author Steve Riesenberg
*/
public class SpringMavenPublishingConventionsPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPlugins().withType(MavenPublishPlugin.class, (mavenPublish) -> {
PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class);
publishing.getPublications().withType(MavenPublication.class, (mavenPublication) ->
customizePom(mavenPublication.getPom(), project));
SpringMavenPublishingConventionsPlugin.this.customizeJavaPlugin(project);
});
}
private void customizePom(MavenPom pom, Project project) {
pom.getUrl().set("https://spring.io/projects/spring-authorization-server");
pom.getName().set(project.provider(project::getName));
pom.getDescription().set(project.provider(project::getDescription));
pom.organization(this::customizeOrganization);
pom.licenses(this::customizeLicences);
pom.developers(this::customizeDevelopers);
pom.scm(this::customizeScm);
pom.issueManagement(this::customizeIssueManagement);
}
private void customizeOrganization(MavenPomOrganization organization) {
organization.getName().set("VMware, Inc.");
organization.getUrl().set("https://spring.io");
}
private void customizeLicences(MavenPomLicenseSpec licences) {
licences.license((licence) -> {
licence.getName().set("Apache License, Version 2.0");
licence.getUrl().set("https://www.apache.org/licenses/LICENSE-2.0");
});
}
private void customizeDevelopers(MavenPomDeveloperSpec developers) {
developers.developer((developer) -> {
developer.getName().set("Joe Grandja");
developer.getEmail().set("jgrandja@vmware.com");
developer.getOrganization().set("VMware, Inc.");
developer.getOrganizationUrl().set("https://spring.io");
developer.getRoles().set(Collections.singletonList("Project lead"));
});
developers.developer((developer) -> {
developer.getName().set("Steve Riesenberg");
developer.getEmail().set("sriesenberg@vmware.com");
developer.getOrganization().set("VMware, Inc.");
developer.getOrganizationUrl().set("https://spring.io");
});
}
private void customizeScm(MavenPomScm scm) {
scm.getConnection().set("scm:git:git://github.com/spring-projects/spring-authorization-server.git");
scm.getDeveloperConnection().set("scm:git:ssh://git@github.com/spring-projects/spring-authorization-server.git");
scm.getUrl().set("https://github.com/spring-projects/spring-authorization-server");
}
private void customizeIssueManagement(MavenPomIssueManagement issueManagement) {
issueManagement.getSystem().set("GitHub");
issueManagement.getUrl().set("https://github.com/spring-projects/spring-authorization-server/issues");
}
private void customizeJavaPlugin(Project project) {
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
JavaPluginExtension extension = project.getExtensions().getByType(JavaPluginExtension.class);
extension.withJavadocJar();
extension.withSourcesJar();
});
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.maven;
import java.net.URI;
import java.time.Duration;
import io.github.gradlenexus.publishplugin.NexusPublishExtension;
import io.github.gradlenexus.publishplugin.NexusPublishPlugin;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.springframework.gradle.ProjectUtils;
/**
* @author Steve Riesenberg
*/
public class SpringNexusPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
// Apply nexus publish plugin
project.getPlugins().apply(NexusPublishPlugin.class);
// Create ossrh repository
NexusPublishExtension nexusPublishing = project.getExtensions().getByType(NexusPublishExtension.class);
nexusPublishing.getRepositories().create("ossrh", (nexusRepository) -> {
nexusRepository.getNexusUrl().set(URI.create("https://s01.oss.sonatype.org/service/local/"));
nexusRepository.getSnapshotRepositoryUrl().set(URI.create("https://s01.oss.sonatype.org/content/repositories/snapshots/"));
});
// Configure timeouts
nexusPublishing.getConnectTimeout().set(Duration.ofMinutes(3));
nexusPublishing.getClientTimeout().set(Duration.ofMinutes(3));
// Ensure release build automatically closes and releases staging repository
Task finalizeDeployArtifacts = project.task("finalizeDeployArtifacts");
if (ProjectUtils.isRelease(project) && project.hasProperty("ossrhUsername")) {
Task closeAndReleaseOssrhStagingRepository = project.getTasks().findByName("closeAndReleaseOssrhStagingRepository");
finalizeDeployArtifacts.dependsOn(closeAndReleaseOssrhStagingRepository);
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.maven;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPlatformPlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.publish.PublishingExtension;
import org.gradle.api.publish.maven.MavenPublication;
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
/**
* @author Steve Riesenberg
*/
public class SpringPublishAllJavaComponentsPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPlugins().withType(MavenPublishPlugin.class, (mavenPublish) -> {
PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class);
publishing.getPublications().create("mavenJava", MavenPublication.class, (maven) -> {
project.getPlugins().withType(JavaPlugin.class, (plugin) ->
maven.from(project.getComponents().getByName("java")));
project.getPlugins().withType(JavaPlatformPlugin.class, (plugin) ->
maven.from(project.getComponents().getByName("javaPlatform")));
});
});
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.maven;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.springframework.gradle.ProjectUtils;
/**
* @author Steve Riesenberg
*/
public class SpringPublishArtifactsPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getTasks().register("publishArtifacts", (publishArtifacts) -> {
publishArtifacts.setGroup("Publishing");
publishArtifacts.setDescription("Publish the artifacts to either Artifactory or Maven Central based on the version");
if (ProjectUtils.isRelease(project)) {
publishArtifacts.dependsOn("publishToOssrh");
} else {
publishArtifacts.dependsOn("artifactoryPublish");
}
});
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.maven;
import java.io.File;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.publish.PublishingExtension;
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
/**
* @author Steve Riesenberg
*/
public class SpringPublishLocalPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPlugins().withType(MavenPublishPlugin.class, (mavenPublish) -> {
PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class);
publishing.getRepositories().maven((maven) -> {
maven.setName("local");
maven.setUrl(new File(project.getRootProject().getBuildDir(), "publications/repos"));
});
});
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.maven;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.springframework.gradle.ProjectUtils;
/**
* @author Steve Riesenberg
*/
public class SpringRepositoryPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
List<String> forceMavenRepositories = Collections.emptyList();
if (project.hasProperty("forceMavenRepositories")) {
forceMavenRepositories = Arrays.asList(((String) project.findProperty("forceMavenRepositories")).split(","));
}
boolean isImplicitSnapshotRepository = forceMavenRepositories.isEmpty() && ProjectUtils.isSnapshot(project);
boolean isImplicitMilestoneRepository = forceMavenRepositories.isEmpty() && ProjectUtils.isMilestone(project);
boolean isSnapshot = isImplicitSnapshotRepository || forceMavenRepositories.contains("snapshot");
boolean isMilestone = isImplicitMilestoneRepository || forceMavenRepositories.contains("milestone");
if (forceMavenRepositories.contains("local")) {
project.getRepositories().mavenLocal();
}
project.getRepositories().mavenCentral();
if (isSnapshot) {
repository(project, "artifactory-snapshot", "https://repo.spring.io/snapshot/");
}
if (isSnapshot || isMilestone) {
repository(project, "artifactory-milestone", "https://repo.spring.io/milestone/");
}
repository(project, "artifactory-release", "https://repo.spring.io/release/");
}
private void repository(Project project, String name, String url) {
project.getRepositories().maven((repo) -> {
repo.setName(name);
if (project.hasProperty("artifactoryUsername")) {
repo.credentials((credentials) -> {
credentials.setUsername(Objects.requireNonNull(project.findProperty("artifactoryUsername")).toString());
credentials.setPassword(Objects.requireNonNull(project.findProperty("artifactoryPassword")).toString());
});
}
repo.setUrl(url);
});
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.gradle.maven;
import java.util.concurrent.Callable;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.publish.Publication;
import org.gradle.api.publish.PublishingExtension;
import org.gradle.plugins.signing.SigningExtension;
import org.gradle.plugins.signing.SigningPlugin;
/**
* @author Steve Riesenberg
*/
public class SpringSigningPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPluginManager().apply(SigningPlugin.class);
project.getPlugins().withType(SigningPlugin.class, (signingPlugin) -> {
boolean hasSigningKey = project.hasProperty("signing.keyId") || project.hasProperty("signingKey");
if (hasSigningKey) {
sign(project);
}
});
}
private void sign(Project project) {
SigningExtension signing = project.getExtensions().getByType(SigningExtension.class);
signing.setRequired((Callable<Boolean>) () -> project.getGradle().getTaskGraph().hasTask("publishArtifacts"));
String signingKeyId = (String) project.findProperty("signingKeyId");
String signingKey = (String) project.findProperty("signingKey");
String signingPassword = (String) project.findProperty("signingPassword");
if (signingKeyId != null) {
signing.useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword);
} else {
signing.useInMemoryPgpKeys(signingKey, signingPassword);
}
project.getPlugins().withType(SpringPublishAllJavaComponentsPlugin.class, (publishingPlugin) -> {
PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class);
Publication maven = publishing.getPublications().getByName("mavenJava");
signing.sign(maven);
});
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.nohttp;
import java.io.File;
import io.spring.nohttp.gradle.NoHttpExtension;
import io.spring.nohttp.gradle.NoHttpPlugin;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
/**
* @author Steve Riesenberg
*/
public class SpringNoHttpPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
// Apply nohttp plugin
project.getPluginManager().apply(NoHttpPlugin.class);
// Configure nohttp
NoHttpExtension nohttp = project.getExtensions().getByType(NoHttpExtension.class);
File allowlistFile = project.getRootProject().file("etc/nohttp/allowlist.lines");
nohttp.setAllowlistFile(allowlistFile);
nohttp.getSource().exclude("buildSrc/build/**");
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.propdeps;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.plugins.PluginManager;
import org.gradle.plugins.ide.eclipse.EclipsePlugin;
import org.gradle.plugins.ide.eclipse.EclipseWtpPlugin;
import org.gradle.plugins.ide.eclipse.model.EclipseModel;
/**
* Plugin to allow optional and provided dependency configurations to work with the
* standard gradle 'eclipse' plugin
*
* @author Phillip Webb
* @author Steve Riesenberg
*/
public class SpringPropDepsEclipsePlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
PluginManager pluginManager = project.getPluginManager();
pluginManager.apply(SpringPropDepsPlugin.class);
pluginManager.apply(EclipsePlugin.class);
pluginManager.apply(EclipseWtpPlugin.class);
EclipseModel eclipseModel = project.getExtensions().getByType(EclipseModel.class);
eclipseModel.classpath((classpath) -> {
classpath.getPlusConfigurations().add(project.getConfigurations().getByName("provided"));
classpath.getPlusConfigurations().add(project.getConfigurations().getByName("optional"));
});
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.propdeps;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.plugins.PluginManager;
import org.gradle.plugins.ide.idea.IdeaPlugin;
import org.gradle.plugins.ide.idea.model.IdeaModel;
/**
* Plugin to allow optional and provided dependency configurations to work with the
* standard gradle 'idea' plugin
*
* @author Phillip Webb
* @author Brian Clozel
* @author Steve Riesenberg
* @link https://youtrack.jetbrains.com/issue/IDEA-107046
* @link https://youtrack.jetbrains.com/issue/IDEA-117668
*/
public class SpringPropDepsIdeaPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
PluginManager pluginManager = project.getPluginManager();
pluginManager.apply(SpringPropDepsPlugin.class);
pluginManager.apply(IdeaPlugin.class);
IdeaModel ideaModel = project.getExtensions().getByType(IdeaModel.class);
ideaModel.module((idea) -> {
// IDEA internally deals with 4 scopes : COMPILE, TEST, PROVIDED, RUNTIME
// but only PROVIDED seems to be picked up
idea.getScopes().get("PROVIDED").get("plus").add(project.getConfigurations().getByName("provided"));
idea.getScopes().get("PROVIDED").get("plus").add(project.getConfigurations().getByName("optional"));
});
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.propdeps;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.plugins.JavaLibraryPlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.javadoc.Javadoc;
import org.springframework.gradle.management.SpringManagementConfigurationPlugin;
/**
* Plugin to allow 'optional' and 'provided' dependency configurations
*
* As stated in the maven documentation, provided scope "is only available on the compilation and test classpath,
* and is not transitive".
*
* This plugin creates two new configurations, and each one:
* <ul>
* <li>is a parent of the compile configuration</li>
* <li>is not visible, not transitive</li>
* <li>all dependencies are excluded from the default configuration</li>
* </ul>
*
* @author Phillip Webb
* @author Brian Clozel
* @author Rob Winch
* @author Steve Riesenberg
*
* @see <a href="https://www.gradle.org/docs/current/userguide/java_plugin.html#N121CF">Maven documentation</a>
* @see <a href="https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Dependency_Scope">Gradle configurations</a>
* @see SpringPropDepsEclipsePlugin
* @see SpringPropDepsIdeaPlugin
*/
public class SpringPropDepsPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
Configuration provided = addConfiguration(project, "provided");
Configuration optional = addConfiguration(project, "optional");
Javadoc javadoc = (Javadoc) project.getTasks().getByName(JavaPlugin.JAVADOC_TASK_NAME);
javadoc.setClasspath(javadoc.getClasspath().plus(provided).plus(optional));
});
}
private Configuration addConfiguration(Project project, String name) {
Configuration configuration = project.getConfigurations().create(name);
configuration.extendsFrom(project.getConfigurations().getByName("implementation"));
project.getPlugins().withType(JavaLibraryPlugin.class, (javaLibraryPlugin) ->
configuration.extendsFrom(project.getConfigurations().getByName("api")));
project.getPlugins().withType(SpringManagementConfigurationPlugin.class, (springManagementConfigurationPlugin) ->
configuration.extendsFrom(project.getConfigurations().getByName("management")));
JavaPluginExtension java = project.getExtensions().getByType(JavaPluginExtension.class);
java.getSourceSets().all((sourceSet) -> {
sourceSet.setCompileClasspath(sourceSet.getCompileClasspath().plus(configuration));
sourceSet.setRuntimeClasspath(sourceSet.getRuntimeClasspath().plus(configuration));
});
return configuration;
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.properties;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
/**
* @author Steve Riesenberg
*/
public class SpringCopyPropertiesPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
copyPropertyFromRootProjectTo("group", project);
copyPropertyFromRootProjectTo("version", project);
copyPropertyFromRootProjectTo("description", project);
}
private void copyPropertyFromRootProjectTo(String propertyName, Project project) {
Project rootProject = project.getRootProject();
Object property = rootProject.findProperty(propertyName);
if (property != null) {
project.setProperty(propertyName, property);
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.gradle.sonarqube;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.sonarqube.gradle.SonarQubeExtension;
import org.sonarqube.gradle.SonarQubePlugin;
import org.springframework.gradle.ProjectUtils;
/**
* @author Steve Riesenberg
*/
public class SpringSonarQubePlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
// Apply sonarqube plugin
project.getPluginManager().apply(SonarQubePlugin.class);
// Configure sonarqube
SonarQubeExtension sonarqube = project.getExtensions().getByType(SonarQubeExtension.class);
sonarqube.properties((properties) -> {
String projectName = ProjectUtils.getProjectName(project);
properties.property("sonar.java.coveragePlugin", "jacoco");
properties.property("sonar.projectName", projectName);
properties.property("sonar.jacoco.reportPath", project.getBuildDir().getName() + "/jacoco.exec");
properties.property("sonar.links.homepage", "https://spring.io/" + projectName);
properties.property("sonar.links.ci", "https://jenkins.spring.io/job/" + projectName + "/");
properties.property("sonar.links.issue", "https://github.com/spring-projects/" + projectName + "/issues");
properties.property("sonar.links.scm", "https://github.com/spring-projects/" + projectName);
properties.property("sonar.links.scm_dev", "https://github.com/spring-projects/" + projectName + ".git");
});
}
}

View File

@@ -0,0 +1 @@
implementation-class=io.spring.gradle.convention.SpringDocsPlugin

View File

@@ -0,0 +1 @@
implementation-class=io.spring.gradle.convention.SpringRootProjectPlugin

View File

@@ -0,0 +1 @@
implementation-class=io.spring.gradle.convention.SpringModulePlugin

View File

@@ -0,0 +1,2 @@
# Referencing this plugin by ID allows java code to depend on groovy compilation
implementation-class=org.springframework.gradle.docs.SpringDeployDocsPlugin

View File

@@ -0,0 +1,24 @@
plugins {
id "java-platform"
}
javaPlatform {
allowDependencies()
}
dependencies {
api platform("org.springframework:spring-framework-bom:$springFrameworkVersion")
api platform("org.springframework.security:spring-security-bom:$springSecurityVersion")
api platform("com.fasterxml.jackson:jackson-bom:2.13.3")
constraints {
api "com.nimbusds:nimbus-jose-jwt:9.22"
api "javax.servlet:javax.servlet-api:4.0.1"
api "junit:junit:4.13.2"
api "org.assertj:assertj-core:3.22.0"
api "org.mockito:mockito-core:4.5.1"
api "com.squareup.okhttp3:mockwebserver:4.9.3"
api "com.squareup.okhttp3:okhttp:4.9.3"
api "com.jayway.jsonpath:json-path:2.7.0"
api "org.hsqldb:hsqldb:2.5.2"
}
}

View File

@@ -1,37 +0,0 @@
apply plugin: 'io.spring.convention.docs'
apply plugin: 'io.spring.convention.springdependencymangement'
apply plugin: 'io.spring.convention.dependency-set'
apply plugin: 'io.spring.convention.repository'
apply plugin: 'java'
asciidoctor {
attributes([stylesheet: 'css/style.css'])
resources {
from(sourceDir) {
include "css/**"
}
}
}
asciidoctorj {
def ghTag = snapshotBuild ? 'main' : project.version
def ghUrl = "https://github.com/spring-projects-experimental/spring-authorization-server/tree/$ghTag"
attributes 'spring-authorization-server-version' : project.version,
'spring-boot-version' : springBootVersion,
revnumber : project.version,
'gh-url': ghUrl,
'gh-samples-url': "$ghUrl/samples"
attributeProvider resolvedVersions(project.configurations.testCompile)
}
def resolvedVersions(Configuration configuration) {
return {
configuration.resolvedConfiguration
.resolvedArtifacts
.collectEntries { [(it.name + "-version"): it.moduleVersion.id.version] }
}
}
repositories {
maven { url "https://repo.spring.io/release" }
}

View File

@@ -1,5 +0,0 @@
@import 'spring.css';
a code {
color: #097dff;
}

View File

@@ -1,15 +0,0 @@
= Spring Authorization Server Reference
Joe Grandja
:include-dir: _includes
:security-api-url: https://docs.spring.io/spring-authorization-server/site/docs/current/api/
:source-indent: 0
:tabsize: 4
:toc: left
== Preface
#TODO:# Document preface
== Introduction
#TODO:# Document introduction

View File

@@ -0,0 +1,25 @@
plugins {
id "io.spring.convention.docs"
}
asciidoctor {
attributes([
"spring-authorization-server-version": project.version,
"spring-security-reference-base-url": "https://docs.spring.io/spring-security/reference",
"spring-security-api-base-url": "https://docs.spring.io/spring-security/site/docs/current/api",
"examples-dir": "examples",
"docs-java": "$sourceDir/examples/src/main/java",
"chomp": "default headers packages",
"toc": "left",
"toclevels": "4"
])
}
docsZip {
from (api) {
into "api"
}
from(asciidoctor) {
into "reference/html"
}
}

View File

@@ -0,0 +1,232 @@
[[configuration-model]]
= Configuration Model
[[default-configuration]]
== Default configuration
`OAuth2AuthorizationServerConfiguration` is a `@Configuration` that provides the minimal default configuration for an OAuth2 authorization server.
`OAuth2AuthorizationServerConfiguration` uses <<customizing-the-configuration, `OAuth2AuthorizationServerConfigurer`>> to apply the default configuration and registers a `SecurityFilterChain` `@Bean` composed of all the infrastructure components supporting an OAuth2 authorization server.
[TIP]
`OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(HttpSecurity)` is a convenience (`static`) utility method that applies the default OAuth2 security configuration to `HttpSecurity`.
The OAuth2 authorization server `SecurityFilterChain` `@Bean` is configured with the following default protocol endpoints:
* xref:protocol-endpoints.adoc#oauth2-authorization-endpoint[OAuth2 Authorization endpoint]
* xref:protocol-endpoints.adoc#oauth2-token-endpoint[OAuth2 Token endpoint]
* xref:protocol-endpoints.adoc#oauth2-token-introspection-endpoint[OAuth2 Token Introspection endpoint]
* xref:protocol-endpoints.adoc#oauth2-token-revocation-endpoint[OAuth2 Token Revocation endpoint]
* xref:protocol-endpoints.adoc#oauth2-authorization-server-metadata-endpoint[OAuth2 Authorization Server Metadata endpoint]
* xref:protocol-endpoints.adoc#jwk-set-endpoint[JWK Set endpoint]
* xref:protocol-endpoints.adoc#oidc-provider-configuration-endpoint[OpenID Connect 1.0 Provider Configuration endpoint]
* xref:protocol-endpoints.adoc#oidc-user-info-endpoint[OpenID Connect 1.0 UserInfo endpoint]
[NOTE]
The JWK Set endpoint is configured *only* if a `JWKSource<SecurityContext>` `@Bean` is registered.
[NOTE]
The xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration endpoint] is disabled by default because many deployments do not require dynamic client registration.
The following example shows how to use `OAuth2AuthorizationServerConfiguration` to apply the minimal default configuration:
[source,java]
----
@Configuration
@Import(OAuth2AuthorizationServerConfiguration.class)
public class AuthorizationServerConfig {
@Bean
public RegisteredClientRepository registeredClientRepository() {
List<RegisteredClient> registrations = ...
return new InMemoryRegisteredClientRepository(registrations);
}
@Bean
public JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = ...
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
}
----
[IMPORTANT]
The https://datatracker.ietf.org/doc/html/rfc6749#section-4.1[authorization_code grant] requires the resource owner to be authenticated. Therefore, a user authentication mechanism *must* be configured in addition to the default OAuth2 security configuration.
[TIP]
`OAuth2AuthorizationServerConfiguration.jwtDecoder(JWKSource<SecurityContext>)` is a convenience (`static`) utility method that can be used to register a `JwtDecoder` `@Bean`, which is *REQUIRED* for the xref:protocol-endpoints.adoc#oidc-user-info-endpoint[OpenID Connect 1.0 UserInfo endpoint] and the xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration endpoint].
The following example shows how to register a `JwtDecoder` `@Bean`:
[source,java]
----
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
----
The main intent of `OAuth2AuthorizationServerConfiguration` is to provide a convenient method to apply the minimal default configuration for an OAuth2 authorization server. However, in most cases, customizing the configuration will be required.
[[customizing-the-configuration]]
== Customizing the configuration
`OAuth2AuthorizationServerConfigurer` provides the ability to fully customize the security configuration for an OAuth2 authorization server.
It lets you specify the core components to use - for example, xref:core-model-components.adoc#registered-client-repository[`RegisteredClientRepository`], xref:core-model-components.adoc#oauth2-authorization-service[`OAuth2AuthorizationService`], xref:core-model-components.adoc#oauth2-token-generator[`OAuth2TokenGenerator`], and others.
Furthermore, it lets you customize the request processing logic for the protocol endpoints for example, xref:protocol-endpoints.adoc#oauth2-authorization-endpoint[authorization endpoint], xref:protocol-endpoints.adoc#oauth2-token-endpoint[token endpoint], xref:protocol-endpoints.adoc#oauth2-token-introspection-endpoint[token introspection endpoint], and others.
`OAuth2AuthorizationServerConfigurer` provides the following configuration options:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.registeredClientRepository(registeredClientRepository) <1>
.authorizationService(authorizationService) <2>
.authorizationConsentService(authorizationConsentService) <3>
.providerSettings(providerSettings) <4>
.tokenGenerator(tokenGenerator) <5>
.clientAuthentication(clientAuthentication -> { }) <6>
.authorizationEndpoint(authorizationEndpoint -> { }) <7>
.tokenEndpoint(tokenEndpoint -> { }) <8>
.tokenIntrospectionEndpoint(tokenIntrospectionEndpoint -> { }) <9>
.tokenRevocationEndpoint(tokenRevocationEndpoint -> { }) <10>
.oidc(oidc -> oidc
.userInfoEndpoint(userInfoEndpoint -> { }) <11>
.clientRegistrationEndpoint(clientRegistrationEndpoint -> { }) <12>
);
return http.build();
}
----
<1> `registeredClientRepository()`: The xref:core-model-components.adoc#registered-client-repository[`RegisteredClientRepository`] (*REQUIRED*) for managing new and existing clients.
<2> `authorizationService()`: The xref:core-model-components.adoc#oauth2-authorization-service[`OAuth2AuthorizationService`] for managing new and existing authorizations.
<3> `authorizationConsentService()`: The xref:core-model-components.adoc#oauth2-authorization-consent-service[`OAuth2AuthorizationConsentService`] for managing new and existing authorization consents.
<4> `providerSettings()`: The <<configuring-provider-settings, `ProviderSettings`>> (*REQUIRED*) for customizing configuration settings for the OAuth2 authorization server.
<5> `tokenGenerator()`: The xref:core-model-components.adoc#oauth2-token-generator[`OAuth2TokenGenerator`] for generating tokens supported by the OAuth2 authorization server.
<6> `clientAuthentication()`: The configurer for <<configuring-client-authentication, OAuth2 Client Authentication>>.
<7> `authorizationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-authorization-endpoint[OAuth2 Authorization endpoint].
<8> `tokenEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-token-endpoint[OAuth2 Token endpoint].
<9> `tokenIntrospectionEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-token-introspection-endpoint[OAuth2 Token Introspection endpoint].
<10> `tokenRevocationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-token-revocation-endpoint[OAuth2 Token Revocation endpoint].
<11> `userInfoEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-user-info-endpoint[OpenID Connect 1.0 UserInfo endpoint].
<12> `clientRegistrationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration endpoint].
[[configuring-provider-settings]]
== Configuring Provider Settings
`ProviderSettings` contains the configuration settings for the OAuth2 authorization server (provider).
It specifies the `URI` for the protocol endpoints as well as the https://datatracker.ietf.org/doc/html/rfc8414#section-2[issuer identifier].
The default `URI` for the protocol endpoints are as follows:
[source,java]
----
public final class ProviderSettings extends AbstractSettings {
...
public static Builder builder() {
return new Builder()
.authorizationEndpoint("/oauth2/authorize")
.tokenEndpoint("/oauth2/token")
.tokenIntrospectionEndpoint("/oauth2/introspect")
.tokenRevocationEndpoint("/oauth2/revoke")
.jwkSetEndpoint("/oauth2/jwks")
.oidcUserInfoEndpoint("/userinfo")
.oidcClientRegistrationEndpoint("/connect/register");
}
...
}
----
[NOTE]
`ProviderSettings` is a *REQUIRED* component.
[TIP]
<<default-configuration, `@Import(OAuth2AuthorizationServerConfiguration.class)`>> automatically registers a `ProviderSettings` `@Bean`, if not already provided.
The following example shows how to customize the configuration settings and register a `ProviderSettings` `@Bean`:
[source,java]
----
@Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder()
.issuer("https://example.com")
.authorizationEndpoint("/oauth2/v1/authorize")
.tokenEndpoint("/oauth2/v1/token")
.tokenIntrospectionEndpoint("/oauth2/v1/introspect")
.tokenRevocationEndpoint("/oauth2/v1/revoke")
.jwkSetEndpoint("/oauth2/v1/jwks")
.oidcUserInfoEndpoint("/connect/v1/userinfo")
.oidcClientRegistrationEndpoint("/connect/v1/register")
.build();
}
----
The `ProviderContext` is a context object that holds information about the provider.
It provides access to the `ProviderSettings` and the "`current`" issuer identifier.
[NOTE]
If the issuer identifier is not configured in `ProviderSettings.builder().issuer(String)`, it is resolved from the current request.
[NOTE]
The `ProviderContext` is accessible through the `ProviderContextHolder`, which associates it with the current request thread by using a `ThreadLocal`.
[NOTE]
The `ProviderContextFilter` associates the `ProviderContext` with the `ProviderContextHolder`.
[[configuring-client-authentication]]
== Configuring Client Authentication
`OAuth2ClientAuthenticationConfigurer` provides the ability to customize https://datatracker.ietf.org/doc/html/rfc6749#section-2.3[OAuth2 client authentication].
It defines extension points that let you customize the pre-processing, main processing, and post-processing logic for client authentication requests.
`OAuth2ClientAuthenticationConfigurer` provides the following configuration options:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.clientAuthentication(clientAuthentication ->
clientAuthentication
.authenticationConverter(authenticationConverter) <1>
.authenticationProvider(authenticationProvider) <2>
.authenticationSuccessHandler(authenticationSuccessHandler) <3>
.errorResponseHandler(errorResponseHandler) <4>
);
return http.build();
}
----
<1> `authenticationConverter()`: The `AuthenticationConverter` (_pre-processor_) used when attempting to extract client credentials from `HttpServletRequest` to an instance of `OAuth2ClientAuthenticationToken`.
<2> `authenticationProvider()`: The `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2ClientAuthenticationToken`. (One or more may be added to replace the defaults.)
<3> `authenticationSuccessHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling a successful client authentication and associating the `OAuth2ClientAuthenticationToken` to the `SecurityContext`.
<4> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling a failed client authentication and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-5.2[`OAuth2Error` response].
`OAuth2ClientAuthenticationConfigurer` configures the `OAuth2ClientAuthenticationFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OAuth2ClientAuthenticationFilter` is the `Filter` that processes client authentication requests.
By default, client authentication is required for the xref:protocol-endpoints.adoc#oauth2-token-endpoint[OAuth2 Token endpoint], the xref:protocol-endpoints.adoc#oauth2-token-introspection-endpoint[OAuth2 Token Introspection endpoint], and the xref:protocol-endpoints.adoc#oauth2-token-revocation-endpoint[OAuth2 Token Revocation endpoint].
The supported client authentication methods are `client_secret_basic`, `client_secret_post`, `private_key_jwt`, `client_secret_jwt`, and `none` (public clients).
`OAuth2ClientAuthenticationFilter` is configured with the following defaults:
* `*AuthenticationConverter*` -- A `DelegatingAuthenticationConverter` composed of `JwtClientAssertionAuthenticationConverter`, `ClientSecretBasicAuthenticationConverter`, `ClientSecretPostAuthenticationConverter`, and `PublicClientAuthenticationConverter`.
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `JwtClientAssertionAuthenticationProvider`, `ClientSecretAuthenticationProvider`, and `PublicClientAuthenticationProvider`.
* `*AuthenticationSuccessHandler*` -- An internal implementation that associates the "`authenticated`" `OAuth2ClientAuthenticationToken` (current `Authentication`) to the `SecurityContext`.
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthenticationException` to return the OAuth2 error response.

View File

@@ -0,0 +1,491 @@
[[core-model-components]]
= Core Model / Components
[[registered-client]]
== RegisteredClient
A `RegisteredClient` is a representation of a client that is https://datatracker.ietf.org/doc/html/rfc6749#section-2[registered] with the authorization server.
A client must be registered with the authorization server before it can initiate an authorization grant flow, such as `authorization_code` or `client_credentials`.
During client registration, the client is assigned a unique https://datatracker.ietf.org/doc/html/rfc6749#section-2.2[client identifier], (optionally) a client secret (depending on https://datatracker.ietf.org/doc/html/rfc6749#section-2.1[client type]), and metadata associated with its unique client identifier.
The client's metadata can range from human-facing display strings (such as client name) to items specific to a protocol flow (such as the list of valid redirect URIs).
[TIP]
The corresponding client registration model in Spring Security's OAuth2 Client support is {spring-security-reference-base-url}/servlet/oauth2/client/core.html#oauth2Client-client-registration[ClientRegistration].
The primary purpose of a client is to request access to protected resources.
The client first requests an access token by authenticating with the authorization server and presenting the authorization grant.
The authorization server authenticates the client and authorization grant, and, if they are valid, issues an access token.
The client can now request the protected resource from the resource server by presenting the access token.
The following example shows how to configure a `RegisteredClient` that is allowed to perform the https://datatracker.ietf.org/doc/html/rfc6749#section-4.1[authorization_code grant] flow to request an access token:
[source,java]
----
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("client-a")
.clientSecret("{noop}secret") <1>
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("http://127.0.0.1:8080/authorized")
.scope("scope-a")
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();
----
<1> `\{noop\}` represents the `PasswordEncoder` id for Spring Security's {spring-security-reference-base-url}/features/authentication/password-storage.html#authentication-password-storage-dpe[NoOpPasswordEncoder].
The corresponding configuration in Spring Security's {spring-security-reference-base-url}/servlet/oauth2/client/index.html[OAuth2 Client support] is:
[source,yaml]
----
spring:
security:
oauth2:
client:
registration:
client-a:
provider: spring
client-id: client-a
client-secret: secret
authorization-grant-type: authorization_code
redirect-uri: "http://127.0.0.1:8080/authorized"
scope: scope-a
provider:
spring:
issuer-uri: http://localhost:9000
----
A `RegisteredClient` has metadata (attributes) associated with its unique Client Identifier and is defined as follows:
[source,java]
----
public class RegisteredClient implements Serializable {
private String id; <1>
private String clientId; <2>
private Instant clientIdIssuedAt; <3>
private String clientSecret; <4>
private Instant clientSecretExpiresAt; <5>
private String clientName; <6>
private Set<ClientAuthenticationMethod> clientAuthenticationMethods; <7>
private Set<AuthorizationGrantType> authorizationGrantTypes; <8>
private Set<String> redirectUris; <9>
private Set<String> scopes; <10>
private ClientSettings clientSettings; <11>
private TokenSettings tokenSettings; <12>
...
}
----
<1> `id`: The ID that uniquely identifies the `RegisteredClient`.
<2> `clientId`: The client identifier.
<3> `clientIdIssuedAt`: The time at which the client identifier was issued.
<4> `clientSecret`: The client's secret. The value should be encoded using Spring Security's {spring-security-reference-base-url}/features/authentication/password-storage.html#authentication-password-storage-dpe[PasswordEncoder].
<5> `clientSecretExpiresAt`: The time at which the client secret expires.
<6> `clientName`: A descriptive name used for the client. The name may be used in certain scenarios, such as when displaying the client name in the consent page.
<7> `clientAuthenticationMethods`: The authentication method(s) that the client may use. The supported values are `client_secret_basic`, `client_secret_post`, https://datatracker.ietf.org/doc/html/rfc7523[`private_key_jwt`], `client_secret_jwt`, and `none` https://datatracker.ietf.org/doc/html/rfc7636[(public clients)].
<8> `authorizationGrantTypes`: The https://datatracker.ietf.org/doc/html/rfc6749#section-1.3[authorization grant type(s)] that the client can use. The supported values are `authorization_code`, `client_credentials`, and `refresh_token`.
<9> `redirectUris`: The registered https://datatracker.ietf.org/doc/html/rfc6749#section-3.1.2[redirect URI(s)] that the client may use in redirect-based flows for example, `authorization_code` grant.
<10> `scopes`: The scope(s) that the client is allowed to request.
<11> `clientSettings`: The custom settings for the client for example, require https://datatracker.ietf.org/doc/html/rfc7636[PKCE], require authorization consent, and others.
<12> `tokenSettings`: The custom settings for the OAuth2 tokens issued to the client for example, access/refresh token time-to-live, reuse refresh tokens, and others.
[[registered-client-repository]]
== RegisteredClientRepository
The `RegisteredClientRepository` is the central component where new clients can be registered and existing clients can be queried.
It is used by other components when following a specific protocol flow, such as client authentication, authorization grant processing, token introspection, dynamic client registration, and others.
The provided implementations of `RegisteredClientRepository` are `InMemoryRegisteredClientRepository` and `JdbcRegisteredClientRepository`.
The `InMemoryRegisteredClientRepository` implementation stores `RegisteredClient` instances in-memory and is recommended *ONLY* to be used during development and testing.
`JdbcRegisteredClientRepository` is a JDBC implementation that persists `RegisteredClient` instances by using `JdbcOperations`.
[NOTE]
The `RegisteredClientRepository` is a *REQUIRED* component.
The following example shows how to register a `RegisteredClientRepository` `@Bean`:
[source,java]
----
@Bean
public RegisteredClientRepository registeredClientRepository() {
List<RegisteredClient> registrations = ...
return new InMemoryRegisteredClientRepository(registrations);
}
----
Alternatively, you can configure the `RegisteredClientRepository` through the xref:configuration-model.adoc#customizing-the-configuration[`OAuth2AuthorizationServerConfigurer`]:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.registeredClientRepository(registeredClientRepository);
...
return http.build();
}
----
[NOTE]
The `OAuth2AuthorizationServerConfigurer` is useful when applying multiple configuration options simultaneously.
[[oauth2-authorization]]
== OAuth2Authorization
An `OAuth2Authorization` is a representation of an OAuth2 authorization, which holds state related to the authorization granted to a <<registered-client, client>>, by the resource owner or itself in the case of the `client_credentials` authorization grant type.
[TIP]
The corresponding authorization model in Spring Security's OAuth2 Client support is {spring-security-reference-base-url}/servlet/oauth2/client/core.html#oauth2Client-authorized-client[OAuth2AuthorizedClient].
After the successful completion of an authorization grant flow, an `OAuth2Authorization` is created and associates an {spring-security-api-base-url}/org/springframework/security/oauth2/core/OAuth2AccessToken.html[`OAuth2AccessToken`], an (optional) {spring-security-api-base-url}/org/springframework/security/oauth2/core/OAuth2RefreshToken.html[`OAuth2RefreshToken`], and additional state specific to the executed authorization grant type.
The {spring-security-api-base-url}/org/springframework/security/oauth2/core/OAuth2Token.html[`OAuth2Token`] instances associated with an `OAuth2Authorization` vary, depending on the authorization grant type.
For the OAuth2 https://datatracker.ietf.org/doc/html/rfc6749#section-4.1[authorization_code grant], an `OAuth2AuthorizationCode`, an `OAuth2AccessToken`, and an (optional) `OAuth2RefreshToken` are associated.
For the OpenID Connect 1.0 https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth[authorization_code grant], an `OAuth2AuthorizationCode`, an {spring-security-api-base-url}/org/springframework/security/oauth2/core/oidc/OidcIdToken.html[`OidcIdToken`], an `OAuth2AccessToken`, and an (optional) `OAuth2RefreshToken` are associated.
For the OAuth2 https://datatracker.ietf.org/doc/html/rfc6749#section-4.4[client_credentials grant], only an `OAuth2AccessToken` is associated.
`OAuth2Authorization` and its attributes are defined as follows:
[source,java]
----
public class OAuth2Authorization implements Serializable {
private String id; <1>
private String registeredClientId; <2>
private String principalName; <3>
private AuthorizationGrantType authorizationGrantType; <4>
private Map<Class<? extends OAuth2Token>, Token<?>> tokens; <5>
private Map<String, Object> attributes; <6>
...
}
----
<1> `id`: The ID that uniquely identifies the `OAuth2Authorization`.
<2> `registeredClientId`: The ID that uniquely identifies the <<registered-client, RegisteredClient>>.
<3> `principalName`: The principal name of the resource owner (or client).
<4> `authorizationGrantType`: The `AuthorizationGrantType` used.
<5> `tokens`: The `OAuth2Token` instances (and associated metadata) specific to the executed authorization grant type.
<6> `attributes`: The additional attributes specific to the executed authorization grant type for example, the authenticated `Principal`, `OAuth2AuthorizationRequest`, authorized scope(s), and others.
`OAuth2Authorization` and its associated `OAuth2Token` instances have a set lifespan.
A newly issued `OAuth2Token` is active and becomes inactive when it either expires or is invalidated (revoked).
The `OAuth2Authorization` is (implicitly) inactive when all associated `OAuth2Token` instances are inactive.
Each `OAuth2Token` is held in an `OAuth2Authorization.Token`, which provides accessors for `isExpired()`, `isInvalidated()`, and `isActive()`.
`OAuth2Authorization.Token` also provides `getClaims()`, which returns the claims (if any) associated with the `OAuth2Token`.
[[oauth2-authorization-service]]
== OAuth2AuthorizationService
The `OAuth2AuthorizationService` is the central component where new authorizations are stored and existing authorizations are queried.
It is used by other components when following a specific protocol flow for example, client authentication, authorization grant processing, token introspection, token revocation, dynamic client registration, and others.
The provided implementations of `OAuth2AuthorizationService` are `InMemoryOAuth2AuthorizationService` and `JdbcOAuth2AuthorizationService`.
The `InMemoryOAuth2AuthorizationService` implementation stores `OAuth2Authorization` instances in-memory and is recommended *ONLY* to be used during development and testing.
`JdbcOAuth2AuthorizationService` is a JDBC implementation that persists `OAuth2Authorization` instances by using `JdbcOperations`.
[NOTE]
The `OAuth2AuthorizationService` is an *OPTIONAL* component and defaults to `InMemoryOAuth2AuthorizationService`.
The following example shows how to register an `OAuth2AuthorizationService` `@Bean`:
[source,java]
----
@Bean
public OAuth2AuthorizationService authorizationService() {
return new InMemoryOAuth2AuthorizationService();
}
----
Alternatively, you can configure the `OAuth2AuthorizationService` through the xref:configuration-model.adoc#customizing-the-configuration[`OAuth2AuthorizationServerConfigurer`]:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.authorizationService(authorizationService);
...
return http.build();
}
----
[NOTE]
The `OAuth2AuthorizationServerConfigurer` is useful when applying multiple configuration options simultaneously.
[[oauth2-authorization-consent]]
== OAuth2AuthorizationConsent
An `OAuth2AuthorizationConsent` is a representation of an authorization "consent" (decision) from an https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1[OAuth2 authorization request flow] for example, the `authorization_code` grant, which holds the authorities granted to a <<registered-client, client>> by the resource owner.
When authorizing access to a client, the resource owner may grant only a subset of the authorities requested by the client.
The typical use case is the `authorization_code` grant flow, in which the client requests scope(s) and the resource owner grants (or denies) access to the requested scope(s).
After the completion of an OAuth2 authorization request flow, an `OAuth2AuthorizationConsent` is created (or updated) and associates the granted authorities with the client and resource owner.
`OAuth2AuthorizationConsent` and its attributes are defined as follows:
[source,java]
----
public final class OAuth2AuthorizationConsent implements Serializable {
private final String registeredClientId; <1>
private final String principalName; <2>
private final Set<GrantedAuthority> authorities; <3>
...
}
----
<1> `registeredClientId`: The ID that uniquely identifies the <<registered-client, RegisteredClient>>.
<2> `principalName`: The principal name of the resource owner.
<3> `authorities`: The authorities granted to the client by the resource owner. An authority can represent a scope, a claim, a permission, a role, and others.
[[oauth2-authorization-consent-service]]
== OAuth2AuthorizationConsentService
The `OAuth2AuthorizationConsentService` is the central component where new authorization consents are stored and existing authorization consents are queried.
It is primarily used by components that implement an OAuth2 authorization request flow for example, the `authorization_code` grant.
The provided implementations of `OAuth2AuthorizationConsentService` are `InMemoryOAuth2AuthorizationConsentService` and `JdbcOAuth2AuthorizationConsentService`.
The `InMemoryOAuth2AuthorizationConsentService` implementation stores `OAuth2AuthorizationConsent` instances in-memory and is recommended *ONLY* for development and testing.
`JdbcOAuth2AuthorizationConsentService` is a JDBC implementation that persists `OAuth2AuthorizationConsent` instances by using `JdbcOperations`.
[NOTE]
The `OAuth2AuthorizationConsentService` is an *OPTIONAL* component and defaults to `InMemoryOAuth2AuthorizationConsentService`.
The following example shows how to register an `OAuth2AuthorizationConsentService` `@Bean`:
[source,java]
----
@Bean
public OAuth2AuthorizationConsentService authorizationConsentService() {
return new InMemoryOAuth2AuthorizationConsentService();
}
----
Alternatively, you can configure the `OAuth2AuthorizationConsentService` through the xref:configuration-model.adoc#customizing-the-configuration[`OAuth2AuthorizationServerConfigurer`]:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.authorizationConsentService(authorizationConsentService);
...
return http.build();
}
----
[NOTE]
The `OAuth2AuthorizationServerConfigurer` is useful when applying multiple configuration options simultaneously.
[[oauth2-token-context]]
== OAuth2TokenContext
An `OAuth2TokenContext` is a context object that holds information associated with an `OAuth2Token` and is used by an <<oauth2-token-generator, OAuth2TokenGenerator>> and <<oauth2-token-customizer, OAuth2TokenCustomizer>>.
`OAuth2TokenContext` provides the following accessors:
[source,java]
----
public interface OAuth2TokenContext extends Context {
default RegisteredClient getRegisteredClient() ... <1>
default <T extends Authentication> T getPrincipal() ... <2>
default ProviderContext getProviderContext() ... <3>
@Nullable
default OAuth2Authorization getAuthorization() ... <4>
default Set<String> getAuthorizedScopes() ... <5>
default OAuth2TokenType getTokenType() ... <6>
default AuthorizationGrantType getAuthorizationGrantType() ... <7>
default <T extends Authentication> T getAuthorizationGrant() ... <8>
...
}
----
<1> `getRegisteredClient()`: The <<registered-client, RegisteredClient>> associated with the authorization grant.
<2> `getPrincipal()`: The `Authentication` instance of the resource owner (or client).
<3> `getProviderContext()`: The xref:configuration-model.adoc#configuring-provider-settings[`ProviderContext`] object that holds information related to the provider.
<4> `getAuthorization()`: The <<oauth2-authorization, OAuth2Authorization>> associated with the authorization grant.
<5> `getAuthorizedScopes()`: The scope(s) authorized for the client.
<6> `getTokenType()`: The `OAuth2TokenType` to generate. The supported values are `code`, `access_token`, `refresh_token`, and `id_token`.
<7> `getAuthorizationGrantType()`: The `AuthorizationGrantType` associated with the authorization grant.
<8> `getAuthorizationGrant()`: The `Authentication` instance used by the `AuthenticationProvider` that processes the authorization grant.
[[oauth2-token-generator]]
== OAuth2TokenGenerator
An `OAuth2TokenGenerator` is responsible for generating an `OAuth2Token` from the information contained in the provided <<oauth2-token-context, OAuth2TokenContext>>.
The `OAuth2Token` generated primarily depends on the type of `OAuth2TokenType` specified in the `OAuth2TokenContext`.
For example, when the `value` for `OAuth2TokenType` is:
* `code`, then `OAuth2AuthorizationCode` is generated.
* `access_token`, then `OAuth2AccessToken` is generated.
* `refresh_token`, then `OAuth2RefreshToken` is generated.
* `id_token`, then `OidcIdToken` is generated.
Furthermore, the format of the generated `OAuth2AccessToken` varies, depending on the `TokenSettings.getAccessTokenFormat()` configured for the <<registered-client, RegisteredClient>>.
If the format is `OAuth2TokenFormat.SELF_CONTAINED` (the default), then a `Jwt` is generated.
If the format is `OAuth2TokenFormat.REFERENCE`, then an "opaque" token is generated.
Finally, if the generated `OAuth2Token` has a set of claims and implements `ClaimAccessor`, the claims are made accessible from <<oauth2-authorization, OAuth2Authorization.Token.getClaims()>>.
The `OAuth2TokenGenerator` is primarily used by components that implement authorization grant processing for example, `authorization_code`, `client_credentials`, and `refresh_token`.
The provided implementations are `OAuth2AccessTokenGenerator`, `OAuth2RefreshTokenGenerator`, and `JwtGenerator`.
The `OAuth2AccessTokenGenerator` generates an "opaque" (`OAuth2TokenFormat.REFERENCE`) access token, and the `JwtGenerator` generates a `Jwt` (`OAuth2TokenFormat.SELF_CONTAINED`).
[NOTE]
The `OAuth2TokenGenerator` is an *OPTIONAL* component and defaults to a `DelegatingOAuth2TokenGenerator` composed of an `OAuth2AccessTokenGenerator` and `OAuth2RefreshTokenGenerator`.
[NOTE]
If a `JwtEncoder` `@Bean` or `JWKSource<SecurityContext>` `@Bean` is registered, then a `JwtGenerator` is additionally composed in the `DelegatingOAuth2TokenGenerator`.
The `OAuth2TokenGenerator` provides great flexibility, as it can support any custom token format for `access_token` and `refresh_token`.
The following example shows how to register an `OAuth2TokenGenerator` `@Bean`:
[source,java]
----
@Bean
public OAuth2TokenGenerator<?> tokenGenerator() {
JwtEncoder jwtEncoder = ...
JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
return new DelegatingOAuth2TokenGenerator(
jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
}
----
Alternatively, you can configure the `OAuth2TokenGenerator` through the xref:configuration-model.adoc#customizing-the-configuration[`OAuth2AuthorizationServerConfigurer`]:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.tokenGenerator(tokenGenerator);
...
return http.build();
}
----
[NOTE]
The `OAuth2AuthorizationServerConfigurer` is useful when applying multiple configuration options simultaneously.
[[oauth2-token-customizer]]
== OAuth2TokenCustomizer
An `OAuth2TokenCustomizer` provides the ability to customize the attributes of an `OAuth2Token`, which are accessible in the provided <<oauth2-token-context, OAuth2TokenContext>>.
It is used by an <<oauth2-token-generator, OAuth2TokenGenerator>> to let it customize the attributes of the `OAuth2Token` before it is generated.
An `OAuth2TokenCustomizer<OAuth2TokenClaimsContext>` declared with a generic type of `OAuth2TokenClaimsContext` (`implements OAuth2TokenContext`) provides the ability to customize the claims of an "opaque" `OAuth2AccessToken`.
`OAuth2TokenClaimsContext.getClaims()` provides access to the `OAuth2TokenClaimsSet.Builder`, allowing the ability to add, replace, and remove claims.
The following example shows how to implement an `OAuth2TokenCustomizer<OAuth2TokenClaimsContext>` and configure it with an `OAuth2AccessTokenGenerator`:
[source,java]
----
@Bean
public OAuth2TokenGenerator<?> tokenGenerator() {
JwtEncoder jwtEncoder = ...
JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
accessTokenGenerator.setAccessTokenCustomizer(accessTokenCustomizer());
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
return new DelegatingOAuth2TokenGenerator(
jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
}
@Bean
public OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer() {
return context -> {
OAuth2TokenClaimsSet.Builder claims = context.getClaims();
// Customize claims
};
}
----
[NOTE]
If the `OAuth2TokenGenerator` is not provided as a `@Bean` or is not configured through the `OAuth2AuthorizationServerConfigurer`, an `OAuth2TokenCustomizer<OAuth2TokenClaimsContext>` `@Bean` will automatically be configured with an `OAuth2AccessTokenGenerator`.
An `OAuth2TokenCustomizer<JwtEncodingContext>` declared with a generic type of `JwtEncodingContext` (`implements OAuth2TokenContext`) provides the ability to customize the headers and claims of a `Jwt`.
`JwtEncodingContext.getHeaders()` provides access to the `JwsHeader.Builder`, allowing the ability to add, replace, and remove headers.
`JwtEncodingContext.getClaims()` provides access to the `JwtClaimsSet.Builder`, allowing the ability to add, replace, and remove claims.
The following example shows how to implement an `OAuth2TokenCustomizer<JwtEncodingContext>` and configure it with a `JwtGenerator`:
[source,java]
----
@Bean
public OAuth2TokenGenerator<?> tokenGenerator() {
JwtEncoder jwtEncoder = ...
JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
jwtGenerator.setJwtCustomizer(jwtCustomizer());
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
return new DelegatingOAuth2TokenGenerator(
jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
}
@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
return context -> {
JwsHeader.Builder headers = context.getHeaders();
JwtClaimsSet.Builder claims = context.getClaims();
if (context.getTokenType().equals(OAuth2TokenType.ACCESS_TOKEN)) {
// Customize headers/claims for access_token
} else if (context.getTokenType().getValue().equals(OidcParameterNames.ID_TOKEN)) {
// Customize headers/claims for id_token
}
};
}
----
[NOTE]
If the `OAuth2TokenGenerator` is not provided as a `@Bean` or is not configured through the `OAuth2AuthorizationServerConfigurer`, an `OAuth2TokenCustomizer<JwtEncodingContext>` `@Bean` will automatically be configured with a `JwtGenerator`.
[TIP]
For an example showing how you can xref:guides/how-to-userinfo.adoc#customize-id-token[customize the ID token], see the guide xref:guides/how-to-userinfo.adoc#how-to-userinfo[How-to: Customize the OpenID Connect 1.0 UserInfo response].

View File

@@ -0,0 +1,30 @@
plugins {
id "java"
}
group = project.rootProject.group
version = project.rootProject.version
sourceCompatibility = "1.8"
repositories {
mavenCentral()
}
dependencies {
implementation platform("org.springframework.boot:spring-boot-dependencies:2.7.0")
implementation "org.springframework.boot:spring-boot-starter-web"
implementation "org.springframework.boot:spring-boot-starter-thymeleaf"
implementation "org.springframework.boot:spring-boot-starter-security"
implementation "org.springframework.boot:spring-boot-starter-oauth2-client"
implementation "org.springframework.boot:spring-boot-starter-oauth2-resource-server"
implementation "org.springframework.boot:spring-boot-starter-data-jpa"
implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity5"
implementation project(":spring-security-oauth2-authorization-server")
runtimeOnly "com.h2database:h2"
testImplementation "org.springframework.boot:spring-boot-starter-test"
testImplementation "org.springframework.security:spring-security-test"
}
tasks.named("test") {
useJUnitPlatform()
}

View File

@@ -0,0 +1,157 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.gettingStarted;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
@Configuration
public class SecurityConfig {
@Bean // <1>
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
// @formatter:off
http
// Redirect to the login page when not authenticated from the
// authorization endpoint
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(
new LoginUrlAuthenticationEntryPoint("/login"))
);
// @formatter:on
return http.build();
}
@Bean // <2>
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
// Form login handles the redirect to the login page from the
// authorization server filter chain
.formLogin(Customizer.withDefaults());
// @formatter:on
return http.build();
}
@Bean // <3>
public UserDetailsService userDetailsService() {
// @formatter:off
UserDetails userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
// @formatter:on
return new InMemoryUserDetailsManager(userDetails);
}
@Bean // <4>
public RegisteredClientRepository registeredClientRepository() {
// @formatter:off
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("messaging-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.scope(OidcScopes.OPENID)
.scope("message.read")
.scope("message.write")
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();
// @formatter:on
return new InMemoryRegisteredClientRepository(registeredClient);
}
@Bean // <5>
public JWKSource<SecurityContext> jwkSource() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// @formatter:off
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
// @formatter:on
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet<>(jwkSet);
}
private static KeyPair generateRsaKey() { // <6>
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
@Bean // <7>
public ProviderSettings providerSettings() {
return ProviderSettings.builder().build();
}
}

View File

@@ -0,0 +1,272 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jpa.entity.authorization;
import java.time.Instant;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "`authorization`")
public class Authorization {
@Id
@Column
private String id;
private String registeredClientId;
private String principalName;
private String authorizationGrantType;
@Column(length = 4000)
private String attributes;
@Column(length = 500)
private String state;
@Column(length = 4000)
private String authorizationCodeValue;
private Instant authorizationCodeIssuedAt;
private Instant authorizationCodeExpiresAt;
private String authorizationCodeMetadata;
@Column(length = 4000)
private String accessTokenValue;
private Instant accessTokenIssuedAt;
private Instant accessTokenExpiresAt;
@Column(length = 2000)
private String accessTokenMetadata;
private String accessTokenType;
@Column(length = 1000)
private String accessTokenScopes;
@Column(length = 4000)
private String refreshTokenValue;
private Instant refreshTokenIssuedAt;
private Instant refreshTokenExpiresAt;
@Column(length = 2000)
private String refreshTokenMetadata;
@Column(length = 4000)
private String oidcIdTokenValue;
private Instant oidcIdTokenIssuedAt;
private Instant oidcIdTokenExpiresAt;
@Column(length = 2000)
private String oidcIdTokenMetadata;
@Column(length = 2000)
private String oidcIdTokenClaims;
// @fold:on
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getRegisteredClientId() {
return registeredClientId;
}
public void setRegisteredClientId(String registeredClientId) {
this.registeredClientId = registeredClientId;
}
public String getPrincipalName() {
return principalName;
}
public void setPrincipalName(String principalName) {
this.principalName = principalName;
}
public String getAuthorizationGrantType() {
return authorizationGrantType;
}
public void setAuthorizationGrantType(String authorizationGrantType) {
this.authorizationGrantType = authorizationGrantType;
}
public String getAttributes() {
return attributes;
}
public void setAttributes(String attributes) {
this.attributes = attributes;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getAuthorizationCodeValue() {
return authorizationCodeValue;
}
public void setAuthorizationCodeValue(String authorizationCode) {
this.authorizationCodeValue = authorizationCode;
}
public Instant getAuthorizationCodeIssuedAt() {
return authorizationCodeIssuedAt;
}
public void setAuthorizationCodeIssuedAt(Instant authorizationCodeIssuedAt) {
this.authorizationCodeIssuedAt = authorizationCodeIssuedAt;
}
public Instant getAuthorizationCodeExpiresAt() {
return authorizationCodeExpiresAt;
}
public void setAuthorizationCodeExpiresAt(Instant authorizationCodeExpiresAt) {
this.authorizationCodeExpiresAt = authorizationCodeExpiresAt;
}
public String getAuthorizationCodeMetadata() {
return authorizationCodeMetadata;
}
public void setAuthorizationCodeMetadata(String authorizationCodeMetadata) {
this.authorizationCodeMetadata = authorizationCodeMetadata;
}
public String getAccessTokenValue() {
return accessTokenValue;
}
public void setAccessTokenValue(String accessToken) {
this.accessTokenValue = accessToken;
}
public Instant getAccessTokenIssuedAt() {
return accessTokenIssuedAt;
}
public void setAccessTokenIssuedAt(Instant accessTokenIssuedAt) {
this.accessTokenIssuedAt = accessTokenIssuedAt;
}
public Instant getAccessTokenExpiresAt() {
return accessTokenExpiresAt;
}
public void setAccessTokenExpiresAt(Instant accessTokenExpiresAt) {
this.accessTokenExpiresAt = accessTokenExpiresAt;
}
public String getAccessTokenMetadata() {
return accessTokenMetadata;
}
public void setAccessTokenMetadata(String accessTokenMetadata) {
this.accessTokenMetadata = accessTokenMetadata;
}
public String getAccessTokenType() {
return accessTokenType;
}
public void setAccessTokenType(String accessTokenType) {
this.accessTokenType = accessTokenType;
}
public String getAccessTokenScopes() {
return accessTokenScopes;
}
public void setAccessTokenScopes(String accessTokenScopes) {
this.accessTokenScopes = accessTokenScopes;
}
public String getRefreshTokenValue() {
return refreshTokenValue;
}
public void setRefreshTokenValue(String refreshToken) {
this.refreshTokenValue = refreshToken;
}
public Instant getRefreshTokenIssuedAt() {
return refreshTokenIssuedAt;
}
public void setRefreshTokenIssuedAt(Instant refreshTokenIssuedAt) {
this.refreshTokenIssuedAt = refreshTokenIssuedAt;
}
public Instant getRefreshTokenExpiresAt() {
return refreshTokenExpiresAt;
}
public void setRefreshTokenExpiresAt(Instant refreshTokenExpiresAt) {
this.refreshTokenExpiresAt = refreshTokenExpiresAt;
}
public String getRefreshTokenMetadata() {
return refreshTokenMetadata;
}
public void setRefreshTokenMetadata(String refreshTokenMetadata) {
this.refreshTokenMetadata = refreshTokenMetadata;
}
public String getOidcIdTokenValue() {
return oidcIdTokenValue;
}
public void setOidcIdTokenValue(String idToken) {
this.oidcIdTokenValue = idToken;
}
public Instant getOidcIdTokenIssuedAt() {
return oidcIdTokenIssuedAt;
}
public void setOidcIdTokenIssuedAt(Instant idTokenIssuedAt) {
this.oidcIdTokenIssuedAt = idTokenIssuedAt;
}
public Instant getOidcIdTokenExpiresAt() {
return oidcIdTokenExpiresAt;
}
public void setOidcIdTokenExpiresAt(Instant idTokenExpiresAt) {
this.oidcIdTokenExpiresAt = idTokenExpiresAt;
}
public String getOidcIdTokenMetadata() {
return oidcIdTokenMetadata;
}
public void setOidcIdTokenMetadata(String idTokenMetadata) {
this.oidcIdTokenMetadata = idTokenMetadata;
}
public String getOidcIdTokenClaims() {
return oidcIdTokenClaims;
}
public void setOidcIdTokenClaims(String idTokenClaims) {
this.oidcIdTokenClaims = idTokenClaims;
}
// @fold:off
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jpa.entity.authorizationConsent;
import java.io.Serializable;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
@Entity
@Table(name = "`authorizationConsent`")
@IdClass(AuthorizationConsent.AuthorizationConsentId.class)
public class AuthorizationConsent {
@Id
private String registeredClientId;
@Id
private String principalName;
@Column(length = 1000)
private String authorities;
// @fold:on
public String getRegisteredClientId() {
return registeredClientId;
}
public void setRegisteredClientId(String registeredClientId) {
this.registeredClientId = registeredClientId;
}
public String getPrincipalName() {
return principalName;
}
public void setPrincipalName(String principalName) {
this.principalName = principalName;
}
public String getAuthorities() {
return authorities;
}
public void setAuthorities(String authorities) {
this.authorities = authorities;
}
// @fold:off
public static class AuthorizationConsentId implements Serializable {
private String registeredClientId;
private String principalName;
// @fold:on
public String getRegisteredClientId() {
return registeredClientId;
}
public void setRegisteredClientId(String registeredClientId) {
this.registeredClientId = registeredClientId;
}
public String getPrincipalName() {
return principalName;
}
public void setPrincipalName(String principalName) {
this.principalName = principalName;
}
// @fold:off
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AuthorizationConsentId that = (AuthorizationConsentId) o;
return registeredClientId.equals(that.registeredClientId) && principalName.equals(that.principalName);
}
@Override
public int hashCode() {
return Objects.hash(registeredClientId, principalName);
}
}
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jpa.entity.client;
import java.time.Instant;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "`client`")
public class Client {
@Id
private String id;
private String clientId;
private Instant clientIdIssuedAt;
private String clientSecret;
private Instant clientSecretExpiresAt;
private String clientName;
@Column(length = 1000)
private String clientAuthenticationMethods;
@Column(length = 1000)
private String authorizationGrantTypes;
@Column(length = 1000)
private String redirectUris;
@Column(length = 1000)
private String scopes;
@Column(length = 2000)
private String clientSettings;
@Column(length = 2000)
private String tokenSettings;
// @fold:on
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public Instant getClientIdIssuedAt() {
return clientIdIssuedAt;
}
public void setClientIdIssuedAt(Instant clientIdIssuedAt) {
this.clientIdIssuedAt = clientIdIssuedAt;
}
public String getClientSecret() {
return clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
public Instant getClientSecretExpiresAt() {
return clientSecretExpiresAt;
}
public void setClientSecretExpiresAt(Instant clientSecretExpiresAt) {
this.clientSecretExpiresAt = clientSecretExpiresAt;
}
public String getClientName() {
return clientName;
}
public void setClientName(String clientName) {
this.clientName = clientName;
}
public String getClientAuthenticationMethods() {
return clientAuthenticationMethods;
}
public void setClientAuthenticationMethods(String clientAuthenticationMethods) {
this.clientAuthenticationMethods = clientAuthenticationMethods;
}
public String getAuthorizationGrantTypes() {
return authorizationGrantTypes;
}
public void setAuthorizationGrantTypes(String authorizationGrantTypes) {
this.authorizationGrantTypes = authorizationGrantTypes;
}
public String getRedirectUris() {
return redirectUris;
}
public void setRedirectUris(String redirectUris) {
this.redirectUris = redirectUris;
}
public String getScopes() {
return scopes;
}
public void setScopes(String scopes) {
this.scopes = scopes;
}
public String getClientSettings() {
return clientSettings;
}
public void setClientSettings(String clientSettings) {
this.clientSettings = clientSettings;
}
public String getTokenSettings() {
return tokenSettings;
}
public void setTokenSettings(String tokenSettings) {
this.tokenSettings = tokenSettings;
}
// @fold:off
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jpa.repository.authorization;
import java.util.Optional;
import sample.jpa.entity.authorization.Authorization;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface AuthorizationRepository extends JpaRepository<Authorization, String> {
Optional<Authorization> findByState(String state);
Optional<Authorization> findByAuthorizationCodeValue(String authorizationCode);
Optional<Authorization> findByAccessTokenValue(String accessToken);
Optional<Authorization> findByRefreshTokenValue(String refreshToken);
@Query("select a from Authorization a where a.state = :token" +
" or a.authorizationCodeValue = :token" +
" or a.accessTokenValue = :token" +
" or a.refreshTokenValue = :token"
)
Optional<Authorization> findByStateOrAuthorizationCodeValueOrAccessTokenValueOrRefreshTokenValue(@Param("token") String token);
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jpa.repository.authorizationConsent;
import java.util.Optional;
import sample.jpa.entity.authorizationConsent.AuthorizationConsent;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface AuthorizationConsentRepository extends JpaRepository<AuthorizationConsent, AuthorizationConsent.AuthorizationConsentId> {
Optional<AuthorizationConsent> findByRegisteredClientIdAndPrincipalName(String registeredClientId, String principalName);
void deleteByRegisteredClientIdAndPrincipalName(String registeredClientId, String principalName);
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jpa.repository.client;
import java.util.Optional;
import sample.jpa.entity.client.Client;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ClientRepository extends JpaRepository<Client, String> {
Optional<Client> findByClientId(String clientId);
}

View File

@@ -0,0 +1,261 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jpa.service.authorization;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import sample.jpa.entity.authorization.Authorization;
import sample.jpa.repository.authorization.AuthorizationRepository;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@Component
public class JpaOAuth2AuthorizationService implements OAuth2AuthorizationService {
private final AuthorizationRepository authorizationRepository;
private final RegisteredClientRepository registeredClientRepository;
private final ObjectMapper objectMapper = new ObjectMapper();
public JpaOAuth2AuthorizationService(AuthorizationRepository authorizationRepository, RegisteredClientRepository registeredClientRepository) {
Assert.notNull(authorizationRepository, "authorizationRepository cannot be null");
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
this.authorizationRepository = authorizationRepository;
this.registeredClientRepository = registeredClientRepository;
ClassLoader classLoader = JpaOAuth2AuthorizationService.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
this.objectMapper.registerModules(securityModules);
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
}
@Override
public void save(OAuth2Authorization authorization) {
Assert.notNull(authorization, "authorization cannot be null");
this.authorizationRepository.save(toEntity(authorization));
}
@Override
public void remove(OAuth2Authorization authorization) {
Assert.notNull(authorization, "authorization cannot be null");
this.authorizationRepository.deleteById(authorization.getId());
}
@Override
public OAuth2Authorization findById(String id) {
Assert.hasText(id, "id cannot be empty");
return this.authorizationRepository.findById(id).map(this::toObject).orElse(null);
}
@Override
public OAuth2Authorization findByToken(String token, OAuth2TokenType tokenType) {
Assert.hasText(token, "token cannot be empty");
Optional<Authorization> result;
if (tokenType == null) {
result = this.authorizationRepository.findByStateOrAuthorizationCodeValueOrAccessTokenValueOrRefreshTokenValue(token);
} else if (OAuth2ParameterNames.STATE.equals(tokenType.getValue())) {
result = this.authorizationRepository.findByState(token);
} else if (OAuth2ParameterNames.CODE.equals(tokenType.getValue())) {
result = this.authorizationRepository.findByAuthorizationCodeValue(token);
} else if (OAuth2ParameterNames.ACCESS_TOKEN.equals(tokenType.getValue())) {
result = this.authorizationRepository.findByAccessTokenValue(token);
} else if (OAuth2ParameterNames.REFRESH_TOKEN.equals(tokenType.getValue())) {
result = this.authorizationRepository.findByRefreshTokenValue(token);
} else {
result = Optional.empty();
}
return result.map(this::toObject).orElse(null);
}
private OAuth2Authorization toObject(Authorization entity) {
RegisteredClient registeredClient = this.registeredClientRepository.findById(entity.getRegisteredClientId());
if (registeredClient == null) {
throw new DataRetrievalFailureException(
"The RegisteredClient with id '" + entity.getRegisteredClientId() + "' was not found in the RegisteredClientRepository.");
}
OAuth2Authorization.Builder builder = OAuth2Authorization.withRegisteredClient(registeredClient)
.id(entity.getId())
.principalName(entity.getPrincipalName())
.authorizationGrantType(resolveAuthorizationGrantType(entity.getAuthorizationGrantType()))
.attributes(attributes -> attributes.putAll(parseMap(entity.getAttributes())));
if (entity.getState() != null) {
builder.attribute(OAuth2ParameterNames.STATE, entity.getState());
}
if (entity.getAuthorizationCodeValue() != null) {
OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(
entity.getAuthorizationCodeValue(),
entity.getAuthorizationCodeIssuedAt(),
entity.getAuthorizationCodeExpiresAt());
builder.token(authorizationCode, metadata -> metadata.putAll(parseMap(entity.getAuthorizationCodeMetadata())));
}
if (entity.getAccessTokenValue() != null) {
OAuth2AccessToken accessToken = new OAuth2AccessToken(
OAuth2AccessToken.TokenType.BEARER,
entity.getAccessTokenValue(),
entity.getAccessTokenIssuedAt(),
entity.getAccessTokenExpiresAt(),
StringUtils.commaDelimitedListToSet(entity.getAccessTokenScopes()));
builder.token(accessToken, metadata -> metadata.putAll(parseMap(entity.getAccessTokenMetadata())));
}
if (entity.getRefreshTokenValue() != null) {
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(
entity.getRefreshTokenValue(),
entity.getRefreshTokenIssuedAt(),
entity.getRefreshTokenExpiresAt());
builder.token(refreshToken, metadata -> metadata.putAll(parseMap(entity.getRefreshTokenMetadata())));
}
if (entity.getOidcIdTokenValue() != null) {
OidcIdToken idToken = new OidcIdToken(
entity.getOidcIdTokenValue(),
entity.getOidcIdTokenIssuedAt(),
entity.getOidcIdTokenExpiresAt(),
parseMap(entity.getOidcIdTokenClaims()));
builder.token(idToken, metadata -> metadata.putAll(parseMap(entity.getOidcIdTokenMetadata())));
}
return builder.build();
}
private Authorization toEntity(OAuth2Authorization authorization) {
Authorization entity = new Authorization();
entity.setId(authorization.getId());
entity.setRegisteredClientId(authorization.getRegisteredClientId());
entity.setPrincipalName(authorization.getPrincipalName());
entity.setAuthorizationGrantType(authorization.getAuthorizationGrantType().getValue());
entity.setAttributes(writeMap(authorization.getAttributes()));
entity.setState(authorization.getAttribute(OAuth2ParameterNames.STATE));
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode =
authorization.getToken(OAuth2AuthorizationCode.class);
setTokenValues(
authorizationCode,
entity::setAuthorizationCodeValue,
entity::setAuthorizationCodeIssuedAt,
entity::setAuthorizationCodeExpiresAt,
entity::setAuthorizationCodeMetadata
);
OAuth2Authorization.Token<OAuth2AccessToken> accessToken =
authorization.getToken(OAuth2AccessToken.class);
setTokenValues(
accessToken,
entity::setAccessTokenValue,
entity::setAccessTokenIssuedAt,
entity::setAccessTokenExpiresAt,
entity::setAccessTokenMetadata
);
if (accessToken != null && accessToken.getToken().getScopes() != null) {
entity.setAccessTokenScopes(StringUtils.collectionToDelimitedString(accessToken.getToken().getScopes(), ","));
}
OAuth2Authorization.Token<OAuth2RefreshToken> refreshToken =
authorization.getToken(OAuth2RefreshToken.class);
setTokenValues(
refreshToken,
entity::setRefreshTokenValue,
entity::setRefreshTokenIssuedAt,
entity::setRefreshTokenExpiresAt,
entity::setRefreshTokenMetadata
);
OAuth2Authorization.Token<OidcIdToken> oidcIdToken =
authorization.getToken(OidcIdToken.class);
setTokenValues(
oidcIdToken,
entity::setOidcIdTokenValue,
entity::setOidcIdTokenIssuedAt,
entity::setOidcIdTokenExpiresAt,
entity::setOidcIdTokenMetadata
);
if (oidcIdToken != null) {
entity.setOidcIdTokenClaims(writeMap(oidcIdToken.getClaims()));
}
return entity;
}
private void setTokenValues(
OAuth2Authorization.Token<?> token,
Consumer<String> tokenValueConsumer,
Consumer<Instant> issuedAtConsumer,
Consumer<Instant> expiresAtConsumer,
Consumer<String> metadataConsumer) {
if (token != null) {
OAuth2Token oAuth2Token = token.getToken();
tokenValueConsumer.accept(oAuth2Token.getTokenValue());
issuedAtConsumer.accept(oAuth2Token.getIssuedAt());
expiresAtConsumer.accept(oAuth2Token.getExpiresAt());
metadataConsumer.accept(writeMap(token.getMetadata()));
}
}
private Map<String, Object> parseMap(String data) {
try {
return this.objectMapper.readValue(data, new TypeReference<Map<String, Object>>() {
});
} catch (Exception ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
}
}
private String writeMap(Map<String, Object> metadata) {
try {
return this.objectMapper.writeValueAsString(metadata);
} catch (Exception ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
}
}
private static AuthorizationGrantType resolveAuthorizationGrantType(String authorizationGrantType) {
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.AUTHORIZATION_CODE;
} else if (AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.CLIENT_CREDENTIALS;
} else if (AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.REFRESH_TOKEN;
}
return new AuthorizationGrantType(authorizationGrantType); // Custom authorization grant type
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jpa.service.authorizationConsent;
import java.util.HashSet;
import java.util.Set;
import sample.jpa.entity.authorizationConsent.AuthorizationConsent;
import sample.jpa.repository.authorizationConsent.AuthorizationConsentRepository;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@Component
public class JpaOAuth2AuthorizationConsentService implements OAuth2AuthorizationConsentService {
private final AuthorizationConsentRepository authorizationConsentRepository;
private final RegisteredClientRepository registeredClientRepository;
public JpaOAuth2AuthorizationConsentService(AuthorizationConsentRepository authorizationConsentRepository, RegisteredClientRepository registeredClientRepository) {
Assert.notNull(authorizationConsentRepository, "authorizationConsentRepository cannot be null");
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
this.authorizationConsentRepository = authorizationConsentRepository;
this.registeredClientRepository = registeredClientRepository;
}
@Override
public void save(OAuth2AuthorizationConsent authorizationConsent) {
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
this.authorizationConsentRepository.save(toEntity(authorizationConsent));
}
@Override
public void remove(OAuth2AuthorizationConsent authorizationConsent) {
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
this.authorizationConsentRepository.deleteByRegisteredClientIdAndPrincipalName(
authorizationConsent.getRegisteredClientId(), authorizationConsent.getPrincipalName());
}
@Override
public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) {
Assert.hasText(registeredClientId, "registeredClientId cannot be empty");
Assert.hasText(principalName, "principalName cannot be empty");
return this.authorizationConsentRepository.findByRegisteredClientIdAndPrincipalName(
registeredClientId, principalName).map(this::toObject).orElse(null);
}
private OAuth2AuthorizationConsent toObject(AuthorizationConsent authorizationConsent) {
String registeredClientId = authorizationConsent.getRegisteredClientId();
RegisteredClient registeredClient = this.registeredClientRepository.findById(registeredClientId);
if (registeredClient == null) {
throw new DataRetrievalFailureException(
"The RegisteredClient with id '" + registeredClientId + "' was not found in the RegisteredClientRepository.");
}
OAuth2AuthorizationConsent.Builder builder = OAuth2AuthorizationConsent.withId(
registeredClientId, authorizationConsent.getPrincipalName());
if (authorizationConsent.getAuthorities() != null) {
for (String authority : StringUtils.commaDelimitedListToSet(authorizationConsent.getAuthorities())) {
builder.authority(new SimpleGrantedAuthority(authority));
}
}
return builder.build();
}
private AuthorizationConsent toEntity(OAuth2AuthorizationConsent authorizationConsent) {
AuthorizationConsent entity = new AuthorizationConsent();
entity.setRegisteredClientId(authorizationConsent.getRegisteredClientId());
entity.setPrincipalName(authorizationConsent.getPrincipalName());
Set<String> authorities = new HashSet<>();
for (GrantedAuthority authority : authorizationConsent.getAuthorities()) {
authorities.add(authority.getAuthority());
}
entity.setAuthorities(StringUtils.collectionToCommaDelimitedString(authorities));
return entity;
}
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jpa.service.client;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import sample.jpa.entity.client.Client;
import sample.jpa.repository.client.ClientRepository;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@Component
public class JpaRegisteredClientRepository implements RegisteredClientRepository {
private final ClientRepository clientRepository;
private final ObjectMapper objectMapper = new ObjectMapper();
public JpaRegisteredClientRepository(ClientRepository clientRepository) {
Assert.notNull(clientRepository, "clientRepository cannot be null");
this.clientRepository = clientRepository;
ClassLoader classLoader = JpaRegisteredClientRepository.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
this.objectMapper.registerModules(securityModules);
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
}
@Override
public void save(RegisteredClient registeredClient) {
Assert.notNull(registeredClient, "registeredClient cannot be null");
this.clientRepository.save(toEntity(registeredClient));
}
@Override
public RegisteredClient findById(String id) {
Assert.hasText(id, "id cannot be empty");
return this.clientRepository.findById(id).map(this::toObject).orElse(null);
}
@Override
public RegisteredClient findByClientId(String clientId) {
Assert.hasText(clientId, "clientId cannot be empty");
return this.clientRepository.findByClientId(clientId).map(this::toObject).orElse(null);
}
private RegisteredClient toObject(Client client) {
Set<String> clientAuthenticationMethods = StringUtils.commaDelimitedListToSet(
client.getClientAuthenticationMethods());
Set<String> authorizationGrantTypes = StringUtils.commaDelimitedListToSet(
client.getAuthorizationGrantTypes());
Set<String> redirectUris = StringUtils.commaDelimitedListToSet(
client.getRedirectUris());
Set<String> clientScopes = StringUtils.commaDelimitedListToSet(
client.getScopes());
RegisteredClient.Builder builder = RegisteredClient.withId(client.getId())
.clientId(client.getClientId())
.clientIdIssuedAt(client.getClientIdIssuedAt())
.clientSecret(client.getClientSecret())
.clientSecretExpiresAt(client.getClientSecretExpiresAt())
.clientName(client.getClientName())
.clientAuthenticationMethods(authenticationMethods ->
clientAuthenticationMethods.forEach(authenticationMethod ->
authenticationMethods.add(resolveClientAuthenticationMethod(authenticationMethod))))
.authorizationGrantTypes((grantTypes) ->
authorizationGrantTypes.forEach(grantType ->
grantTypes.add(resolveAuthorizationGrantType(grantType))))
.redirectUris((uris) -> uris.addAll(redirectUris))
.scopes((scopes) -> scopes.addAll(clientScopes));
Map<String, Object> clientSettingsMap = parseMap(client.getClientSettings());
builder.clientSettings(ClientSettings.withSettings(clientSettingsMap).build());
Map<String, Object> tokenSettingsMap = parseMap(client.getTokenSettings());
builder.tokenSettings(TokenSettings.withSettings(tokenSettingsMap).build());
return builder.build();
}
private Client toEntity(RegisteredClient registeredClient) {
List<String> clientAuthenticationMethods = new ArrayList<>(registeredClient.getClientAuthenticationMethods().size());
registeredClient.getClientAuthenticationMethods().forEach(clientAuthenticationMethod ->
clientAuthenticationMethods.add(clientAuthenticationMethod.getValue()));
List<String> authorizationGrantTypes = new ArrayList<>(registeredClient.getAuthorizationGrantTypes().size());
registeredClient.getAuthorizationGrantTypes().forEach(authorizationGrantType ->
authorizationGrantTypes.add(authorizationGrantType.getValue()));
Client entity = new Client();
entity.setId(registeredClient.getId());
entity.setClientId(registeredClient.getClientId());
entity.setClientIdIssuedAt(registeredClient.getClientIdIssuedAt());
entity.setClientSecret(registeredClient.getClientSecret());
entity.setClientSecretExpiresAt(registeredClient.getClientSecretExpiresAt());
entity.setClientName(registeredClient.getClientName());
entity.setClientAuthenticationMethods(StringUtils.collectionToCommaDelimitedString(clientAuthenticationMethods));
entity.setAuthorizationGrantTypes(StringUtils.collectionToCommaDelimitedString(authorizationGrantTypes));
entity.setRedirectUris(StringUtils.collectionToCommaDelimitedString(registeredClient.getRedirectUris()));
entity.setScopes(StringUtils.collectionToCommaDelimitedString(registeredClient.getScopes()));
entity.setClientSettings(writeMap(registeredClient.getClientSettings().getSettings()));
entity.setTokenSettings(writeMap(registeredClient.getTokenSettings().getSettings()));
return entity;
}
private Map<String, Object> parseMap(String data) {
try {
return this.objectMapper.readValue(data, new TypeReference<Map<String, Object>>() {
});
} catch (Exception ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
}
}
private String writeMap(Map<String, Object> data) {
try {
return this.objectMapper.writeValueAsString(data);
} catch (Exception ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
}
}
private static AuthorizationGrantType resolveAuthorizationGrantType(String authorizationGrantType) {
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.AUTHORIZATION_CODE;
} else if (AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.CLIENT_CREDENTIALS;
} else if (AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(authorizationGrantType)) {
return AuthorizationGrantType.REFRESH_TOKEN;
}
return new AuthorizationGrantType(authorizationGrantType); // Custom authorization grant type
}
private static ClientAuthenticationMethod resolveClientAuthenticationMethod(String clientAuthenticationMethod) {
if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue().equals(clientAuthenticationMethod)) {
return ClientAuthenticationMethod.CLIENT_SECRET_BASIC;
} else if (ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue().equals(clientAuthenticationMethod)) {
return ClientAuthenticationMethod.CLIENT_SECRET_POST;
} else if (ClientAuthenticationMethod.NONE.getValue().equals(clientAuthenticationMethod)) {
return ClientAuthenticationMethod.NONE;
}
return new ClientAuthenticationMethod(clientAuthenticationMethod); // Custom client authentication method
}
}

View File

@@ -0,0 +1,166 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.userinfo;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
@Configuration
public class EnableUserInfoSecurityConfig {
@Bean // <1>
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
// @formatter:off
http
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) // <2>
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
);
// @formatter:on
return http.build();
}
// @fold:on
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
// @formatter:on
return http.build();
}
// @fold:off
@Bean // <3>
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
// @fold:on
@Bean
public UserDetailsService userDetailsService() {
// @formatter:off
UserDetails userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
// @formatter:on
return new InMemoryUserDetailsManager(userDetails);
}
@Bean
public RegisteredClientRepository registeredClientRepository() {
// @formatter:off
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("messaging-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.ADDRESS)
.scope(OidcScopes.EMAIL)
.scope(OidcScopes.PHONE)
.scope(OidcScopes.PROFILE)
.scope("message.read")
.scope("message.write")
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();
// @formatter:on
return new InMemoryRegisteredClientRepository(registeredClient);
}
@Bean
public JWKSource<SecurityContext> jwkSource() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// @formatter:off
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
// @formatter:on
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet<>(jwkSet);
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
@Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder().build();
}
// @fold:off
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.userinfo.idtoken;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
@Configuration
public class IdTokenCustomizerConfig {
// @formatter:off
@Bean // <1>
public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer(
OidcUserInfoService userInfoService) {
return (context) -> {
if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) {
OidcUserInfo userInfo = userInfoService.loadUser( // <2>
context.getPrincipal().getName());
context.getClaims().claims(claims ->
claims.putAll(userInfo.getClaims()));
}
};
}
// @formatter:on
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.userinfo.idtoken;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.stereotype.Service;
/**
* Example service to perform lookup of user info for customizing an {@code id_token}.
*/
@Service
public class OidcUserInfoService {
private final UserInfoRepository userInfoRepository = new UserInfoRepository();
public OidcUserInfo loadUser(String username) {
return new OidcUserInfo(this.userInfoRepository.findByUsername(username));
}
static class UserInfoRepository {
private final Map<String, Map<String, Object>> userInfo = new HashMap<>();
public UserInfoRepository() {
this.userInfo.put("user1", createUser("user1"));
this.userInfo.put("user2", createUser("user2"));
}
public Map<String, Object> findByUsername(String username) {
return this.userInfo.get(username);
}
private static Map<String, Object> createUser(String username) {
return OidcUserInfo.builder()
.subject(username)
.name("First Last")
.givenName("First")
.familyName("Last")
.middleName("Middle")
.nickname("User")
.preferredUsername(username)
.profile("https://example.com/" + username)
.picture("https://example.com/" + username + ".jpg")
.website("https://example.com")
.email(username + "@example.com")
.emailVerified(true)
.gender("female")
.birthdate("1970-01-01")
.zoneinfo("Europe/Paris")
.locale("en-US")
.phoneNumber("+1 (604) 555-1234;ext=5678")
.phoneNumberVerified("false")
.claim("address", Collections.singletonMap("formatted", "Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance"))
.updatedAt("1970-01-01T00:00:00Z")
.build()
.getClaims();
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.userinfo.jwt;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
@Configuration
public class JwtTokenCustomizerConfig {
// @formatter:off
@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer() {
return (context) -> {
if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
context.getClaims().claims((claims) -> {
claims.put("claim-1", "value-1");
claims.put("claim-2", "value-2");
});
}
};
}
// @formatter:on
}

View File

@@ -0,0 +1,190 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.userinfo.jwt;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;
import java.util.function.Function;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.RequestMatcher;
@Configuration
public class JwtUserInfoMapperSecurityConfig {
@Bean // <1>
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
RequestMatcher endpointsMatcher = authorizationServerConfigurer
.getEndpointsMatcher();
Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper = (context) -> { // <2>
OidcUserInfoAuthenticationToken authentication = context.getAuthentication();
JwtAuthenticationToken principal = (JwtAuthenticationToken) authentication.getPrincipal();
return new OidcUserInfo(principal.getToken().getClaims());
};
// @formatter:off
authorizationServerConfigurer
.oidc((oidc) -> oidc
.userInfoEndpoint((userInfo) -> userInfo
.userInfoMapper(userInfoMapper) // <3>
)
);
http
.requestMatcher(endpointsMatcher)
.authorizeRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) // <4>
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
)
.apply(authorizationServerConfigurer); // <5>
// @formatter:on
return http.build();
}
// @fold:on
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
// @formatter:on
return http.build();
}
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
@Bean
public UserDetailsService userDetailsService() {
// @formatter:off
UserDetails userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
// @formatter:on
return new InMemoryUserDetailsManager(userDetails);
}
@Bean
public RegisteredClientRepository registeredClientRepository() {
// @formatter:off
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("messaging-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.scope(OidcScopes.OPENID)
.scope("message.read")
.scope("message.write")
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();
// @formatter:on
return new InMemoryRegisteredClientRepository(registeredClient);
}
@Bean
public JWKSource<SecurityContext> jwkSource() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// @formatter:off
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
// @formatter:on
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet<>(jwkSet);
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
@Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder().build();
}
// @fold:off
}

View File

@@ -0,0 +1,6 @@
server:
port: 9000
logging:
level:
org.springframework.security: trace

View File

@@ -0,0 +1,171 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Helper class that performs steps of the {@code authorization_code} flow using
* {@link MockMvc} for testing.
*
* @author Steve Riesenberg
*/
public class AuthorizationCodeGrantFlow {
private static final Pattern HIDDEN_STATE_INPUT_PATTERN = Pattern.compile(".+<input type=\"hidden\" name=\"state\" value=\"([^\"]+)\">.+");
private static final TypeReference<Map<String, Object>> TOKEN_RESPONSE_TYPE_REFERENCE = new TypeReference<Map<String, Object>>() {
};
private final MockMvc mockMvc;
private String username = "user";
private Set<String> scopes = new HashSet<>();
public AuthorizationCodeGrantFlow(MockMvc mockMvc) {
this.mockMvc = mockMvc;
}
public void setUsername(String username) {
this.username = username;
}
public void addScope(String scope) {
this.scopes.add(scope);
}
/**
* Perform the authorization request and obtain a state parameter.
*
* @param registeredClient The registered client
* @return The state parameter for submitting consent for authorization
*/
public String authorize(RegisteredClient registeredClient) throws Exception {
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.set(OAuth2ParameterNames.RESPONSE_TYPE, OAuth2AuthorizationResponseType.CODE.getValue());
parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next());
parameters.set(OAuth2ParameterNames.SCOPE,
StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " "));
parameters.set(OAuth2ParameterNames.STATE, "state");
MvcResult mvcResult = this.mockMvc.perform(get("/oauth2/authorize")
.params(parameters)
.with(user(this.username).roles("USER")))
.andExpect(status().isOk())
.andExpect(header().string("content-type", containsString(MediaType.TEXT_HTML_VALUE)))
.andReturn();
String responseHtml = mvcResult.getResponse().getContentAsString();
Matcher matcher = HIDDEN_STATE_INPUT_PATTERN.matcher(responseHtml);
return matcher.matches() ? matcher.group(1) : null;
}
/**
* Submit consent for the authorization request and obtain an authorization code.
*
* @param registeredClient The registered client
* @param state The state paramter from the authorization request
* @return An authorization code
*/
public String submitConsent(RegisteredClient registeredClient, String state) throws Exception {
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
parameters.set(OAuth2ParameterNames.STATE, state);
for (String scope : scopes) {
parameters.add(OAuth2ParameterNames.SCOPE, scope);
}
MvcResult mvcResult = this.mockMvc.perform(post("/oauth2/authorize")
.params(parameters)
.with(user(this.username).roles("USER")))
.andExpect(status().is3xxRedirection())
.andReturn();
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
assertThat(redirectedUrl).isNotNull();
assertThat(redirectedUrl).matches("http://127.0.0.1:8080/authorized\\?code=.{15,}&state=state");
String locationHeader = URLDecoder.decode(redirectedUrl, StandardCharsets.UTF_8.name());
UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build();
return uriComponents.getQueryParams().getFirst("code");
}
/**
* Exchange an authorization code for an access token.
*
* @param registeredClient The registered client
* @param authorizationCode The authorization code obtained from the authorization request
* @return The token response
*/
public Map<String, Object> getTokenResponse(RegisteredClient registeredClient, String authorizationCode) throws Exception {
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue());
parameters.set(OAuth2ParameterNames.CODE, authorizationCode);
parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next());
HttpHeaders basicAuth = new HttpHeaders();
basicAuth.setBasicAuth(registeredClient.getClientId(), "secret");
MvcResult mvcResult = this.mockMvc.perform(post("/oauth2/token")
.params(parameters)
.headers(basicAuth))
.andExpect(status().isOk())
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, containsString(MediaType.APPLICATION_JSON_VALUE)))
.andExpect(jsonPath("$.access_token").isNotEmpty())
.andExpect(jsonPath("$.token_type").isNotEmpty())
.andExpect(jsonPath("$.expires_in").isNotEmpty())
.andExpect(jsonPath("$.refresh_token").isNotEmpty())
.andExpect(jsonPath("$.scope").isNotEmpty())
.andExpect(jsonPath("$.id_token").isNotEmpty())
.andReturn();
ObjectMapper objectMapper = new ObjectMapper();
String responseJson = mvcResult.getResponse().getContentAsString();
return objectMapper.readValue(responseJson, TOKEN_RESPONSE_TYPE_REFERENCE);
}
}

View File

@@ -0,0 +1,144 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.gettingStarted;
import java.util.Map;
import org.assertj.core.api.ObjectAssert;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.AuthorizationCodeGrantFlow;
import sample.test.SpringTestContext;
import sample.test.SpringTestContextExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for the Getting Started section of the reference documentation.
*
* @author Steve Riesenberg
*/
@ExtendWith(SpringTestContextExtension.class)
public class SecurityConfigTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
private MockMvc mockMvc;
@Autowired
private RegisteredClientRepository registeredClientRepository;
@Autowired
private OAuth2AuthorizationService authorizationService;
@Autowired
private OAuth2AuthorizationConsentService authorizationConsentService;
@Test
public void oidcLoginWhenGettingStartedConfigUsedThenSuccess() throws Exception {
this.spring.register(AuthorizationServerConfig.class).autowire();
assertThat(this.registeredClientRepository).isInstanceOf(InMemoryRegisteredClientRepository.class);
assertThat(this.authorizationService).isInstanceOf(InMemoryOAuth2AuthorizationService.class);
assertThat(this.authorizationConsentService).isInstanceOf(InMemoryOAuth2AuthorizationConsentService.class);
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId("messaging-client");
assertThat(registeredClient).isNotNull();
AuthorizationCodeGrantFlow authorizationCodeGrantFlow = new AuthorizationCodeGrantFlow(this.mockMvc);
authorizationCodeGrantFlow.setUsername("user");
authorizationCodeGrantFlow.addScope("message.read");
authorizationCodeGrantFlow.addScope("message.write");
String state = authorizationCodeGrantFlow.authorize(registeredClient);
assertThatAuthorization(state, OAuth2ParameterNames.STATE).isNotNull();
assertThatAuthorization(state, null).isNotNull();
String authorizationCode = authorizationCodeGrantFlow.submitConsent(registeredClient, state);
assertThatAuthorization(authorizationCode, OAuth2ParameterNames.CODE).isNotNull();
assertThatAuthorization(authorizationCode, null).isNotNull();
Map<String, Object> tokenResponse = authorizationCodeGrantFlow.getTokenResponse(registeredClient, authorizationCode);
String accessToken = (String) tokenResponse.get(OAuth2ParameterNames.ACCESS_TOKEN);
assertThatAuthorization(accessToken, OAuth2ParameterNames.ACCESS_TOKEN).isNotNull();
assertThatAuthorization(accessToken, null).isNotNull();
String refreshToken = (String) tokenResponse.get(OAuth2ParameterNames.REFRESH_TOKEN);
assertThatAuthorization(refreshToken, OAuth2ParameterNames.REFRESH_TOKEN).isNotNull();
assertThatAuthorization(refreshToken, null).isNotNull();
String idToken = (String) tokenResponse.get(OidcParameterNames.ID_TOKEN);
assertThatAuthorization(idToken, OidcParameterNames.ID_TOKEN).isNull(); // id_token is not searchable
OAuth2Authorization authorization = findAuthorization(accessToken, OAuth2ParameterNames.ACCESS_TOKEN);
assertThat(authorization.getToken(idToken)).isNotNull();
String scopes = (String) tokenResponse.get(OAuth2ParameterNames.SCOPE);
OAuth2AuthorizationConsent authorizationConsent = this.authorizationConsentService.findById(
registeredClient.getId(), "user");
assertThat(authorizationConsent).isNotNull();
assertThat(authorizationConsent.getScopes()).containsExactlyInAnyOrder(
StringUtils.delimitedListToStringArray(scopes, " "));
}
private ObjectAssert<OAuth2Authorization> assertThatAuthorization(String token, String tokenType) {
return assertThat(findAuthorization(token, tokenType));
}
private OAuth2Authorization findAuthorization(String token, String tokenType) {
return this.authorizationService.findByToken(token, tokenType == null ? null : new OAuth2TokenType(tokenType));
}
@EnableWebSecurity
@EnableAutoConfiguration
@ComponentScan
@Import(OAuth2AuthorizationServerConfiguration.class)
static class AuthorizationServerConfig extends SecurityConfig {
@Bean
public OAuth2AuthorizationService authorizationService() {
return new InMemoryOAuth2AuthorizationService();
}
@Bean
public OAuth2AuthorizationConsentService authorizationConsentService() {
return new InMemoryOAuth2AuthorizationConsentService();
}
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jose;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.UUID;
import javax.crypto.SecretKey;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jose.jwk.OctetSequenceKey;
import com.nimbusds.jose.jwk.RSAKey;
/**
* @author Joe Grandja
*/
public final class TestJwks {
private static final KeyPairGenerator rsaKeyPairGenerator;
static {
try {
rsaKeyPairGenerator = KeyPairGenerator.getInstance("RSA");
rsaKeyPairGenerator.initialize(2048);
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
// @formatter:off
public static final RSAKey DEFAULT_RSA_JWK =
jwk(
TestKeys.DEFAULT_PUBLIC_KEY,
TestKeys.DEFAULT_PRIVATE_KEY
).build();
// @formatter:on
// @formatter:off
public static final ECKey DEFAULT_EC_JWK =
jwk(
(ECPublicKey) TestKeys.DEFAULT_EC_KEY_PAIR.getPublic(),
(ECPrivateKey) TestKeys.DEFAULT_EC_KEY_PAIR.getPrivate()
).build();
// @formatter:on
// @formatter:off
public static final OctetSequenceKey DEFAULT_SECRET_JWK =
jwk(
TestKeys.DEFAULT_SECRET_KEY
).build();
// @formatter:on
private TestJwks() {
}
public static RSAKey.Builder generateRsaJwk() {
KeyPair keyPair = rsaKeyPairGenerator.generateKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// @formatter:off
return jwk(publicKey, privateKey)
.keyID(UUID.randomUUID().toString());
// @formatter:on
}
public static RSAKey.Builder jwk(RSAPublicKey publicKey, RSAPrivateKey privateKey) {
// @formatter:off
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyUse(KeyUse.SIGNATURE)
.keyID("rsa-jwk-kid");
// @formatter:on
}
public static ECKey.Builder jwk(ECPublicKey publicKey, ECPrivateKey privateKey) {
// @formatter:off
Curve curve = Curve.forECParameterSpec(publicKey.getParams());
return new ECKey.Builder(curve, publicKey)
.privateKey(privateKey)
.keyUse(KeyUse.SIGNATURE)
.keyID("ec-jwk-kid");
// @formatter:on
}
public static OctetSequenceKey.Builder jwk(SecretKey secretKey) {
// @formatter:off
return new OctetSequenceKey.Builder(secretKey)
.keyUse(KeyUse.SIGNATURE)
.keyID("secret-jwk-kid");
// @formatter:on
}
}

View File

@@ -0,0 +1,154 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jose;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.ECFieldFp;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.EllipticCurve;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* TODO
* This class is a straight copy from Spring Security.
* It should be removed when merging this codebase into Spring Security.
*
* @author Joe Grandja
* @since 5.2
*/
public final class TestKeys {
public static final KeyFactory kf;
static {
try {
kf = KeyFactory.getInstance("RSA");
}
catch (NoSuchAlgorithmException ex) {
throw new IllegalStateException(ex);
}
}
public static final String DEFAULT_ENCODED_SECRET_KEY = "bCzY/M48bbkwBEWjmNSIEPfwApcvXOnkCxORBEbPr+4=";
public static final SecretKey DEFAULT_SECRET_KEY = new SecretKeySpec(
Base64.getDecoder().decode(DEFAULT_ENCODED_SECRET_KEY), "AES");
// @formatter:off
public static final String DEFAULT_RSA_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3FlqJr5TRskIQIgdE3Dd"
+ "7D9lboWdcTUT8a+fJR7MAvQm7XXNoYkm3v7MQL1NYtDvL2l8CAnc0WdSTINU6IRv"
+ "c5Kqo2Q4csNX9SHOmEfzoROjQqahEcve1jBXluoCXdYuYpx4/1tfRgG6ii4Uhxh6"
+ "iI8qNMJQX+fLfqhbfYfxBQVRPywBkAbIP4x1EAsbC6FSNmkhCxiMNqEgxaIpY8C2"
+ "kJdJ/ZIV+WW4noDdzpKqHcwmB8FsrumlVY/DNVvUSDIipiq9PbP4H99TXN1o746o"
+ "RaNa07rq1hoCgMSSy+85SagCoxlmyE+D+of9SsMY8Ol9t0rdzpobBuhyJ/o5dfvj"
+ "KwIDAQAB";
// @formatter:on
public static final RSAPublicKey DEFAULT_PUBLIC_KEY;
static {
X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.getDecoder().decode(DEFAULT_RSA_PUBLIC_KEY));
try {
DEFAULT_PUBLIC_KEY = (RSAPublicKey) kf.generatePublic(spec);
}
catch (InvalidKeySpecException ex) {
throw new IllegalArgumentException(ex);
}
}
// @formatter:off
public static final String DEFAULT_RSA_PRIVATE_KEY = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDcWWomvlNGyQhA"
+ "iB0TcN3sP2VuhZ1xNRPxr58lHswC9Cbtdc2hiSbe/sxAvU1i0O8vaXwICdzRZ1JM"
+ "g1TohG9zkqqjZDhyw1f1Ic6YR/OhE6NCpqERy97WMFeW6gJd1i5inHj/W19GAbqK"
+ "LhSHGHqIjyo0wlBf58t+qFt9h/EFBVE/LAGQBsg/jHUQCxsLoVI2aSELGIw2oSDF"
+ "oiljwLaQl0n9khX5ZbiegN3OkqodzCYHwWyu6aVVj8M1W9RIMiKmKr09s/gf31Nc"
+ "3WjvjqhFo1rTuurWGgKAxJLL7zlJqAKjGWbIT4P6h/1Kwxjw6X23St3OmhsG6HIn"
+ "+jl1++MrAgMBAAECggEBAMf820wop3pyUOwI3aLcaH7YFx5VZMzvqJdNlvpg1jbE"
+ "E2Sn66b1zPLNfOIxLcBG8x8r9Ody1Bi2Vsqc0/5o3KKfdgHvnxAB3Z3dPh2WCDek"
+ "lCOVClEVoLzziTuuTdGO5/CWJXdWHcVzIjPxmK34eJXioiLaTYqN3XKqKMdpD0ZG"
+ "mtNTGvGf+9fQ4i94t0WqIxpMpGt7NM4RHy3+Onggev0zLiDANC23mWrTsUgect/7"
+ "62TYg8g1bKwLAb9wCBT+BiOuCc2wrArRLOJgUkj/F4/gtrR9ima34SvWUyoUaKA0"
+ "bi4YBX9l8oJwFGHbU9uFGEMnH0T/V0KtIB7qetReywkCgYEA9cFyfBIQrYISV/OA"
+ "+Z0bo3vh2aL0QgKrSXZ924cLt7itQAHNZ2ya+e3JRlTczi5mnWfjPWZ6eJB/8MlH"
+ "Gpn12o/POEkU+XjZZSPe1RWGt5g0S3lWqyx9toCS9ACXcN9tGbaqcFSVI73zVTRA"
+ "8J9grR0fbGn7jaTlTX2tnlOTQ60CgYEA5YjYpEq4L8UUMFkuj+BsS3u0oEBnzuHd"
+ "I9LEHmN+CMPosvabQu5wkJXLuqo2TxRnAznsA8R3pCLkdPGoWMCiWRAsCn979TdY"
+ "QbqO2qvBAD2Q19GtY7lIu6C35/enQWzJUMQE3WW0OvjLzZ0l/9mA2FBRR+3F9A1d"
+ "rBdnmv0c3TcCgYEAi2i+ggVZcqPbtgrLOk5WVGo9F1GqUBvlgNn30WWNTx4zIaEk"
+ "HSxtyaOLTxtq2odV7Kr3LGiKxwPpn/T+Ief+oIp92YcTn+VfJVGw4Z3BezqbR8lA"
+ "Uf/+HF5ZfpMrVXtZD4Igs3I33Duv4sCuqhEvLWTc44pHifVloozNxYfRfU0CgYBN"
+ "HXa7a6cJ1Yp829l62QlJKtx6Ymj95oAnQu5Ez2ROiZMqXRO4nucOjGUP55Orac1a"
+ "FiGm+mC/skFS0MWgW8evaHGDbWU180wheQ35hW6oKAb7myRHtr4q20ouEtQMdQIF"
+ "snV39G1iyqeeAsf7dxWElydXpRi2b68i3BIgzhzebQKBgQCdUQuTsqV9y/JFpu6H"
+ "c5TVvhG/ubfBspI5DhQqIGijnVBzFT//UfIYMSKJo75qqBEyP2EJSmCsunWsAFsM"
+ "TszuiGTkrKcZy9G0wJqPztZZl2F2+bJgnA6nBEV7g5PA4Af+QSmaIhRwqGDAuROR"
+ "47jndeyIaMTNETEmOnms+as17g==";
// @formatter:on
public static final RSAPrivateKey DEFAULT_PRIVATE_KEY;
static {
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(DEFAULT_RSA_PRIVATE_KEY));
try {
DEFAULT_PRIVATE_KEY = (RSAPrivateKey) kf.generatePrivate(spec);
}
catch (InvalidKeySpecException ex) {
throw new IllegalArgumentException(ex);
}
}
public static final KeyPair DEFAULT_RSA_KEY_PAIR = new KeyPair(DEFAULT_PUBLIC_KEY, DEFAULT_PRIVATE_KEY);
public static final KeyPair DEFAULT_EC_KEY_PAIR = generateEcKeyPair();
static KeyPair generateEcKeyPair() {
EllipticCurve ellipticCurve = new EllipticCurve(
new ECFieldFp(new BigInteger(
"115792089210356248762697446949407573530086143415290314195533631308867097853951")),
new BigInteger("115792089210356248762697446949407573530086143415290314195533631308867097853948"),
new BigInteger("41058363725152142129326129780047268409114441015993725554835256314039467401291"));
ECPoint ecPoint = new ECPoint(
new BigInteger("48439561293906451759052585252797914202762949526041747995844080717082404635286"),
new BigInteger("36134250956749795798585127919587881956611106672985015071877198253568414405109"));
ECParameterSpec ecParameterSpec = new ECParameterSpec(ellipticCurve, ecPoint,
new BigInteger("115792089210356248762697446949407573529996955224135760342422259061068512044369"), 1);
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
keyPairGenerator.initialize(ecParameterSpec);
keyPair = keyPairGenerator.generateKeyPair();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
private TestKeys() {
}
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jpa;
import java.util.Map;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.assertj.core.api.ObjectAssert;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.AuthorizationCodeGrantFlow;
import sample.jose.TestJwks;
import sample.jpa.service.authorization.JpaOAuth2AuthorizationService;
import sample.jpa.service.authorizationConsent.JpaOAuth2AuthorizationConsentService;
import sample.jpa.service.client.JpaRegisteredClientRepository;
import sample.test.SpringTestContext;
import sample.test.SpringTestContextExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static sample.util.RegisteredClients.messagingClient;
/**
* Tests for the guide How-to: Implement core services with JPA.
*
* @author Steve Riesenberg
*/
@ExtendWith(SpringTestContextExtension.class)
public class JpaTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
private MockMvc mockMvc;
@Autowired
private RegisteredClientRepository registeredClientRepository;
@Autowired
private OAuth2AuthorizationService authorizationService;
@Autowired
private OAuth2AuthorizationConsentService authorizationConsentService;
@Test
public void oidcLoginWhenJpaCoreServicesAutowiredThenUsed() throws Exception {
this.spring.register(AuthorizationServerConfig.class).autowire();
assertThat(this.registeredClientRepository).isInstanceOf(JpaRegisteredClientRepository.class);
assertThat(this.authorizationService).isInstanceOf(JpaOAuth2AuthorizationService.class);
assertThat(this.authorizationConsentService).isInstanceOf(JpaOAuth2AuthorizationConsentService.class);
RegisteredClient registeredClient = messagingClient();
this.registeredClientRepository.save(registeredClient);
AuthorizationCodeGrantFlow authorizationCodeGrantFlow = new AuthorizationCodeGrantFlow(this.mockMvc);
authorizationCodeGrantFlow.setUsername("user");
authorizationCodeGrantFlow.addScope("message.read");
authorizationCodeGrantFlow.addScope("message.write");
String state = authorizationCodeGrantFlow.authorize(registeredClient);
assertThatAuthorization(state, OAuth2ParameterNames.STATE).isNotNull();
assertThatAuthorization(state, null).isNotNull();
String authorizationCode = authorizationCodeGrantFlow.submitConsent(registeredClient, state);
assertThatAuthorization(authorizationCode, OAuth2ParameterNames.CODE).isNotNull();
assertThatAuthorization(authorizationCode, null).isNotNull();
Map<String, Object> tokenResponse = authorizationCodeGrantFlow.getTokenResponse(registeredClient, authorizationCode);
String accessToken = (String) tokenResponse.get(OAuth2ParameterNames.ACCESS_TOKEN);
assertThatAuthorization(accessToken, OAuth2ParameterNames.ACCESS_TOKEN).isNotNull();
assertThatAuthorization(accessToken, null).isNotNull();
String refreshToken = (String) tokenResponse.get(OAuth2ParameterNames.REFRESH_TOKEN);
assertThatAuthorization(refreshToken, OAuth2ParameterNames.REFRESH_TOKEN).isNotNull();
assertThatAuthorization(refreshToken, null).isNotNull();
String idToken = (String) tokenResponse.get(OidcParameterNames.ID_TOKEN);
assertThatAuthorization(idToken, OidcParameterNames.ID_TOKEN).isNull(); // id_token is not searchable
OAuth2Authorization authorization = findAuthorization(accessToken, OAuth2ParameterNames.ACCESS_TOKEN);
assertThat(authorization.getToken(idToken)).isNotNull();
String scopes = (String) tokenResponse.get(OAuth2ParameterNames.SCOPE);
OAuth2AuthorizationConsent authorizationConsent = this.authorizationConsentService.findById(
registeredClient.getId(), "user");
assertThat(authorizationConsent).isNotNull();
assertThat(authorizationConsent.getScopes()).containsExactlyInAnyOrder(
StringUtils.delimitedListToStringArray(scopes, " "));
}
private ObjectAssert<OAuth2Authorization> assertThatAuthorization(String token, String tokenType) {
return assertThat(findAuthorization(token, tokenType));
}
private OAuth2Authorization findAuthorization(String token, String tokenType) {
return this.authorizationService.findByToken(token, tokenType == null ? null : new OAuth2TokenType(tokenType));
}
@EnableWebSecurity
@EnableAutoConfiguration
@ComponentScan
@Import(OAuth2AuthorizationServerConfiguration.class)
static class AuthorizationServerConfig {
@Bean
public JWKSource<SecurityContext> jwkSource() {
JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
return new ImmutableJWKSet<>(jwkSet);
}
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
}
}

View File

@@ -0,0 +1,162 @@
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.test;
import java.io.Closeable;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.mock.web.MockServletConfig;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.config.BeanIds;
import org.springframework.test.context.web.GenericXmlWebContextLoader;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.filter.OncePerRequestFilter;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
/**
* TODO
* This class is a straight copy from Spring Security.
*
* @author Rob Winch
* @since 5.0
*/
public class SpringTestContext implements Closeable {
private Object test;
private ConfigurableWebApplicationContext context;
private List<Filter> filters = new ArrayList<>();
public SpringTestContext(Object test) {
setTest(test);
}
public void setTest(Object test) {
this.test = test;
}
@Override
public void close() {
try {
this.context.close();
}
catch (Exception ex) {
}
}
public SpringTestContext context(ConfigurableWebApplicationContext context) {
this.context = context;
return this;
}
public SpringTestContext register(Class<?>... classes) {
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(classes);
this.context = applicationContext;
return this;
}
public SpringTestContext testConfigLocations(String... configLocations) {
GenericXmlWebContextLoader loader = new GenericXmlWebContextLoader();
String[] locations = loader.processLocations(this.test.getClass(), configLocations);
return configLocations(locations);
}
public SpringTestContext configLocations(String... configLocations) {
XmlWebApplicationContext context = new XmlWebApplicationContext();
context.setConfigLocations(configLocations);
this.context = context;
return this;
}
// public SpringTestContext context(String configuration) {
// InMemoryXmlWebApplicationContext context = new InMemoryXmlWebApplicationContext(configuration);
// this.context = context;
// return this;
// }
public SpringTestContext mockMvcAfterSpringSecurityOk() {
return addFilter(new OncePerRequestFilter() {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) {
response.setStatus(HttpServletResponse.SC_OK);
}
});
}
private SpringTestContext addFilter(Filter filter) {
this.filters.add(filter);
return this;
}
public ConfigurableWebApplicationContext getContext() {
if (!this.context.isRunning()) {
this.context.setServletContext(new MockServletContext());
this.context.setServletConfig(new MockServletConfig());
this.context.refresh();
}
return this.context;
}
public void autowire() {
this.context.setServletContext(new MockServletContext());
this.context.setServletConfig(new MockServletConfig());
this.context.refresh();
if (this.context.containsBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN)) {
// @formatter:off
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).
apply(springSecurity())
.apply(new AddFilter())
.build();
// @formatter:on
this.context.getBeanFactory().registerResolvableDependency(MockMvc.class, mockMvc);
}
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(this.context.getBeanFactory());
bpp.processInjection(this.test);
}
private class AddFilter implements MockMvcConfigurer {
@Override
public RequestPostProcessor beforeMockMvcCreated(ConfigurableMockMvcBuilder<?> builder,
WebApplicationContext context) {
builder.addFilters(SpringTestContext.this.filters.toArray(new Filter[0]));
return null;
}
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.test;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.springframework.security.test.context.TestSecurityContextHolder;
/**
* TODO
* This class is a straight copy from Spring Security.
*/
public class SpringTestContextExtension implements BeforeEachCallback, AfterEachCallback {
@Override
public void afterEach(ExtensionContext context) throws Exception {
TestSecurityContextHolder.clearContext();
getContexts(context.getRequiredTestInstance()).forEach((springTestContext) -> springTestContext.close());
}
@Override
public void beforeEach(ExtensionContext context) throws Exception {
Object testInstance = context.getRequiredTestInstance();
getContexts(testInstance).forEach((springTestContext) -> springTestContext.setTest(testInstance));
}
private static List<SpringTestContext> getContexts(Object test) throws IllegalAccessException {
Field[] declaredFields = test.getClass().getDeclaredFields();
List<SpringTestContext> result = new ArrayList<>();
for (Field field : declaredFields) {
if (SpringTestContext.class.isAssignableFrom(field.getType())) {
result.add((SpringTestContext) field.get(test));
}
}
return result;
}
}

View File

@@ -0,0 +1,189 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.userinfo;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sample.AuthorizationCodeGrantFlow;
import sample.test.SpringTestContext;
import sample.test.SpringTestContextExtension;
import sample.userinfo.idtoken.IdTokenCustomizerConfig;
import sample.userinfo.idtoken.OidcUserInfoService;
import sample.userinfo.jwt.JwtTokenCustomizerConfig;
import sample.userinfo.jwt.JwtUserInfoMapperSecurityConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.test.web.servlet.MockMvc;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for the guide How-to: Customize the OpenID Connect 1.0 UserInfo response.
*
* @author Steve Riesenberg
*/
@ExtendWith(SpringTestContextExtension.class)
public class EnableUserInfoSecurityConfigTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
private MockMvc mockMvc;
@Autowired
private RegisteredClientRepository registeredClientRepository;
@Test
public void userInfoWhenEnabledThenSuccess() throws Exception {
this.spring.register(AuthorizationServerConfig.class).autowire();
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId("messaging-client");
assertThat(registeredClient).isNotNull();
AuthorizationCodeGrantFlow authorizationCodeGrantFlow = new AuthorizationCodeGrantFlow(this.mockMvc);
authorizationCodeGrantFlow.setUsername("user1");
authorizationCodeGrantFlow.addScope("message.read");
authorizationCodeGrantFlow.addScope("message.write");
String state = authorizationCodeGrantFlow.authorize(registeredClient);
String authorizationCode = authorizationCodeGrantFlow.submitConsent(registeredClient, state);
Map<String, Object> tokenResponse = authorizationCodeGrantFlow.getTokenResponse(registeredClient, authorizationCode);
String accessToken = (String) tokenResponse.get(OAuth2ParameterNames.ACCESS_TOKEN);
this.mockMvc.perform(get("/userinfo")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken))
.andExpect(status().isOk())
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON_VALUE)))
.andExpect(jsonPath("sub").value("user1"));
}
@Test
public void userInfoWhenIdTokenCustomizerThenIdTokenClaimsMappedToResponse() throws Exception {
this.spring.register(AuthorizationServerConfigWithIdTokenCustomizer.class).autowire();
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId("messaging-client");
assertThat(registeredClient).isNotNull();
AuthorizationCodeGrantFlow authorizationCodeGrantFlow = new AuthorizationCodeGrantFlow(this.mockMvc);
authorizationCodeGrantFlow.setUsername("user1");
authorizationCodeGrantFlow.addScope(OidcScopes.ADDRESS);
authorizationCodeGrantFlow.addScope(OidcScopes.EMAIL);
authorizationCodeGrantFlow.addScope(OidcScopes.PHONE);
authorizationCodeGrantFlow.addScope(OidcScopes.PROFILE);
String state = authorizationCodeGrantFlow.authorize(registeredClient);
String authorizationCode = authorizationCodeGrantFlow.submitConsent(registeredClient, state);
Map<String, Object> tokenResponse = authorizationCodeGrantFlow.getTokenResponse(registeredClient, authorizationCode);
String accessToken = (String) tokenResponse.get(OAuth2ParameterNames.ACCESS_TOKEN);
this.mockMvc.perform(get("/userinfo")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken))
.andExpect(status().isOk())
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON_VALUE)))
.andExpectAll(
jsonPath("sub").value("user1"),
jsonPath("name").value("First Last"),
jsonPath("given_name").value("First"),
jsonPath("family_name").value("Last"),
jsonPath("middle_name").value("Middle"),
jsonPath("nickname").value("User"),
jsonPath("preferred_username").value("user1"),
jsonPath("profile").value("https://example.com/user1"),
jsonPath("picture").value("https://example.com/user1.jpg"),
jsonPath("website").value("https://example.com"),
jsonPath("email").value("user1@example.com"),
jsonPath("email_verified").value("true"),
jsonPath("gender").value("female"),
jsonPath("birthdate").value("1970-01-01"),
jsonPath("zoneinfo").value("Europe/Paris"),
jsonPath("locale").value("en-US"),
jsonPath("phone_number").value("+1 (604) 555-1234;ext=5678"),
jsonPath("phone_number_verified").value("false"),
jsonPath("address.formatted").value("Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance"),
jsonPath("updated_at").value("1970-01-01T00:00:00Z")
);
}
@Test
public void userInfoWhenUserInfoMapperThenClaimsMappedToResponse() throws Exception {
this.spring.register(AuthorizationServerConfigWithJwtTokenCustomizer.class).autowire();
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId("messaging-client");
assertThat(registeredClient).isNotNull();
AuthorizationCodeGrantFlow authorizationCodeGrantFlow = new AuthorizationCodeGrantFlow(this.mockMvc);
authorizationCodeGrantFlow.setUsername("user1");
authorizationCodeGrantFlow.addScope("message.read");
authorizationCodeGrantFlow.addScope("message.write");
String state = authorizationCodeGrantFlow.authorize(registeredClient);
String authorizationCode = authorizationCodeGrantFlow.submitConsent(registeredClient, state);
Map<String, Object> tokenResponse = authorizationCodeGrantFlow.getTokenResponse(registeredClient, authorizationCode);
String accessToken = (String) tokenResponse.get(OAuth2ParameterNames.ACCESS_TOKEN);
this.mockMvc.perform(get("/userinfo")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken))
.andExpect(status().isOk())
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON_VALUE)))
.andExpectAll(
jsonPath("sub").value("user1"),
jsonPath("claim-1").value("value-1"),
jsonPath("claim-2").value("value-2")
);
}
@EnableWebSecurity
@EnableAutoConfiguration
@Import(EnableUserInfoSecurityConfig.class)
static class AuthorizationServerConfig {
}
@EnableWebSecurity
@Import({EnableUserInfoSecurityConfig.class, IdTokenCustomizerConfig.class})
static class AuthorizationServerConfigWithIdTokenCustomizer {
@Bean
public OidcUserInfoService userInfoService() {
return new OidcUserInfoService();
}
}
@EnableWebSecurity
@EnableAutoConfiguration
@Import({JwtUserInfoMapperSecurityConfig.class, JwtTokenCustomizerConfig.class})
static class AuthorizationServerConfigWithJwtTokenCustomizer {
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.util;
import java.util.UUID;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
/**
* @author Steve Riesenberg
*/
public class RegisteredClients {
// @formatter:off
public static RegisteredClient messagingClient() {
return RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("messaging-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://127.0.0.1:8080/authorized")
.scope(OidcScopes.OPENID)
.scope("message.read")
.scope("message.write")
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
.build();
}
// @formatter:on
}

View File

@@ -0,0 +1,23 @@
[[getting-help]]
= Getting Help
[[community]]
== Community
Welcome to the https://docs.spring.io/spring-security/reference/community.html[Spring Security Community].
Spring Authorization Server is an open source project led by the Spring Security team.
If you need help with Spring Authorization Server, we are here to help.
[[resources]]
== Resources
The following are some of the best ways to get help:
* Try the xref:how-to.adoc[How-to guides]. They provide solutions to the most common questions.
* Learn the Spring Security basics that Spring Authorization Server builds on. If you are starting out with Spring Security, check the https://spring.io/projects/spring-security#learn[reference documentation] or try one of the https://github.com/spring-projects/spring-security-samples[samples].
* Read through xref:index.adoc[this documentation].
* Try one of our many https://github.com/spring-projects/spring-authorization-server/tree/main/samples[sample applications].
* Ask a question on Stack Overflow with the https://stackoverflow.com/questions/tagged/spring-security[`spring-security`] tag.
* Report bugs and enhancement requests on https://github.com/spring-projects/spring-authorization-server/issues[GitHub].
NOTE: Spring Authorization Server is open source, including the documentation. If you find problems with the docs or if you want to improve them, please https://github.com/spring-projects/spring-authorization-server[get involved].

View File

@@ -0,0 +1,58 @@
[[getting-started]]
= Getting Started
If you are just getting started with Spring Authorization Server, the following sections walk you through creating your first application.
[[system-requirements]]
== System Requirements
Spring Authorization Server requires a Java 8 or higher Runtime Environment.
[[installing-spring-authorization-server]]
== Installing Spring Authorization Server
Spring Authorization Server can be used anywhere you already use https://docs.spring.io/spring-security/reference/prerequisites.html[Spring Security].
The easiest way to begin using Spring Authorization Server is by creating a https://spring.io/projects/spring-boot[Spring Boot]-based application.
You can use https://start.spring.io[start.spring.io] to generate a basic project or use the https://github.com/spring-projects/spring-authorization-server/tree/main/samples/default-authorizationserver[default authorization server sample] as a guide.
Then add Spring Authorization Server as a dependency, as in the following example:
[[maven-dependency]]
.Maven
[source,xml,role="primary",subs="attributes,verbatim"]
----
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
<version>{spring-authorization-server-version}</version>
</dependency>
----
[[gradle-dependency]]
.Gradle
[source,gradle,role="secondary",subs="attributes,verbatim"]
----
implementation "org.springframework.security:spring-security-oauth2-authorization-server:{spring-authorization-server-version}"
----
TIP: See https://docs.spring.io/spring-boot/docs/current/reference/html/getting-started.html#getting-started.installing[Installing Spring Boot] for more information on using Spring Boot with Maven or Gradle.
[[developing-your-first-application]]
== Developing Your First Application
To get started, you need the minimum required components defined as a `@Bean` in a Spring `@Configuration`. These components can be defined as follows:
TIP: To skip the setup and run a working example, see the https://github.com/spring-projects/spring-authorization-server/tree/main/samples/default-authorizationserver[default authorization server sample].
[[sample.gettingStarted]]
include::code:SecurityConfig[]
This is a minimal configuration for getting started quickly. To understand what each component is used for, see the following descriptions:
<1> A Spring Security filter chain for the xref:protocol-endpoints.adoc[Protocol Endpoints].
<2> A Spring Security filter chain for https://docs.spring.io/spring-security/reference/servlet/authentication/index.html[authentication].
<3> An instance of {spring-security-api-base-url}/org/springframework/security/core/userdetails/UserDetailsService.html[`UserDetailsService`] for retrieving users to authenticate.
<4> An instance of xref:core-model-components.adoc#registered-client-repository[`RegisteredClientRepository`] for managing clients.
<5> An instance of `com.nimbusds.jose.jwk.source.JWKSource` for signing access tokens.
<6> An instance of `java.security.KeyPair` with keys generated on startup used to create the `JWKSource` above.
<7> An instance of xref:configuration-model#configuring-provider-settings[`ProviderSettings`] to configure Spring Authorization Server.

View File

@@ -0,0 +1,233 @@
[[how-to-jpa]]
= How-to: Implement core services with JPA
:index-link: ../how-to.html
:docs-dir: ..
This guide shows how to implement the xref:{docs-dir}/core-model-components.adoc#core-model-components[core services] of xref:{docs-dir}/index.adoc#top[Spring Authorization Server] with JPA.
The purpose of this guide is to provide a starting point for implementing these services yourself, with the intention that you can make modifications to suit your needs.
* <<define-data-model>>
* <<create-jpa-entities>>
* <<create-spring-data-repositories>>
* <<implement-core-services>>
[[define-data-model]]
== Define the data model
This guide provides a starting point for the data model and uses the simplest possible structure and data types.
To come up with the initial schema, we begin by reviewing the xref:{docs-dir}/core-model-components.adoc#core-model-components[domain objects] used by the core services.
[NOTE]
Except for token, state, metadata, settings, and claims values, we use the JPA default column length of 255 for all columns.
In reality, the length and even type of columns you use may need to be customized.
You are encouraged to experiment and test before deploying to production.
* <<client-schema>>
* <<authorization-schema>>
* <<authorization-consent-schema>>
[[client-schema]]
=== Client Schema
The xref:{docs-dir}/core-model-components.adoc#registered-client[`RegisteredClient`] domain object contains a few multi-valued fields and some settings fields that require storing arbitrary key/value data.
The following listing shows the `client` schema.
.Client Schema
[source,sql]
----
CREATE TABLE client (
id varchar(255) NOT NULL,
clientId varchar(255) NOT NULL,
clientIdIssuedAt timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
clientSecret varchar(255) DEFAULT NULL,
clientSecretExpiresAt timestamp DEFAULT NULL,
clientName varchar(255) NOT NULL,
clientAuthenticationMethods varchar(1000) NOT NULL,
authorizationGrantTypes varchar(1000) NOT NULL,
redirectUris varchar(1000) DEFAULT NULL,
scopes varchar(1000) NOT NULL,
clientSettings varchar(2000) NOT NULL,
tokenSettings varchar(2000) NOT NULL,
PRIMARY KEY (id)
);
----
[[authorization-schema]]
=== Authorization Schema
The xref:{docs-dir}/core-model-components.adoc#oauth2-authorization[`OAuth2Authorization`] domain object is more complex and contains several multi-valued fields as well as numerous arbitrarily long token values, metadata, settings and claims values.
The built-in JDBC implementation utilizes a flattened structure that prefers performance over normalization, which we adopt here as well.
[CAUTION]
It has been difficult to find a flattened database schema that works well in all cases and with all database vendors.
You may need to normalize or heavily alter the following schema for your needs.
The following listing shows the `authorization` schema.
.Authorization Schema
[source,sql]
----
CREATE TABLE authorization (
id varchar(255) NOT NULL,
registeredClientId varchar(255) NOT NULL,
principalName varchar(255) NOT NULL,
authorizationGrantType varchar(255) NOT NULL,
attributes varchar(4000) DEFAULT NULL,
state varchar(500) DEFAULT NULL,
authorizationCodeValue varchar(4000) DEFAULT NULL,
authorizationCodeIssuedAt timestamp DEFAULT NULL,
authorizationCodeExpiresAt timestamp DEFAULT NULL,
authorizationCodeMetadata varchar(2000) DEFAULT NULL,
accessTokenValue varchar(4000) DEFAULT NULL,
accessTokenIssuedAt timestamp DEFAULT NULL,
accessTokenExpiresAt timestamp DEFAULT NULL,
accessTokenMetadata varchar(2000) DEFAULT NULL,
accessTokenType varchar(255) DEFAULT NULL,
accessTokenScopes varchar(1000) DEFAULT NULL,
refreshTokenValue varchar(4000) DEFAULT NULL,
refreshTokenIssuedAt timestamp DEFAULT NULL,
refreshTokenExpiresAt timestamp DEFAULT NULL,
refreshTokenMetadata varchar(2000) DEFAULT NULL,
oidcIdTokenValue varchar(4000) DEFAULT NULL,
oidcIdTokenIssuedAt timestamp DEFAULT NULL,
oidcIdTokenExpiresAt timestamp DEFAULT NULL,
oidcIdTokenMetadata varchar(2000) DEFAULT NULL,
oidcIdTokenClaims varchar(2000) DEFAULT NULL,
PRIMARY KEY (id)
);
----
[[authorization-consent-schema]]
=== Authorization Consent Schema
The xref:{docs-dir}/core-model-components.adoc#oauth2-authorization-consent[`OAuth2AuthorizationConsent`] domain object is the simplest to model and contains only a single multi-valued field in addition to a composite key.
The following listing shows the `authorizationConsent` schema.
.Authorization Consent Schema
[source,sql]
----
CREATE TABLE authorizationConsent (
registeredClientId varchar(255) NOT NULL,
principalName varchar(255) NOT NULL,
authorities varchar(1000) NOT NULL,
PRIMARY KEY (registeredClientId, principalName)
);
----
[[create-jpa-entities]]
== Create JPA entities
The preceding schema examples provide a reference for the structure of the entities we need to create.
[NOTE]
The following entities are minimally annotated and are just examples.
They allow the schema to be created dynamically and therefore do not require the above sql scripts to be executed manually.
* <<client-entity>>
* <<authorization-entity>>
* <<authorization-consent-entity>>
[[client-entity]]
=== Client Entity
The following listing shows the `Client` entity, which is used to persist information mapped from the xref:{docs-dir}/core-model-components.adoc#registered-client[`RegisteredClient`] domain object.
[[sample.jpa.entity.client]]
.Client Entity
include::code:Client[]
[[authorization-entity]]
=== Authorization Entity
The following listing shows the `Authorization` entity, which is used to persist information mapped from the xref:{docs-dir}/core-model-components.adoc#oauth2-authorization[`OAuth2Authorization`] domain object.
[[sample.jpa.entity.authorization]]
.Authorization Entity
include::code:Authorization[]
[[authorization-consent-entity]]
=== Authorization Consent Entity
The following listing shows the `AuthorizationConsent` entity, which is used to persist information mapped from the xref:{docs-dir}/core-model-components.adoc#oauth2-authorization-consent[`OAuth2AuthorizationConsent`] domain object.
[[sample.jpa.entity.authorizationConsent]]
.Authorization Consent Entity
include::code:AuthorizationConsent[]
[[create-spring-data-repositories]]
== Create Spring Data repositories
By closely examining the interfaces of each core service and reviewing the `Jdbc` implementations, we can derive a minimal set of queries needed for supporting a JPA version of each interface.
* <<client-repository>>
* <<authorization-repository>>
* <<authorization-consent-repository>>
[[client-repository]]
=== Client Repository
The following listing shows the `ClientRepository`, which is able to find a <<client-entity,`Client`>> by the `id` and `clientId` fields.
[[sample.jpa.repository.client]]
.Client Repository
include::code:ClientRepository[]
[[authorization-repository]]
=== Authorization Repository
The following listing shows the `AuthorizationRepository`, which is able to find an <<authorization-entity,`Authorization`>> by the `id` field as well as the `state`, `authorizationCodeValue`, `accessTokenValue` and `refreshTokenValue` token fields.
It also allows querying a combination of token fields.
[[sample.jpa.repository.authorization]]
.Authorization Repository
include::code:AuthorizationRepository[]
[[authorization-consent-repository]]
=== Authorization Consent Repository
The following listing shows the `AuthorizationConsentRepository`, which is able to find and delete an <<authorization-consent-entity,`AuthorizationConsent`>> by the `registeredClientId` and `principalName` fields that form a composite primary key.
[[sample.jpa.repository.authorizationConsent]]
.Authorization Consent Repository
include::code:AuthorizationConsentRepository[]
[[implement-core-services]]
== Implement core services
With the above <<create-jpa-entities,entities>> and <<create-spring-data-repositories,repositories>>, we can begin implementing the core services.
By reviewing the `Jdbc` implementations, we can derive a minimal set of internal utilities for converting to and from string values for enumerations and reading and writing JSON data for attributes, settings, metadata and claims fields.
[CAUTION]
Keep in mind that writing JSON data to text columns with a fixed length has proven problematic with the `Jdbc` implementations.
While these examples continue to do so, you may need to split these fields out into a separate table or data store that supports arbitrarily long data values.
* <<registered-client-repository>>
* <<authorization-service>>
* <<authorization-consent-service>>
[[registered-client-repository]]
=== Registered Client Repository
The following listing shows the `JpaRegisteredClientRepository`, which uses a <<client-repository,`ClientRepository`>> for persisting a <<client-entity,`Client`>> and maps to and from the xref:{docs-dir}/core-model-components.adoc#registered-client[`RegisteredClient`] domain object.
[[sample.jpa.service.client]]
.`RegisteredClientRepository` Implementation
include::code:JpaRegisteredClientRepository[]
[[authorization-service]]
=== Authorization Service
The following listing shows the `JpaOAuth2AuthorizationService`, which uses an <<authorization-repository,`AuthorizationRepository`>> for persisting an <<authorization-entity,`Authorization`>> and maps to and from the xref:{docs-dir}/core-model-components.adoc#oauth2-authorization[`OAuth2Authorization`] domain object.
[[sample.jpa.service.authorization]]
.`OAuth2AuthorizationService` Implementation
include::code:JpaOAuth2AuthorizationService[]
[[authorization-consent-service]]
=== Authorization Consent Service
The following listing shows the `JpaOAuth2AuthorizationConsentService`, which uses an <<authorization-consent-repository,`AuthorizationConsentRepository`>> for persisting an <<authorization-consent-entity,`AuthorizationConsent`>> and maps to and from the xref:{docs-dir}/core-model-components.adoc#oauth2-authorization-consent[`OAuth2AuthorizationConsent`] domain object.
[[sample.jpa.service.authorizationConsent]]
.`OAuth2AuthorizationConsentService` Implementation
include::code:JpaOAuth2AuthorizationConsentService[]

View File

@@ -0,0 +1,86 @@
[[how-to-userinfo]]
= How-to: Customize the OpenID Connect 1.0 UserInfo response
:index-link: ../how-to.html
:docs-dir: ..
This guide shows how to customize the xref:{docs-dir}/protocol-endpoints.adoc#oidc-user-info-endpoint[UserInfo endpoint] of the xref:{docs-dir}/index.adoc#top[Spring Authorization Server].
The purpose of this guide is to demonstrate how to enable the endpoint and use the available customization options to produce a custom response.
* <<enable-user-info>>
* <<customize-user-info>>
[[enable-user-info]]
== Enable the User Info Endpoint
The xref:{docs-dir}/protocol-endpoints.adoc#oidc-user-info-endpoint[OpenID Connect 1.0 UserInfo endpoint] is an OAuth2 protected resource, which *REQUIRES* an access token to be sent as a bearer token in the https://openid.net/specs/openid-connect-core-1_0.html#UserInfoRequest[UserInfo request].
> The Access Token obtained from an OpenID Connect Authentication Request MUST be sent as a Bearer Token, per Section 2 of https://openid.net/specs/openid-connect-core-1_0.html#RFC6750[OAuth 2.0 Bearer Token Usage] [RFC6750].
Before customizing the response, you need to enable the UserInfo endpoint.
The following listing shows how to enable the {spring-security-reference-base-url}/servlet/oauth2/resource-server/jwt.html[OAuth2 resource server configuration].
[[sample.userinfo]]
include::code:EnableUserInfoSecurityConfig[]
TIP: Click on the "Expanded folded text" icon in the code sample above to display the full example.
This configuration provides the following:
<1> A Spring Security filter chain for the xref:{docs-dir}/protocol-endpoints.adoc[Protocol Endpoints].
<2> Resource server support that allows User Info requests to be authenticated with access tokens.
<3> An instance of `JwtDecoder` used to validate access tokens.
[[customize-user-info]]
== Customize the User Info response
The following sections describe some options for customizing the user info response.
* <<customize-id-token>>
* <<customize-user-info-mapper>>
[[customize-id-token]]
=== Customize the ID Token
By default, the user info response is generated by using claims from the `id_token` that are returned with the xref:{docs-dir}/protocol-endpoints.adoc#oauth2-token-endpoint[token response].
Using the default strategy, https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims[standard claims] are returned only with the user info response based on the https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims[requested scopes] during authorization.
The preferred way to customize the user info response is to add standard claims to the `id_token`.
The following listing shows how to add claims to the `id_token`.
[[sample.userinfo.idtoken]]
include::code:IdTokenCustomizerConfig[]
This configuration provides the following:
<1> An instance of xref:{docs-dir}/core-model-components.adoc#oauth2-token-customizer[`OAuth2TokenCustomizer`] for customizing the `id_token`.
<2> A custom service used to obtain user info in a domain-specific way.
The following listing shows a custom service for looking up user info in a domain-specific way:
include::code:OidcUserInfoService[]
[[customize-user-info-mapper]]
=== Customize the User Info Mapper
To fully customize the user info response, you can provide a custom user info mapper capable of generating the object used to render the response, which is an instance of the `OidcUserInfo` class from Spring Security.
The mapper implementation receives an instance of `OidcUserInfoAuthenticationContext` with information about the current request, including the xref:{docs-dir}/core-model-components.adoc#oauth2-authorization[`OAuth2Authorization`].
The following listing shows how to use the customization option that is available while working directly with the `OAuth2AuthorizationServerConfigurer`.
[[sample.userinfo.jwt]]
include::code:JwtUserInfoMapperSecurityConfig[]
This configuration maps claims from the access token (which is a JWT when using the xref:{docs-dir}/getting-started.adoc#sample.gettingStarted[Getting Started config]) to populate the user info response and provides the following:
<1> A Spring Security filter chain for the xref:{docs-dir}/protocol-endpoints.adoc[Protocol Endpoints].
<2> A user info mapper that maps claims in a domain-specific way.
<3> An example showing the configuration option for customizing the user info mapper.
<4> Resource server support that allows User Info requests to be authenticated with access tokens.
<5> An example showing how to apply the `OAuth2AuthorizationServerConfigurer` to the Spring Security configuration.
The user info mapper is not limited to mapping claims from a JWT, but this is a simple example that demonstrates the customization option.
Similar to the <<customize-id-token,example shown earlier>> where we customize claims of the ID token, you can customize claims of the access token itself ahead of time, as in the following example:
include::code:JwtTokenCustomizerConfig[]
Whether you customize the user info response directly or use this example and customize the access token, you can look up information in a database, perform an LDAP query, make a request to another service, or use any other means of obtaining the information you want to be presented in the user info response.

View File

@@ -0,0 +1,8 @@
[[how-to]]
= How-to Guides
[[how-to-overview]]
== List of Guides
* xref:guides/how-to-userinfo.adoc[Customize the OpenID Connect 1.0 UserInfo response]
* xref:guides/how-to-jpa.adoc[Implement core services with JPA]

View File

@@ -0,0 +1,8 @@
<div id="footer">
<div id="footer-text">
Version {spring-authorization-server-version}<br>
Last updated {docdatetime}<br>
Copyright &copy; 2020-{docyear}
<p class="paragraph">Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.</p>
</div>
</div>

View File

@@ -0,0 +1,13 @@
<productname>Spring Authorization Server</productname>
<releaseinfo>{spring-authorization-server-version}</releaseinfo>
<copyright>
<year>2020-{docyear}</year>
</copyright>
<legalnotice>
<para>
Copies of this document may be made for your own use and for distribution to
others, provided that you do not charge any fee for such copies and further provided
that each copy contains this Copyright Notice, whether distributed in print or
electronically.
</para>
</legalnotice>

View File

@@ -0,0 +1,15 @@
[[top]]
= Spring Authorization Server Reference
Joe Grandja, Steve Riesenberg
v{spring-authorization-server-version}
:docinfo: private-footer
:nofooter:
[horizontal]
xref:overview.adoc[Overview] :: Introduction and feature list
xref:getting-help.adoc[Getting Help] :: Links to samples, questions and issues
xref:getting-started.adoc[Getting Started] :: System requirements, dependencies and developing your first application
xref:configuration-model.adoc[Configuration Model] :: Default configuration and customizing the configuration
xref:core-model-components.adoc[Core Model / Components] :: Core domain model and component interfaces
xref:protocol-endpoints.adoc[Protocol Endpoints] :: OAuth2 and OpenID Connect 1.0 protocol endpoint implementations
xref:how-to.adoc[How-to Guides] :: Guides to get the most from Spring Authorization Server

View File

@@ -0,0 +1,81 @@
[[overview]]
= Overview
This site contains reference documentation and how-to guides for Spring Authorization Server.
[[introducing-spring-authorization-server]]
== Introducing Spring Authorization Server
Spring Authorization Server is a framework that provides implementations of the https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05[OAuth 2.1] and https://openid.net/specs/openid-connect-core-1_0.html[OpenID Connect 1.0] specifications and other related specifications.
It is built on top of https://spring.io/projects/spring-security[Spring Security] to provide a secure, light-weight, and customizable foundation for building OpenID Connect 1.0 Identity Providers and OAuth2 Authorization Server products.
[[feature-list]]
== Feature List
Spring Authorization Server supports the following features:
[cols="2a,4a,6a"]
|===
|Category |Feature |Related specifications
|xref:protocol-endpoints.adoc#oauth2-token-endpoint[Authorization Grant]
|
* Authorization Code
** xref:protocol-endpoints.adoc#oauth2-authorization-endpoint[User Consent]
* Client Credentials
* Refresh Token
|
* The OAuth 2.1 Authorization Framework (https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05[draft])
** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05#section-4.1[Authorization Code Grant]
** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05#section-4.2[Client Credentials Grant]
** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05#section-4.3[Refresh Token Grant]
* OpenID Connect Core 1.0 (https://openid.net/specs/openid-connect-core-1_0.html[spec])
** https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth[Authorization Code Flow]
|xref:core-model-components.adoc#oauth2-token-generator[Token Formats]
|
* Self-contained (JWT)
* Reference (Opaque)
|
* JSON Web Token (JWT) (https://tools.ietf.org/html/rfc7519[RFC 7519])
* JSON Web Signature (JWS) (https://tools.ietf.org/html/rfc7515[RFC 7515])
|xref:configuration-model.adoc#configuring-client-authentication[Client Authentication]
|
* `client_secret_basic`
* `client_secret_post`
* `client_secret_jwt`
* `private_key_jwt`
* `none` (public clients)
|
* The OAuth 2.1 Authorization Framework (https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05#section-2.4[Client Authentication])
* JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication (https://tools.ietf.org/html/rfc7523[RFC 7523])
* Proof Key for Code Exchange by OAuth Public Clients (PKCE) (https://tools.ietf.org/html/rfc7636[RFC 7636])
|xref:protocol-endpoints.adoc[Protocol Endpoints]
|
* xref:protocol-endpoints.adoc#oauth2-authorization-endpoint[OAuth2 Authorization Endpoint]
* xref:protocol-endpoints.adoc#oauth2-token-endpoint[OAuth2 Token Endpoint]
* xref:protocol-endpoints.adoc#oauth2-token-introspection-endpoint[OAuth2 Token Introspection Endpoint]
* xref:protocol-endpoints.adoc#oauth2-token-revocation-endpoint[OAuth2 Token Revocation Endpoint]
* xref:protocol-endpoints.adoc#oauth2-authorization-server-metadata-endpoint[OAuth2 Authorization Server Metadata Endpoint]
* xref:protocol-endpoints.adoc#jwk-set-endpoint[JWK Set Endpoint]
* xref:protocol-endpoints.adoc#oidc-provider-configuration-endpoint[OpenID Connect 1.0 Provider Configuration Endpoint]
* xref:protocol-endpoints.adoc#oidc-user-info-endpoint[OpenID Connect 1.0 UserInfo Endpoint]
* xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration Endpoint]
|
* The OAuth 2.1 Authorization Framework (https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05[draft])
** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05#section-3.1[Authorization Endpoint]
** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05#section-3.2[Token Endpoint]
* OAuth 2.0 Token Introspection (https://tools.ietf.org/html/rfc7662[RFC 7662])
* OAuth 2.0 Token Revocation (https://tools.ietf.org/html/rfc7009[RFC 7009])
* OAuth 2.0 Authorization Server Metadata (https://tools.ietf.org/html/rfc8414[RFC 8414])
* JSON Web Key (JWK) (https://tools.ietf.org/html/rfc7517[RFC 7517])
* OpenID Connect Discovery 1.0 (https://openid.net/specs/openid-connect-discovery-1_0.html[spec])
** https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[Provider Configuration Endpoint]
* OpenID Connect Core 1.0 (https://openid.net/specs/openid-connect-core-1_0.html[spec])
** https://openid.net/specs/openid-connect-core-1_0.html#UserInfo[UserInfo Endpoint]
* OpenID Connect Dynamic Client Registration 1.0 (https://openid.net/specs/openid-connect-registration-1_0.html[spec])
** https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration[Client Registration Endpoint]
** https://openid.net/specs/openid-connect-registration-1_0.html#ClientConfigurationEndpoint[Client Configuration Endpoint]
|===

View File

@@ -0,0 +1,344 @@
[[protocol-endpoints]]
= Protocol Endpoints
[[oauth2-authorization-endpoint]]
== OAuth2 Authorization Endpoint
`OAuth2AuthorizationEndpointConfigurer` provides the ability to customize the https://datatracker.ietf.org/doc/html/rfc6749#section-3.1[OAuth2 Authorization endpoint].
It defines extension points that let you customize the pre-processing, main processing, and post-processing logic for https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1[OAuth2 authorization requests].
`OAuth2AuthorizationEndpointConfigurer` provides the following configuration options:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.authorizationEndpoint(authorizationEndpoint ->
authorizationEndpoint
.authorizationRequestConverter(authorizationRequestConverter) <1>
.authenticationProvider(authenticationProvider) <2>
.authorizationResponseHandler(authorizationResponseHandler) <3>
.errorResponseHandler(errorResponseHandler) <4>
.consentPage("/oauth2/v1/authorize") <5>
);
return http.build();
}
----
<1> `authorizationRequestConverter()`: The `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1[OAuth2 authorization request] (or consent) from `HttpServletRequest` to an instance of `OAuth2AuthorizationCodeRequestAuthenticationToken`.
<2> `authenticationProvider()`: The `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2AuthorizationCodeRequestAuthenticationToken`. (One or more may be added to replace the defaults.)
<3> `authorizationResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OAuth2AuthorizationCodeRequestAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2[OAuth2AuthorizationResponse].
<4> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthorizationCodeRequestAuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1[OAuth2Error response].
<5> `consentPage()`: The `URI` of the custom consent page to redirect resource owners to if consent is required during the authorization request flow.
`OAuth2AuthorizationEndpointConfigurer` configures the `OAuth2AuthorizationEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OAuth2AuthorizationEndpointFilter` is the `Filter` that processes OAuth2 authorization requests (and consents).
`OAuth2AuthorizationEndpointFilter` is configured with the following defaults:
* `*AuthenticationConverter*` -- An `OAuth2AuthorizationCodeRequestAuthenticationConverter`.
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OAuth2AuthorizationCodeRequestAuthenticationProvider`.
* `*AuthenticationSuccessHandler*` -- An internal implementation that handles an "`authenticated`" `OAuth2AuthorizationCodeRequestAuthenticationToken` and returns the `OAuth2AuthorizationResponse`.
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthorizationCodeRequestAuthenticationException` and returns the `OAuth2Error` response.
[[oauth2-token-endpoint]]
== OAuth2 Token Endpoint
`OAuth2TokenEndpointConfigurer` provides the ability to customize the https://datatracker.ietf.org/doc/html/rfc6749#section-3.2[OAuth2 Token endpoint].
It defines extension points that let you customize the pre-processing, main processing, and post-processing logic for https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3[OAuth2 access token requests].
`OAuth2TokenEndpointConfigurer` provides the following configuration options:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.tokenEndpoint(tokenEndpoint ->
tokenEndpoint
.accessTokenRequestConverter(accessTokenRequestConverter) <1>
.authenticationProvider(authenticationProvider) <2>
.accessTokenResponseHandler(accessTokenResponseHandler) <3>
.errorResponseHandler(errorResponseHandler) <4>
);
return http.build();
}
----
<1> `accessTokenRequestConverter()`: The `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3[OAuth2 access token request] from `HttpServletRequest` to an instance of `OAuth2AuthorizationGrantAuthenticationToken`.
<2> `authenticationProvider()`: The `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2AuthorizationGrantAuthenticationToken`. (One or more may be added to replace the defaults.)
<3> `accessTokenResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an `OAuth2AccessTokenAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-5.1[`OAuth2AccessTokenResponse`].
<4> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-5.2[OAuth2Error response].
`OAuth2TokenEndpointConfigurer` configures the `OAuth2TokenEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OAuth2TokenEndpointFilter` is the `Filter` that processes OAuth2 access token requests.
The supported https://datatracker.ietf.org/doc/html/rfc6749#section-1.3[authorization grant types] are `authorization_code`, `refresh_token`, and `client_credentials`.
`OAuth2TokenEndpointFilter` is configured with the following defaults:
* `*AuthenticationConverter*` -- A `DelegatingAuthenticationConverter` composed of `OAuth2AuthorizationCodeAuthenticationConverter`, `OAuth2RefreshTokenAuthenticationConverter`, and `OAuth2ClientCredentialsAuthenticationConverter`.
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OAuth2AuthorizationCodeAuthenticationProvider`, `OAuth2RefreshTokenAuthenticationProvider`, and `OAuth2ClientCredentialsAuthenticationProvider`.
* `*AuthenticationSuccessHandler*` -- An internal implementation that handles an `OAuth2AccessTokenAuthenticationToken` and returns the `OAuth2AccessTokenResponse`.
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthenticationException` and returns the `OAuth2Error` response.
[[oauth2-token-introspection-endpoint]]
== OAuth2 Token Introspection Endpoint
`OAuth2TokenIntrospectionEndpointConfigurer` provides the ability to customize the https://datatracker.ietf.org/doc/html/rfc7662#section-2[OAuth2 Token Introspection endpoint].
It defines extension points that let you customize the pre-processing, main processing, and post-processing logic for https://datatracker.ietf.org/doc/html/rfc7662#section-2.1[OAuth2 introspection requests].
`OAuth2TokenIntrospectionEndpointConfigurer` provides the following configuration options:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.tokenIntrospectionEndpoint(tokenIntrospectionEndpoint ->
tokenIntrospectionEndpoint
.introspectionRequestConverter(introspectionRequestConverter) <1>
.authenticationProvider(authenticationProvider) <2>
.introspectionResponseHandler(introspectionResponseHandler) <3>
.errorResponseHandler(errorResponseHandler) <4>
);
return http.build();
}
----
<1> `introspectionRequestConverter()`: The `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc7662#section-2.1[OAuth2 introspection request] from `HttpServletRequest` to an instance of `OAuth2TokenIntrospectionAuthenticationToken`.
<2> `authenticationProvider()`: The `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2TokenIntrospectionAuthenticationToken`. (One or more may be added to replace the defaults.)
<3> `introspectionResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OAuth2TokenIntrospectionAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc7662#section-2.2[OAuth2TokenIntrospection response].
<4> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc7662#section-2.3[OAuth2Error response].
`OAuth2TokenIntrospectionEndpointConfigurer` configures the `OAuth2TokenIntrospectionEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OAuth2TokenIntrospectionEndpointFilter` is the `Filter` that processes OAuth2 introspection requests.
`OAuth2TokenIntrospectionEndpointFilter` is configured with the following defaults:
* `*AuthenticationConverter*` -- An internal implementation that returns the `OAuth2TokenIntrospectionAuthenticationToken`.
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OAuth2TokenIntrospectionAuthenticationProvider`.
* `*AuthenticationSuccessHandler*` -- An internal implementation that handles an "`authenticated`" `OAuth2TokenIntrospectionAuthenticationToken` and returns the `OAuth2TokenIntrospection` response.
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthenticationException` and returns the `OAuth2Error` response.
[[oauth2-token-revocation-endpoint]]
== OAuth2 Token Revocation Endpoint
`OAuth2TokenRevocationEndpointConfigurer` provides the ability to customize the https://datatracker.ietf.org/doc/html/rfc7009#section-2[OAuth2 Token Revocation endpoint].
It defines extension points that let you customize the pre-processing, main processing, and post-processing logic for https://datatracker.ietf.org/doc/html/rfc7009#section-2.1[OAuth2 revocation requests].
`OAuth2TokenRevocationEndpointConfigurer` provides the following configuration options:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.tokenRevocationEndpoint(tokenRevocationEndpoint ->
tokenRevocationEndpoint
.revocationRequestConverter(revocationRequestConverter) <1>
.authenticationProvider(authenticationProvider) <2>
.revocationResponseHandler(revocationResponseHandler) <3>
.errorResponseHandler(errorResponseHandler) <4>
);
return http.build();
}
----
<1> `revocationRequestConverter()`: The `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc7009#section-2.1[OAuth2 revocation request] from `HttpServletRequest` to an instance of `OAuth2TokenRevocationAuthenticationToken`.
<2> `authenticationProvider()`: The `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2TokenRevocationAuthenticationToken`. (One or more may be added to replace the defaults.)
<3> `revocationResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OAuth2TokenRevocationAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc7009#section-2.2[OAuth2 revocation response].
<4> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc7009#section-2.2.1[OAuth2Error response].
`OAuth2TokenRevocationEndpointConfigurer` configures the `OAuth2TokenRevocationEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OAuth2TokenRevocationEndpointFilter` is the `Filter` that processes OAuth2 revocation requests.
`OAuth2TokenRevocationEndpointFilter` is configured with the following defaults:
* `*AuthenticationConverter*` -- An internal implementation that returns the `OAuth2TokenRevocationAuthenticationToken`.
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OAuth2TokenRevocationAuthenticationProvider`.
* `*AuthenticationSuccessHandler*` -- An internal implementation that handles an "`authenticated`" `OAuth2TokenRevocationAuthenticationToken` and returns the OAuth2 revocation response.
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthenticationException` and returns the `OAuth2Error` response.
[[oauth2-authorization-server-metadata-endpoint]]
== OAuth2 Authorization Server Metadata Endpoint
`OAuth2AuthorizationServerConfigurer` provides support for the https://datatracker.ietf.org/doc/html/rfc8414#section-3[OAuth2 Authorization Server Metadata endpoint].
`OAuth2AuthorizationServerConfigurer` configures the `OAuth2AuthorizationServerMetadataEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OAuth2AuthorizationServerMetadataEndpointFilter` is the `Filter` that processes https://datatracker.ietf.org/doc/html/rfc8414#section-3.1[OAuth2 authorization server metadata requests] and returns the https://datatracker.ietf.org/doc/html/rfc8414#section-3.2[OAuth2AuthorizationServerMetadata response].
[[jwk-set-endpoint]]
== JWK Set Endpoint
`OAuth2AuthorizationServerConfigurer` provides support for the https://datatracker.ietf.org/doc/html/rfc7517[JWK Set endpoint].
`OAuth2AuthorizationServerConfigurer` configures the `NimbusJwkSetEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`NimbusJwkSetEndpointFilter` is the `Filter` that returns the https://datatracker.ietf.org/doc/html/rfc7517#section-5[JWK Set].
[NOTE]
The JWK Set endpoint is configured *only* if a `JWKSource<SecurityContext>` `@Bean` is registered.
[[oidc-provider-configuration-endpoint]]
== OpenID Connect 1.0 Provider Configuration Endpoint
`OidcConfigurer` provides support for the https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[OpenID Connect 1.0 Provider Configuration endpoint].
`OidcConfigurer` configures the `OidcProviderConfigurationEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OidcProviderConfigurationEndpointFilter` is the `Filter` that returns the https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse[OidcProviderConfiguration response].
[[oidc-user-info-endpoint]]
== OpenID Connect 1.0 UserInfo Endpoint
`OidcUserInfoEndpointConfigurer` provides the ability to customize the https://openid.net/specs/openid-connect-core-1_0.html#UserInfo[OpenID Connect 1.0 UserInfo endpoint].
It defines extension points that let you customize the https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse[UserInfo response].
`OidcUserInfoEndpointConfigurer` provides the following configuration option:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.oidc(oidc ->
oidc
.userInfoEndpoint(userInfoEndpoint ->
userInfoEndpoint.userInfoMapper(userInfoMapper) <1>
)
);
return http.build();
}
----
<1> `userInfoMapper()`: The `Function` used to extract claims from `OidcUserInfoAuthenticationContext` to an instance of `OidcUserInfo`.
`OidcUserInfoEndpointConfigurer` configures the `OidcUserInfoEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OidcUserInfoEndpointFilter` is the `Filter` that processes https://openid.net/specs/openid-connect-core-1_0.html#UserInfoRequest[UserInfo requests] and returns the https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse[OidcUserInfo response].
`OidcUserInfoEndpointFilter` is configured with the following defaults:
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OidcUserInfoAuthenticationProvider`, which is associated with an internal implementation of `userInfoMapper` that extracts https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims[standard claims] from the https://openid.net/specs/openid-connect-core-1_0.html#IDToken[ID Token] based on the https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims[scopes requested] during authorization.
[TIP]
You can customize the ID Token by providing an xref:core-model-components.adoc#oauth2-token-customizer[`OAuth2TokenCustomizer<JwtEncodingContext>`] `@Bean`.
The OpenID Connect 1.0 UserInfo endpoint is an OAuth2 protected resource, which *REQUIRES* an access token to be sent as a bearer token in the https://openid.net/specs/openid-connect-core-1_0.html#UserInfoRequest[UserInfo request].
The following example shows how to enable the OAuth2 resource server configuration:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
http.apply(authorizationServerConfigurer);
...
http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
----
[NOTE]
A `JwtDecoder` `@Bean` is *REQUIRED* for the OpenID Connect 1.0 UserInfo endpoint.
[TIP]
The guide xref:guides/how-to-userinfo.adoc#how-to-userinfo[How-to: Customize the OpenID Connect 1.0 UserInfo response] contains examples of customizing the UserInfo endpoint.
[[oidc-client-registration-endpoint]]
== OpenID Connect 1.0 Client Registration Endpoint
`OidcClientRegistrationEndpointConfigurer` configures the https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration[OpenID Connect 1.0 Client Registration endpoint].
The following example shows how to enable (disabled by default) the OpenID Connect 1.0 Client Registration endpoint:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.oidc(oidc ->
oidc
.clientRegistrationEndpoint(Customizer.withDefaults())
);
return http.build();
}
----
[NOTE]
The OpenID Connect 1.0 Client Registration endpoint is disabled by default because many deployments do not require dynamic client registration.
`OidcClientRegistrationEndpointConfigurer` configures the `OidcClientRegistrationEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OidcClientRegistrationEndpointFilter` is the `Filter` that processes https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationRequest[Client Registration requests] and returns the https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse[OidcClientRegistration response].
[TIP]
`OidcClientRegistrationEndpointFilter` also processes https://openid.net/specs/openid-connect-registration-1_0.html#ReadRequest[Client Read requests] and returns the https://openid.net/specs/openid-connect-registration-1_0.html#ReadResponse[OidcClientRegistration response].
`OidcClientRegistrationEndpointFilter` is configured with the following defaults:
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OidcClientRegistrationAuthenticationProvider`.
The OpenID Connect 1.0 Client Registration endpoint is an https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration[OAuth2 protected resource], which *REQUIRES* an access token to be sent as a bearer token in the Client Registration (or Client Read) request.
[IMPORTANT]
The access token in a Client Registration request *REQUIRES* the OAuth2 scope `client.create`.
[IMPORTANT]
The access token in a Client Read request *REQUIRES* the OAuth2 scope `client.read`.
The following example shows how to enable the OAuth2 resource server configuration:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
http.apply(authorizationServerConfigurer);
...
http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
----
[NOTE]
A `JwtDecoder` `@Bean` is *REQUIRED* for the OpenID Connect 1.0 Client Registration endpoint.

View File

@@ -5,3 +5,4 @@
^http://lists.webappsec.org/.*
^http://webblaze.cs.berkeley.edu/.*
^http://www.w3.org/2000/09/xmldsig.*
^http://www.gnu.org/.*

View File

@@ -1,5 +1,11 @@
version=0.1.2
springBootVersion=2.5.2
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
version=0.3.1
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
org.gradle.caching=true
springFrameworkVersion=5.3.20
springSecurityVersion=5.7.1
springJavaformatVersion=0.0.31
springJavaformatExcludePackages=org/springframework/security/config org/springframework/security/oauth2
checkstyleToolVersion=8.34
nohttpCheckstyleVersion=0.0.10
jacocoToolVersion=0.8.7

View File

@@ -1,38 +0,0 @@
if (!project.hasProperty("springVersion")) {
ext.springVersion = "5.3.8"
}
if (!project.hasProperty("springSecurityVersion")) {
ext.springSecurityVersion = "5.5.1"
}
if (!project.hasProperty("reactorVersion")) {
ext.reactorVersion = "2020.0.8"
}
if (!project.hasProperty("locksDisabled")) {
dependencyLocking {
lockAllConfigurations()
}
}
dependencyManagement {
imports {
mavenBom "org.springframework:spring-framework-bom:$springVersion"
mavenBom "org.springframework.security:spring-security-bom:$springSecurityVersion"
mavenBom "io.projectreactor:reactor-bom:$reactorVersion"
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.3"
}
dependencies {
dependency "com.nimbusds:nimbus-jose-jwt:9.8.1"
dependency "javax.servlet:javax.servlet-api:4.0.1"
dependency 'junit:junit:4.13.2'
dependency 'org.assertj:assertj-core:3.19.0'
dependency 'org.mockito:mockito-core:3.9.0'
dependency "com.squareup.okhttp3:mockwebserver:3.14.9"
dependency "com.squareup.okhttp3:okhttp:3.14.9"
dependency "com.jayway.jsonpath:json-path:2.5.0"
dependency "org.hsqldb:hsqldb:2.5.2"
}
}

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,30 +1,33 @@
apply plugin: 'io.spring.convention.spring-module'
plugins {
id "io.spring.convention.spring-module"
}
dependencies {
compile 'org.springframework.security:spring-security-config'
compile 'org.springframework.security:spring-security-web'
compile 'org.springframework.security:spring-security-oauth2-core'
compile 'org.springframework.security:spring-security-oauth2-jose'
compile 'org.springframework.security:spring-security-oauth2-resource-server'
compile springCoreDependency
compile 'com.nimbusds:nimbus-jose-jwt'
compile 'com.fasterxml.jackson.core:jackson-databind'
management platform(project(":spring-authorization-server-dependencies"))
optional 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
optional 'org.springframework:spring-jdbc'
api "org.springframework.security:spring-security-config"
api "org.springframework.security:spring-security-web"
api "org.springframework.security:spring-security-oauth2-core"
api "org.springframework.security:spring-security-oauth2-jose"
api "org.springframework.security:spring-security-oauth2-resource-server"
api("org.springframework:spring-core") {
exclude group: "commons-logging", module: "commons-logging"
}
api "com.nimbusds:nimbus-jose-jwt"
api "com.fasterxml.jackson.core:jackson-databind"
testCompile 'org.springframework.security:spring-security-test'
testCompile 'org.springframework:spring-webmvc'
testCompile 'junit:junit'
testCompile 'org.assertj:assertj-core'
testCompile 'org.mockito:mockito-core'
testCompile 'com.jayway.jsonpath:json-path'
optional "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"
optional "org.springframework:spring-jdbc"
testRuntime 'org.hsqldb:hsqldb'
testImplementation "org.springframework.security:spring-security-test"
testImplementation "org.springframework:spring-webmvc"
testImplementation "junit:junit"
testImplementation "org.assertj:assertj-core"
testImplementation "org.mockito:mockito-core"
testImplementation "com.jayway.jsonpath:json-path"
testImplementation "com.squareup.okhttp3:mockwebserver"
provided 'javax.servlet:javax.servlet-api'
}
jacoco {
toolVersion = '0.8.6'
testRuntimeOnly "org.hsqldb:hsqldb"
provided "javax.servlet:javax.servlet-api"
}

View File

@@ -32,9 +32,9 @@ import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -68,12 +68,10 @@ public class OAuth2AuthorizationServerConfiguration {
authorizeRequests.anyRequest().authenticated()
)
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.apply(authorizationServerConfigurer);
}
// @formatter:on
@Bean
public static JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
Set<JWSAlgorithm> jwsAlgs = new HashSet<>();
jwsAlgs.addAll(JWSAlgorithm.Family.RSA);
@@ -89,4 +87,11 @@ public class OAuth2AuthorizationServerConfiguration {
return new NimbusJwtDecoder(jwtProcessor);
}
@Bean
RegisterMissingBeanPostProcessor registerMissingBeanPostProcessor() {
RegisterMissingBeanPostProcessor postProcessor = new RegisterMissingBeanPostProcessor();
postProcessor.addBeanDefinition(ProviderSettings.class, () -> ProviderSettings.builder().build());
return postProcessor;
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configuration;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
/**
* Post processor to register one or more bean definitions on container initialization, if not already present.
*
* @author Steve Riesenberg
* @since 0.2.0
*/
final class RegisterMissingBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware {
private final AnnotationBeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
private final List<AbstractBeanDefinition> beanDefinitions = new ArrayList<>();
private BeanFactory beanFactory;
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
for (AbstractBeanDefinition beanDefinition : this.beanDefinitions) {
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
(ListableBeanFactory) this.beanFactory, beanDefinition.getBeanClass(), false, false);
if (beanNames.length == 0) {
String beanName = this.beanNameGenerator.generateBeanName(beanDefinition, registry);
registry.registerBeanDefinition(beanName, beanDefinition);
}
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
<T> void addBeanDefinition(Class<T> beanClass, Supplier<T> beanSupplier) {
this.beanDefinitions.add(new RootBeanDefinition(beanClass, beanSupplier));
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
}

View File

@@ -38,8 +38,12 @@ abstract class AbstractOAuth2Configurer {
abstract RequestMatcher getRequestMatcher();
protected <T> T postProcess(T object) {
protected final <T> T postProcess(T object) {
return (T) this.objectPostProcessor.postProcess(object);
}
protected final ObjectPostProcessor<Object> getObjectPostProcessor() {
return this.objectPostProcessor;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,6 +39,7 @@ import org.springframework.security.web.authentication.preauth.AbstractPreAuthen
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
@@ -83,6 +84,7 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
* @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration
*/
public OAuth2AuthorizationEndpointConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) {
Assert.notNull(authenticationProvider, "authenticationProvider cannot be null");
this.authenticationProviders.add(authenticationProvider);
return this;
}
@@ -130,7 +132,7 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
*
* <ul>
* <li>It must be an HTTP POST</li>
* <li>It must be submitted to {@link ProviderSettings#authorizationEndpoint()}</li>
* <li>It must be submitted to {@link ProviderSettings#getAuthorizationEndpoint()} ()}</li>
* <li>It must include the received {@code client_id} as an HTTP parameter</li>
* <li>It must include the received {@code state} as an HTTP parameter</li>
* <li>It must include the list of {@code scope}s the {@code Resource Owner}
@@ -150,10 +152,10 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
this.requestMatcher = new OrRequestMatcher(
new AntPathRequestMatcher(
providerSettings.authorizationEndpoint(),
providerSettings.getAuthorizationEndpoint(),
HttpMethod.GET.name()),
new AntPathRequestMatcher(
providerSettings.authorizationEndpoint(),
providerSettings.getAuthorizationEndpoint(),
HttpMethod.POST.name()));
List<AuthenticationProvider> authenticationProviders =
@@ -172,7 +174,7 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
OAuth2AuthorizationEndpointFilter authorizationEndpointFilter =
new OAuth2AuthorizationEndpointFilter(
authenticationManager,
providerSettings.authorizationEndpoint());
providerSettings.getAuthorizationEndpoint());
if (this.authorizationRequestConverter != null) {
authorizationEndpointFilter.setAuthenticationConverter(this.authorizationRequestConverter);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,34 +20,25 @@ import java.util.LinkedHashMap;
import java.util.Map;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.oauth2.server.authorization.web.ProviderContextFilter;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -60,40 +51,35 @@ import org.springframework.util.Assert;
* @author Daniel Garnier-Moiroux
* @author Gerardo Roza
* @author Ovidiu Popa
* @author Gaurav Tiwari
* @since 0.0.1
* @see AbstractHttpConfigurer
* @see OAuth2ClientAuthenticationConfigurer
* @see OAuth2AuthorizationEndpointConfigurer
* @see OAuth2TokenEndpointConfigurer
* @see OAuth2TokenIntrospectionEndpointConfigurer
* @see OAuth2TokenRevocationEndpointConfigurer
* @see OidcConfigurer
* @see RegisteredClientRepository
* @see OAuth2AuthorizationService
* @see OAuth2AuthorizationConsentService
* @see OAuth2TokenIntrospectionEndpointFilter
* @see OAuth2TokenRevocationEndpointFilter
* @see NimbusJwkSetEndpointFilter
* @see OidcProviderConfigurationEndpointFilter
* @see OAuth2AuthorizationServerMetadataEndpointFilter
* @see OAuth2ClientAuthenticationFilter
* @see OidcClientRegistrationEndpointFilter
*/
public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBuilder<B>>
extends AbstractHttpConfigurer<OAuth2AuthorizationServerConfigurer<B>, B> {
private final Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = createConfigurers();
private RequestMatcher tokenIntrospectionEndpointMatcher;
private RequestMatcher tokenRevocationEndpointMatcher;
private RequestMatcher jwkSetEndpointMatcher;
private RequestMatcher oidcProviderConfigurationEndpointMatcher;
private RequestMatcher authorizationServerMetadataEndpointMatcher;
private RequestMatcher oidcClientRegistrationEndpointMatcher;
private final RequestMatcher endpointsMatcher = (request) ->
getRequestMatcher(OAuth2AuthorizationEndpointConfigurer.class).matches(request) ||
getRequestMatcher(OAuth2TokenEndpointConfigurer.class).matches(request) ||
this.tokenIntrospectionEndpointMatcher.matches(request) ||
this.tokenRevocationEndpointMatcher.matches(request) ||
getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class).matches(request) ||
getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class).matches(request) ||
getRequestMatcher(OidcConfigurer.class).matches(request) ||
this.jwkSetEndpointMatcher.matches(request) ||
this.oidcProviderConfigurationEndpointMatcher.matches(request) ||
this.authorizationServerMetadataEndpointMatcher.matches(request) ||
this.oidcClientRegistrationEndpointMatcher.matches(request);
this.authorizationServerMetadataEndpointMatcher.matches(request);
/**
* Sets the repository of registered clients.
@@ -103,7 +89,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
*/
public OAuth2AuthorizationServerConfigurer<B> registeredClientRepository(RegisteredClientRepository registeredClientRepository) {
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
this.getBuilder().setSharedObject(RegisteredClientRepository.class, registeredClientRepository);
getBuilder().setSharedObject(RegisteredClientRepository.class, registeredClientRepository);
return this;
}
@@ -115,7 +101,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
*/
public OAuth2AuthorizationServerConfigurer<B> authorizationService(OAuth2AuthorizationService authorizationService) {
Assert.notNull(authorizationService, "authorizationService cannot be null");
this.getBuilder().setSharedObject(OAuth2AuthorizationService.class, authorizationService);
getBuilder().setSharedObject(OAuth2AuthorizationService.class, authorizationService);
return this;
}
@@ -127,7 +113,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
*/
public OAuth2AuthorizationServerConfigurer<B> authorizationConsentService(OAuth2AuthorizationConsentService authorizationConsentService) {
Assert.notNull(authorizationConsentService, "authorizationConsentService cannot be null");
this.getBuilder().setSharedObject(OAuth2AuthorizationConsentService.class, authorizationConsentService);
getBuilder().setSharedObject(OAuth2AuthorizationConsentService.class, authorizationConsentService);
return this;
}
@@ -139,7 +125,31 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
*/
public OAuth2AuthorizationServerConfigurer<B> providerSettings(ProviderSettings providerSettings) {
Assert.notNull(providerSettings, "providerSettings cannot be null");
this.getBuilder().setSharedObject(ProviderSettings.class, providerSettings);
getBuilder().setSharedObject(ProviderSettings.class, providerSettings);
return this;
}
/**
* Sets the token generator.
*
* @param tokenGenerator the token generator
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
* @since 0.2.3
*/
public OAuth2AuthorizationServerConfigurer<B> tokenGenerator(OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
getBuilder().setSharedObject(OAuth2TokenGenerator.class, tokenGenerator);
return this;
}
/**
* Configures OAuth 2.0 Client Authentication.
*
* @param clientAuthenticationCustomizer the {@link Customizer} providing access to the {@link OAuth2ClientAuthenticationConfigurer}
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
*/
public OAuth2AuthorizationServerConfigurer<B> clientAuthentication(Customizer<OAuth2ClientAuthenticationConfigurer> clientAuthenticationCustomizer) {
clientAuthenticationCustomizer.customize(getConfigurer(OAuth2ClientAuthenticationConfigurer.class));
return this;
}
@@ -165,6 +175,41 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
return this;
}
/**
* Configures the OAuth 2.0 Token Introspection Endpoint.
*
* @param tokenIntrospectionEndpointCustomizer the {@link Customizer} providing access to the {@link OAuth2TokenIntrospectionEndpointConfigurer}
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
* @since 0.2.3
*/
public OAuth2AuthorizationServerConfigurer<B> tokenIntrospectionEndpoint(Customizer<OAuth2TokenIntrospectionEndpointConfigurer> tokenIntrospectionEndpointCustomizer) {
tokenIntrospectionEndpointCustomizer.customize(getConfigurer(OAuth2TokenIntrospectionEndpointConfigurer.class));
return this;
}
/**
* Configures the OAuth 2.0 Token Revocation Endpoint.
*
* @param tokenRevocationEndpointCustomizer the {@link Customizer} providing access to the {@link OAuth2TokenRevocationEndpointConfigurer}
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
* @since 0.2.2
*/
public OAuth2AuthorizationServerConfigurer<B> tokenRevocationEndpoint(Customizer<OAuth2TokenRevocationEndpointConfigurer> tokenRevocationEndpointCustomizer) {
tokenRevocationEndpointCustomizer.customize(getConfigurer(OAuth2TokenRevocationEndpointConfigurer.class));
return this;
}
/**
* Configures OpenID Connect 1.0 support.
*
* @param oidcCustomizer the {@link Customizer} providing access to the {@link OidcConfigurer}
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
*/
public OAuth2AuthorizationServerConfigurer<B> oidc(Customizer<OidcConfigurer> oidcCustomizer) {
oidcCustomizer.customize(getConfigurer(OidcConfigurer.class));
return this;
}
/**
* Returns a {@link RequestMatcher} for the authorization server endpoints.
*
@@ -182,42 +227,14 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
this.configurers.values().forEach(configurer -> configurer.init(builder));
OAuth2ClientAuthenticationProvider clientAuthenticationProvider =
new OAuth2ClientAuthenticationProvider(
OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
OAuth2ConfigurerUtils.getAuthorizationService(builder));
PasswordEncoder passwordEncoder = OAuth2ConfigurerUtils.getOptionalBean(builder, PasswordEncoder.class);
if (passwordEncoder != null) {
clientAuthenticationProvider.setPasswordEncoder(passwordEncoder);
}
builder.authenticationProvider(postProcess(clientAuthenticationProvider));
OAuth2TokenIntrospectionAuthenticationProvider tokenIntrospectionAuthenticationProvider =
new OAuth2TokenIntrospectionAuthenticationProvider(
OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
OAuth2ConfigurerUtils.getAuthorizationService(builder));
builder.authenticationProvider(postProcess(tokenIntrospectionAuthenticationProvider));
OAuth2TokenRevocationAuthenticationProvider tokenRevocationAuthenticationProvider =
new OAuth2TokenRevocationAuthenticationProvider(
OAuth2ConfigurerUtils.getAuthorizationService(builder));
builder.authenticationProvider(postProcess(tokenRevocationAuthenticationProvider));
// TODO Make OpenID Client Registration an "opt-in" feature
OidcClientRegistrationAuthenticationProvider oidcClientRegistrationAuthenticationProvider =
new OidcClientRegistrationAuthenticationProvider(
OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
OAuth2ConfigurerUtils.getAuthorizationService(builder));
builder.authenticationProvider(postProcess(oidcClientRegistrationAuthenticationProvider));
ExceptionHandlingConfigurer<B> exceptionHandling = builder.getConfigurer(ExceptionHandlingConfigurer.class);
if (exceptionHandling != null) {
exceptionHandling.defaultAuthenticationEntryPointFor(
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
new OrRequestMatcher(
getRequestMatcher(OAuth2TokenEndpointConfigurer.class),
this.tokenIntrospectionEndpointMatcher,
this.tokenRevocationEndpointMatcher)
getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class),
getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class))
);
}
}
@@ -227,57 +244,30 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
this.configurers.values().forEach(configurer -> configurer.configure(builder));
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
if (providerSettings.issuer() != null) {
OidcProviderConfigurationEndpointFilter oidcProviderConfigurationEndpointFilter =
new OidcProviderConfigurationEndpointFilter(providerSettings);
builder.addFilterBefore(postProcess(oidcProviderConfigurationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
OAuth2AuthorizationServerMetadataEndpointFilter authorizationServerMetadataEndpointFilter =
new OAuth2AuthorizationServerMetadataEndpointFilter(providerSettings);
builder.addFilterBefore(postProcess(authorizationServerMetadataEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
ProviderContextFilter providerContextFilter = new ProviderContextFilter(providerSettings);
builder.addFilterAfter(postProcess(providerContextFilter), SecurityContextPersistenceFilter.class);
JWKSource<com.nimbusds.jose.proc.SecurityContext> jwkSource = OAuth2ConfigurerUtils.getJwkSource(builder);
if (jwkSource != null) {
NimbusJwkSetEndpointFilter jwkSetEndpointFilter = new NimbusJwkSetEndpointFilter(
jwkSource, providerSettings.getJwkSetEndpoint());
builder.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
}
JWKSource<SecurityContext> jwkSource = OAuth2ConfigurerUtils.getJwkSource(builder);
NimbusJwkSetEndpointFilter jwkSetEndpointFilter = new NimbusJwkSetEndpointFilter(
jwkSource,
providerSettings.jwkSetEndpoint());
builder.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
OAuth2ClientAuthenticationFilter clientAuthenticationFilter =
new OAuth2ClientAuthenticationFilter(
authenticationManager,
new OrRequestMatcher(
getRequestMatcher(OAuth2TokenEndpointConfigurer.class),
this.tokenIntrospectionEndpointMatcher,
this.tokenRevocationEndpointMatcher));
builder.addFilterAfter(postProcess(clientAuthenticationFilter), AbstractPreAuthenticatedProcessingFilter.class);
OAuth2TokenIntrospectionEndpointFilter tokenIntrospectionEndpointFilter =
new OAuth2TokenIntrospectionEndpointFilter(
authenticationManager,
providerSettings.tokenIntrospectionEndpoint());
builder.addFilterAfter(postProcess(tokenIntrospectionEndpointFilter), FilterSecurityInterceptor.class);
OAuth2TokenRevocationEndpointFilter tokenRevocationEndpointFilter =
new OAuth2TokenRevocationEndpointFilter(
authenticationManager,
providerSettings.tokenRevocationEndpoint());
builder.addFilterAfter(postProcess(tokenRevocationEndpointFilter), OAuth2TokenIntrospectionEndpointFilter.class);
// TODO Make OpenID Client Registration an "opt-in" feature
OidcClientRegistrationEndpointFilter oidcClientRegistrationEndpointFilter =
new OidcClientRegistrationEndpointFilter(
authenticationManager,
providerSettings.oidcClientRegistrationEndpoint());
builder.addFilterAfter(postProcess(oidcClientRegistrationEndpointFilter), OAuth2TokenRevocationEndpointFilter.class);
OAuth2AuthorizationServerMetadataEndpointFilter authorizationServerMetadataEndpointFilter =
new OAuth2AuthorizationServerMetadataEndpointFilter(providerSettings);
builder.addFilterBefore(postProcess(authorizationServerMetadataEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
}
private Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> createConfigurers() {
Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = new LinkedHashMap<>();
configurers.put(OAuth2ClientAuthenticationConfigurer.class, new OAuth2ClientAuthenticationConfigurer(this::postProcess));
configurers.put(OAuth2AuthorizationEndpointConfigurer.class, new OAuth2AuthorizationEndpointConfigurer(this::postProcess));
configurers.put(OAuth2TokenEndpointConfigurer.class, new OAuth2TokenEndpointConfigurer(this::postProcess));
configurers.put(OAuth2TokenIntrospectionEndpointConfigurer.class, new OAuth2TokenIntrospectionEndpointConfigurer(this::postProcess));
configurers.put(OAuth2TokenRevocationEndpointConfigurer.class, new OAuth2TokenRevocationEndpointConfigurer(this::postProcess));
configurers.put(OidcConfigurer.class, new OidcConfigurer(this::postProcess));
return configurers;
}
@@ -291,27 +281,25 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
}
private void initEndpointMatchers(ProviderSettings providerSettings) {
this.tokenIntrospectionEndpointMatcher = new AntPathRequestMatcher(
providerSettings.tokenIntrospectionEndpoint(), HttpMethod.POST.name());
this.tokenRevocationEndpointMatcher = new AntPathRequestMatcher(
providerSettings.tokenRevocationEndpoint(), HttpMethod.POST.name());
this.jwkSetEndpointMatcher = new AntPathRequestMatcher(
providerSettings.jwkSetEndpoint(), HttpMethod.GET.name());
this.oidcProviderConfigurationEndpointMatcher = new AntPathRequestMatcher(
OidcProviderConfigurationEndpointFilter.DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI, HttpMethod.GET.name());
providerSettings.getJwkSetEndpoint(), HttpMethod.GET.name());
this.authorizationServerMetadataEndpointMatcher = new AntPathRequestMatcher(
OAuth2AuthorizationServerMetadataEndpointFilter.DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI, HttpMethod.GET.name());
this.oidcClientRegistrationEndpointMatcher = new AntPathRequestMatcher(
providerSettings.oidcClientRegistrationEndpoint(), HttpMethod.POST.name());
"/.well-known/oauth-authorization-server", HttpMethod.GET.name());
}
private static void validateProviderSettings(ProviderSettings providerSettings) {
if (providerSettings.issuer() != null) {
if (providerSettings.getIssuer() != null) {
URI issuerUri;
try {
new URI(providerSettings.issuer()).toURL();
issuerUri = new URI(providerSettings.getIssuer());
issuerUri.toURL();
} catch (Exception ex) {
throw new IllegalArgumentException("issuer must be a valid URL", ex);
}
// rfc8414 https://datatracker.ietf.org/doc/html/rfc8414#section-2
if (issuerUri.getQuery() != null || issuerUri.getFragment() != null) {
throw new IllegalArgumentException("issuer cannot contain query or fragment component");
}
}
}

View File

@@ -0,0 +1,187 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.authentication.ClientSecretAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.JwtClientAssertionAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.PublicClientAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
/**
* Configurer for OAuth 2.0 Client Authentication.
*
* @author Joe Grandja
* @since 0.2.0
* @see OAuth2AuthorizationServerConfigurer#clientAuthentication
* @see OAuth2ClientAuthenticationFilter
*/
public final class OAuth2ClientAuthenticationConfigurer extends AbstractOAuth2Configurer {
private RequestMatcher requestMatcher;
private AuthenticationConverter authenticationConverter;
private final List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
private AuthenticationSuccessHandler authenticationSuccessHandler;
private AuthenticationFailureHandler errorResponseHandler;
/**
* Restrict for internal use only.
*/
OAuth2ClientAuthenticationConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor);
}
/**
* Sets the {@link AuthenticationConverter} used when attempting to extract client credentials from {@link HttpServletRequest}
* to an instance of {@link OAuth2ClientAuthenticationToken} used for authenticating the client.
*
* @param authenticationConverter the {@link AuthenticationConverter} used when attempting to extract client credentials from {@link HttpServletRequest}
* @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration
*/
public OAuth2ClientAuthenticationConfigurer authenticationConverter(AuthenticationConverter authenticationConverter) {
this.authenticationConverter = authenticationConverter;
return this;
}
/**
* Adds an {@link AuthenticationProvider} used for authenticating an {@link OAuth2ClientAuthenticationToken}.
*
* @param authenticationProvider an {@link AuthenticationProvider} used for authenticating an {@link OAuth2ClientAuthenticationToken}
* @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration
*/
public OAuth2ClientAuthenticationConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) {
Assert.notNull(authenticationProvider, "authenticationProvider cannot be null");
this.authenticationProviders.add(authenticationProvider);
return this;
}
/**
* Sets the {@link AuthenticationSuccessHandler} used for handling a successful client authentication
* and associating the {@link OAuth2ClientAuthenticationToken} to the {@link SecurityContext}.
*
* @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler} used for handling a successful client authentication
* @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration
*/
public OAuth2ClientAuthenticationConfigurer authenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) {
this.authenticationSuccessHandler = authenticationSuccessHandler;
return this;
}
/**
* Sets the {@link AuthenticationFailureHandler} used for handling a failed client authentication
* and returning the {@link OAuth2Error Error Response}.
*
* @param errorResponseHandler the {@link AuthenticationFailureHandler} used for handling a failed client authentication
* @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration
*/
public OAuth2ClientAuthenticationConfigurer errorResponseHandler(AuthenticationFailureHandler errorResponseHandler) {
this.errorResponseHandler = errorResponseHandler;
return this;
}
@Override
<B extends HttpSecurityBuilder<B>> void init(B builder) {
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
this.requestMatcher = new OrRequestMatcher(
new AntPathRequestMatcher(
providerSettings.getTokenEndpoint(),
HttpMethod.POST.name()),
new AntPathRequestMatcher(
providerSettings.getTokenIntrospectionEndpoint(),
HttpMethod.POST.name()),
new AntPathRequestMatcher(
providerSettings.getTokenRevocationEndpoint(),
HttpMethod.POST.name()));
List<AuthenticationProvider> authenticationProviders =
!this.authenticationProviders.isEmpty() ?
this.authenticationProviders :
createDefaultAuthenticationProviders(builder);
authenticationProviders.forEach(authenticationProvider ->
builder.authenticationProvider(postProcess(authenticationProvider)));
}
@Override
<B extends HttpSecurityBuilder<B>> void configure(B builder) {
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
OAuth2ClientAuthenticationFilter clientAuthenticationFilter = new OAuth2ClientAuthenticationFilter(
authenticationManager, this.requestMatcher);
if (this.authenticationConverter != null) {
clientAuthenticationFilter.setAuthenticationConverter(this.authenticationConverter);
}
if (this.authenticationSuccessHandler != null) {
clientAuthenticationFilter.setAuthenticationSuccessHandler(this.authenticationSuccessHandler);
}
if (this.errorResponseHandler != null) {
clientAuthenticationFilter.setAuthenticationFailureHandler(this.errorResponseHandler);
}
builder.addFilterAfter(postProcess(clientAuthenticationFilter), AbstractPreAuthenticatedProcessingFilter.class);
}
@Override
RequestMatcher getRequestMatcher() {
return this.requestMatcher;
}
private <B extends HttpSecurityBuilder<B>> List<AuthenticationProvider> createDefaultAuthenticationProviders(B builder) {
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
RegisteredClientRepository registeredClientRepository = OAuth2ConfigurerUtils.getRegisteredClientRepository(builder);
OAuth2AuthorizationService authorizationService = OAuth2ConfigurerUtils.getAuthorizationService(builder);
JwtClientAssertionAuthenticationProvider jwtClientAssertionAuthenticationProvider =
new JwtClientAssertionAuthenticationProvider(registeredClientRepository, authorizationService);
authenticationProviders.add(jwtClientAssertionAuthenticationProvider);
ClientSecretAuthenticationProvider clientSecretAuthenticationProvider =
new ClientSecretAuthenticationProvider(registeredClientRepository, authorizationService);
PasswordEncoder passwordEncoder = OAuth2ConfigurerUtils.getOptionalBean(builder, PasswordEncoder.class);
if (passwordEncoder != null) {
clientSecretAuthenticationProvider.setPasswordEncoder(passwordEncoder);
}
authenticationProviders.add(clientSecretAuthenticationProvider);
PublicClientAuthenticationProvider publicClientAuthenticationProvider =
new PublicClientAuthenticationProvider(registeredClientRepository, authorizationService);
authenticationProviders.add(publicClientAuthenticationProvider);
return authenticationProviders;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,16 +26,23 @@ import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwsEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.JwtGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2AccessTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.util.StringUtils;
/**
@@ -82,15 +89,61 @@ final class OAuth2ConfigurerUtils {
return authorizationConsentService;
}
static <B extends HttpSecurityBuilder<B>> JwtEncoder getJwtEncoder(B builder) {
@SuppressWarnings("unchecked")
static <B extends HttpSecurityBuilder<B>> OAuth2TokenGenerator<? extends OAuth2Token> getTokenGenerator(B builder) {
OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator = builder.getSharedObject(OAuth2TokenGenerator.class);
if (tokenGenerator == null) {
tokenGenerator = getOptionalBean(builder, OAuth2TokenGenerator.class);
if (tokenGenerator == null) {
JwtGenerator jwtGenerator = getJwtGenerator(builder);
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer = getAccessTokenCustomizer(builder);
if (accessTokenCustomizer != null) {
accessTokenGenerator.setAccessTokenCustomizer(accessTokenCustomizer);
}
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
if (jwtGenerator != null) {
tokenGenerator = new DelegatingOAuth2TokenGenerator(
jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
} else {
tokenGenerator = new DelegatingOAuth2TokenGenerator(
accessTokenGenerator, refreshTokenGenerator);
}
}
builder.setSharedObject(OAuth2TokenGenerator.class, tokenGenerator);
}
return tokenGenerator;
}
private static <B extends HttpSecurityBuilder<B>> JwtGenerator getJwtGenerator(B builder) {
JwtGenerator jwtGenerator = builder.getSharedObject(JwtGenerator.class);
if (jwtGenerator == null) {
JwtEncoder jwtEncoder = getJwtEncoder(builder);
if (jwtEncoder != null) {
jwtGenerator = new JwtGenerator(jwtEncoder);
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = getJwtCustomizer(builder);
if (jwtCustomizer != null) {
jwtGenerator.setJwtCustomizer(jwtCustomizer);
}
builder.setSharedObject(JwtGenerator.class, jwtGenerator);
}
}
return jwtGenerator;
}
private static <B extends HttpSecurityBuilder<B>> JwtEncoder getJwtEncoder(B builder) {
JwtEncoder jwtEncoder = builder.getSharedObject(JwtEncoder.class);
if (jwtEncoder == null) {
jwtEncoder = getOptionalBean(builder, JwtEncoder.class);
if (jwtEncoder == null) {
JWKSource<SecurityContext> jwkSource = getJwkSource(builder);
jwtEncoder = new NimbusJwsEncoder(jwkSource);
if (jwkSource != null) {
jwtEncoder = new NimbusJwtEncoder(jwkSource);
}
}
if (jwtEncoder != null) {
builder.setSharedObject(JwtEncoder.class, jwtEncoder);
}
builder.setSharedObject(JwtEncoder.class, jwtEncoder);
}
return jwtEncoder;
}
@@ -100,32 +153,28 @@ final class OAuth2ConfigurerUtils {
JWKSource<SecurityContext> jwkSource = builder.getSharedObject(JWKSource.class);
if (jwkSource == null) {
ResolvableType type = ResolvableType.forClassWithGenerics(JWKSource.class, SecurityContext.class);
jwkSource = getBean(builder, type);
builder.setSharedObject(JWKSource.class, jwkSource);
jwkSource = getOptionalBean(builder, type);
if (jwkSource != null) {
builder.setSharedObject(JWKSource.class, jwkSource);
}
}
return jwkSource;
}
@SuppressWarnings("unchecked")
static <B extends HttpSecurityBuilder<B>> OAuth2TokenCustomizer<JwtEncodingContext> getJwtCustomizer(B builder) {
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = builder.getSharedObject(OAuth2TokenCustomizer.class);
if (jwtCustomizer == null) {
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, JwtEncodingContext.class);
jwtCustomizer = getOptionalBean(builder, type);
if (jwtCustomizer != null) {
builder.setSharedObject(OAuth2TokenCustomizer.class, jwtCustomizer);
}
}
return jwtCustomizer;
private static <B extends HttpSecurityBuilder<B>> OAuth2TokenCustomizer<JwtEncodingContext> getJwtCustomizer(B builder) {
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, JwtEncodingContext.class);
return getOptionalBean(builder, type);
}
private static <B extends HttpSecurityBuilder<B>> OAuth2TokenCustomizer<OAuth2TokenClaimsContext> getAccessTokenCustomizer(B builder) {
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, OAuth2TokenClaimsContext.class);
return getOptionalBean(builder, type);
}
static <B extends HttpSecurityBuilder<B>> ProviderSettings getProviderSettings(B builder) {
ProviderSettings providerSettings = builder.getSharedObject(ProviderSettings.class);
if (providerSettings == null) {
providerSettings = getOptionalBean(builder, ProviderSettings.class);
if (providerSettings == null) {
providerSettings = new ProviderSettings();
}
providerSettings = getBean(builder, ProviderSettings.class);
builder.setSharedObject(ProviderSettings.class, providerSettings);
}
return providerSettings;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,10 +28,10 @@ import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
@@ -45,6 +45,7 @@ import org.springframework.security.web.authentication.AuthenticationFailureHand
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
/**
* Configurer for the OAuth 2.0 Token Endpoint.
@@ -87,6 +88,7 @@ public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configure
* @return the {@link OAuth2TokenEndpointConfigurer} for further configuration
*/
public OAuth2TokenEndpointConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) {
Assert.notNull(authenticationProvider, "authenticationProvider cannot be null");
this.authenticationProviders.add(authenticationProvider);
return this;
}
@@ -119,7 +121,7 @@ public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configure
<B extends HttpSecurityBuilder<B>> void init(B builder) {
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
this.requestMatcher = new AntPathRequestMatcher(
providerSettings.tokenEndpoint(), HttpMethod.POST.name());
providerSettings.getTokenEndpoint(), HttpMethod.POST.name());
List<AuthenticationProvider> authenticationProviders =
!this.authenticationProviders.isEmpty() ?
@@ -137,7 +139,7 @@ public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configure
OAuth2TokenEndpointFilter tokenEndpointFilter =
new OAuth2TokenEndpointFilter(
authenticationManager,
providerSettings.tokenEndpoint());
providerSettings.getTokenEndpoint());
if (this.accessTokenRequestConverter != null) {
tokenEndpointFilter.setAuthenticationConverter(this.accessTokenRequestConverter);
}
@@ -158,34 +160,19 @@ public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configure
private <B extends HttpSecurityBuilder<B>> List<AuthenticationProvider> createDefaultAuthenticationProviders(B builder) {
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
JwtEncoder jwtEncoder = OAuth2ConfigurerUtils.getJwtEncoder(builder);
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = OAuth2ConfigurerUtils.getJwtCustomizer(builder);
OAuth2AuthorizationService authorizationService = OAuth2ConfigurerUtils.getAuthorizationService(builder);
OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator = OAuth2ConfigurerUtils.getTokenGenerator(builder);
OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider =
new OAuth2AuthorizationCodeAuthenticationProvider(
OAuth2ConfigurerUtils.getAuthorizationService(builder),
jwtEncoder);
if (jwtCustomizer != null) {
authorizationCodeAuthenticationProvider.setJwtCustomizer(jwtCustomizer);
}
new OAuth2AuthorizationCodeAuthenticationProvider(authorizationService, tokenGenerator);
authenticationProviders.add(authorizationCodeAuthenticationProvider);
OAuth2RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider =
new OAuth2RefreshTokenAuthenticationProvider(
OAuth2ConfigurerUtils.getAuthorizationService(builder),
jwtEncoder);
if (jwtCustomizer != null) {
refreshTokenAuthenticationProvider.setJwtCustomizer(jwtCustomizer);
}
new OAuth2RefreshTokenAuthenticationProvider(authorizationService, tokenGenerator);
authenticationProviders.add(refreshTokenAuthenticationProvider);
OAuth2ClientCredentialsAuthenticationProvider clientCredentialsAuthenticationProvider =
new OAuth2ClientCredentialsAuthenticationProvider(
OAuth2ConfigurerUtils.getAuthorizationService(builder),
jwtEncoder);
if (jwtCustomizer != null) {
clientCredentialsAuthenticationProvider.setJwtCustomizer(jwtCustomizer);
}
new OAuth2ClientCredentialsAuthenticationProvider(authorizationService, tokenGenerator);
authenticationProviders.add(clientCredentialsAuthenticationProvider);
return authenticationProviders;

Some files were not shown because too many files have changed in this diff Show More