567 Commits

Author SHA1 Message Date
Joe Grandja
3729dc0d43 Release 0.4.0-M2 2022-09-20 14:55:14 -04:00
Joe Grandja
dc142cb253 Update to okhttp:4.10.0
Closes gh-904
2022-09-20 14:50:19 -04:00
Joe Grandja
9c0ca08e68 Update to mockito-core:4.8.0
Closes gh-903
2022-09-20 14:50:08 -04:00
Joe Grandja
1f68ad1655 Update to assertj-core:3.23.1
Closes gh-902
2022-09-20 14:49:58 -04:00
Joe Grandja
b71801cd1e Update to jackson-bom:2.13.4
Closes gh-901
2022-09-20 14:49:46 -04:00
Joe Grandja
213bf49510 Update to nimbus-jose-jwt:9.24.4
Closes gh-900
2022-09-20 14:49:31 -04:00
Joe Grandja
f114e3a1ce Update to Spring Security 5.8.0-M3
Closes gh-899
2022-09-20 14:49:21 -04:00
Joe Grandja
17c882c06d Update to Spring Framework 5.3.23
Closes gh-898
2022-09-20 14:48:50 -04:00
Joe Grandja
92dbcf29a5 Move integration tests for OidcProviderConfiguration 2022-09-20 12:25:58 -04:00
Joe Grandja
26aed3c183 Polish gh-881 2022-09-20 11:23:42 -04:00
sahariardev
cd6f1d7dc3 Return registration_endpoint when client registration is enabled
Closes gh-370
2022-09-20 11:22:45 -04:00
Joe Grandja
4d94e7095d Decompose OAuth2AuthorizationCodeRequestAuthenticationProvider
Closes gh-896
2022-09-20 06:03:32 -04:00
Joe Grandja
80b01854f2 Update README with documentation links 2022-09-13 11:57:41 -04:00
Joe Grandja
c326b1a2ba Remove OAuth2AuthenticationValidator
Closes gh-891
2022-09-12 16:57:22 -04:00
Joe Grandja
1db05991af Make OAuth2AuthenticationContext an interface
Closes gh-890
2022-09-12 16:56:45 -04:00
Joe Grandja
2cc603c7e7 Improve configurability for AuthenticationConverter and AuthenticationProvider
Closes gh-417
2022-09-07 04:29:03 -04:00
doctormacky
07d69cbfb4 Validate client secret not expired
Closes gh-850
2022-08-30 09:41:29 -04:00
Joe Grandja
502fa24cfb Polish gh-787 2022-08-30 05:58:15 -04:00
721806280
4466cbe69d Use configured ID Token signature algorithm
Closes gh-787
2022-08-30 05:19:21 -04:00
Joe Grandja
8043b8c949 Allow customizing Authorization Server Metadata Response
Closes gh-878
2022-08-29 17:06:46 -04:00
Joe Grandja
0994a1e1e1 Allow customizing OIDC Provider Configuration Response
Closes gh-616
2022-08-29 10:11:05 -04:00
Joe Grandja
70d433a45a Update ref-doc with OAuth2Authorization.getAuthorizedScopes()
Issue gh-829
2022-08-26 11:14:20 -04:00
Joe Grandja
2dabfa02e0 Remove constructor in OidcProviderConfigurationEndpointFilter
Closes gh-869
2022-08-23 13:57:11 -04:00
Joe Grandja
6b66719a83 Remove constructor in OAuth2AuthorizationServerMetadataEndpointFilter
Closes gh-868
2022-08-23 13:55:51 -04:00
Joe Grandja
aebc613862 Make AuthorizationServerContext an interface
Closes gh-867
2022-08-23 11:26:11 -04:00
Joe Grandja
f583668a9c Make AuthorizationServerContextFilter private
Closes gh-866
2022-08-23 11:25:46 -04:00
Joe Grandja
3efee494ad Rename ProviderContext
Closes gh-865
2022-08-23 11:25:18 -04:00
Joe Grandja
c60ae4532f Rename ProviderSettings
Closes gh-864
2022-08-23 11:24:22 -04:00
Joe Grandja
4066c3ec4d Next Development Version 2022-08-16 14:19:44 -04:00
Joe Grandja
daeeb14141 Release 0.4.0-M1 2022-08-16 14:07:02 -04:00
Joe Grandja
6701913d0e Update to nimbus-jose-jwt:9.23
Closes gh-857
2022-08-16 14:01:07 -04:00
Joe Grandja
03b31d90a7 Update to Spring Security 5.8.0-M2
Closes gh-856
2022-08-16 13:59:39 -04:00
Joe Grandja
c9ac9afb60 Update to Spring Framework 5.3.22
Closes gh-855
2022-08-16 13:58:38 -04:00
Joe Grandja
ce0d1b7b1e Enhance samples to call UserInfo endpoint
Closes gh-847
2022-08-16 11:31:46 -04:00
Joe Grandja
2e07ce8b8f Polish gh-738 2022-08-16 03:58:50 -04:00
yonyes
61621c5671 Default authorized scope to empty for client_credentials grant
Closes gh-780
2022-08-16 03:58:37 -04:00
Joe Grandja
532cade256 Update github workflows 2022-08-02 09:02:43 -04:00
Joe Grandja
6284e1dc1e Remove deprecated API usage 2022-08-01 11:47:29 -04:00
Joe Grandja
4934088593 Remove generic type from OAuth2AuthorizationServerConfigurer
Closes gh-831
2022-08-01 11:17:52 -04:00
Joe Grandja
0656fde051 Remove OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME
Closes gh-829
2022-07-29 15:42:26 -04:00
Joe Grandja
83be809814 Move SpringTestRule 2022-07-29 05:35:19 -04:00
Joe Grandja
a38b66adf2 Remove unused code 2022-07-29 05:08:16 -04:00
Joe Grandja
33a78913fa Cleanup TODOs 2022-07-29 04:29:11 -04:00
Joe Grandja
f29a068b7a Rename JwtEncodingContext.getHeaders() to getJwsHeader()
Closes gh-826
2022-07-28 15:54:52 -04:00
Joe Grandja
94053f2068 Make builders final for AbstractSettings implementations
Closes gh-825
2022-07-28 15:54:38 -04:00
Joe Grandja
0b85fdc883 Make OAuth2TokenIntrospectionEndpointConfigurer.getRequestMatcher() package-private
Closes gh-824
2022-07-28 15:54:05 -04:00
Joe Grandja
852d795abc Polish gh-786 2022-07-28 14:51:01 -04:00
Joe Grandja
95e9a9e4bd Rename Version to SpringAuthorizationServerVersion
Closes gh-823
2022-07-27 07:19:45 -04:00
Joe Grandja
403d7070d2 Relocate Version
Closes gh-823
2022-07-27 07:19:23 -04:00
Joe Grandja
fdda88263b Relocate OAuth2TokenFormat
Closes gh-822
2022-07-27 07:18:53 -04:00
Joe Grandja
8aa2213bc4 Relocate OAuth2TokenType
Closes gh-821
2022-07-27 07:18:43 -04:00
Joe Grandja
af5f71f2ae Relocate OAuth2AuthorizationCode
Closes gh-820
2022-07-27 07:18:28 -04:00
Joe Grandja
6d69f3831e Relocate OAuth2TokenIntrospection
Closes gh-819
2022-07-27 07:18:16 -04:00
Joe Grandja
9c34eb874c Relocate OidcUserInfoHttpMessageConverter
Closes gh-818
2022-07-27 07:18:04 -04:00
Joe Grandja
05468b7b29 Relocate OidcClientRegistration
Closes gh-817
2022-07-27 07:17:52 -04:00
Joe Grandja
33f6f4f731 Relocate OidcProviderConfiguration
Closes gh-816
2022-07-27 07:17:34 -04:00
Joe Grandja
a48fbe46d5 Relocate OAuth2AuthorizationServerMetadata
Closes gh-815
2022-07-27 07:17:21 -04:00
Joe Grandja
0808fba26e Relocate classes out from oauth2.core.context package
Closes gh-814
2022-07-27 07:17:08 -04:00
Joe Grandja
2b2221a467 Relocate classes out from oauth2.core.authentication package
Closes gh-813
2022-07-27 07:16:22 -04:00
Joe Grandja
3877999a2a Move AbstractSettings implementations to settings package
Closes gh-811
2022-07-26 11:43:45 -04:00
Joe Grandja
ab601640ff Relocate classes out from config.annotation.web.configurers package
Closes gh-810
2022-07-26 08:55:07 -04:00
Joe Grandja
435a17e4b9 Relocate classes out from config.annotation.web.configuration package
Closes gh-810
2022-07-26 08:38:21 -04:00
Joe Grandja
65400f8c71 Update gradle-enterprise-conventions plugin to 0.0.11
Issue gh-788
2022-07-19 16:12:06 -04:00
Jerome Prinet
f008d5dded Update Gradle Enterprise plugin
Closes gh-788
2022-07-19 16:11:36 -04:00
wingzero
d5a57396a4 Update custom consent page sample
Closes gh-802
2022-07-19 15:32:42 -04:00
Joe Grandja
19c7606c52 Polish gh-786 2022-07-19 12:27:43 -04:00
Gyeongwon, Do
303043ea78 Add configuration for authorization code time-to-live
Closes gh-642
2022-07-19 11:40:02 -04:00
Joe Grandja
3f5e7c793b Next Development Version 2022-06-17 12:15:39 -04:00
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
Joe Grandja
2523916ca1 Release 0.1.2 2021-07-09 08:20:19 -04:00
Joe Grandja
7ffcbe57a7 Fix package tangle 2021-07-09 08:00:45 -04:00
Joe Grandja
7b4fc46369 Polish authorization consent page
Issue gh-340
2021-07-09 07:15:01 -04:00
Joe Grandja
09439a10ce Polish gh-329 2021-07-09 06:56:56 -04:00
Joe Grandja
c93c1a8097 Polish gh-338 2021-07-09 06:51:13 -04:00
Joe Grandja
1ae4f7aa13 Polish JdbcRegisteredClientRepository
Issue gh-291
2021-07-09 06:06:53 -04:00
Joe Grandja
a11284f0f5 Polish gh-331 2021-07-09 04:37:52 -04:00
Joe Grandja
ad108f519a Polish JdbcOAuth2AuthorizationConsentService
Issue gh-314
2021-07-08 09:58:07 -04:00
Joe Grandja
9787794ea1 Polish JdbcOAuth2AuthorizationService
Issue gh-304
2021-07-08 08:50:02 -04:00
Joe Grandja
41dd68903c Update spring-build-conventions to 0.0.38
Issue gh-344
2021-07-07 11:47:12 -04:00
Joe Grandja
f1834633b1 Update to use s01.oss.sonatype.org
Issue gh-344
2021-07-07 11:42:18 -04:00
Joe Grandja
1e6032e01a Update nohttp-gradle to 0.0.8
Issue gh-344
2021-07-07 11:11:45 -04:00
Joe Grandja
059880f51a Update nimbus-jose-jwt to 9.8.1
Issue gh-344
2021-07-07 10:31:37 -04:00
Joe Grandja
1d94ac85a4 Update json-path to 2.5.0
Issue gh-344
2021-07-07 10:14:03 -04:00
Joe Grandja
d6ce4c237a Update mockito-core to 3.9.0
Issue gh-344
2021-07-07 10:11:45 -04:00
Joe Grandja
4c314cf570 Update assertj-core to 3.19.0
Issue gh-344
2021-07-07 10:10:35 -04:00
Joe Grandja
d520fa7774 Update junit to 4.13.2
Issue gh-344
2021-07-07 10:08:36 -04:00
Joe Grandja
65213e97d6 Update jackson-bom to 2.12.3
Issue gh-344
2021-07-07 10:05:56 -04:00
Joe Grandja
b921b85665 Update Spring Boot to 2.5.2
Issue gh-344
2021-07-07 09:54:11 -04:00
Joe Grandja
41c38a4b53 Update Reactor to 2020.0.8
Issue gh-344
2021-07-07 09:54:11 -04:00
Joe Grandja
da945a12be Update Spring Security to 5.5.1
Issue gh-344
2021-07-07 09:54:11 -04:00
Joe Grandja
51bf550d12 Update Spring Framework to 5.3.8
Issue gh-344
2021-07-07 09:54:11 -04:00
Joe Grandja
1f17005edd Update Gradle to 6.9
Issue gh-344
2021-07-07 09:54:11 -04:00
Steve Riesenberg
4204bc7e78 Temporarily fix expires_in for access token response
TODO: This can be reverted when Spring Security 5.6 is released.

Closes gh-281
2021-07-07 08:05:44 -04:00
Anoop Garlapati
385fc37b1d Refresh token grant may issue ID token
See https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokenResponse

Closes gh-287
2021-07-07 06:18:25 -04:00
Daniel Garnier-Moiroux
b62b161b95 Include WebAuthenticationDetails in token requests
Closes gh-322
2021-07-07 04:49:26 -04:00
Joe Grandja
9def059e29 Provide configuration for the authorization endpoint
Closes gh-342
2021-07-06 18:15:59 -04:00
Joe Grandja
fb276e7a4a Provide extension for processing authorization response
Issue gh-342
2021-07-06 15:47:33 -04:00
Joe Grandja
543fa264b3 Provide extension for processing authorization request
Issue gh-342
2021-07-06 15:07:59 -04:00
Joe Grandja
cf235ceb4e Polish gh-319 2021-07-06 14:39:40 -04:00
Joe Grandja
4517022f36 Polish authorization consent
Issue gh-340 gh-280
2021-07-06 10:21:12 -04:00
Joe Grandja
08ba07d676 Extract AuthenticationProvider from OAuth2AuthorizationEndpointFilter
Closes gh-340
2021-07-06 09:16:27 -04:00
Steve Riesenberg
023e22c9d3 Update integration tests to use jdbc 2021-07-01 17:50:19 -05:00
Steve Riesenberg
549cdc7222 Update integration tests to use in-memory 2021-07-01 17:50:19 -05:00
Steve Riesenberg
4daade7366 Update authorization server sample to use jdbc
Closes gh-329
2021-07-01 14:21:41 -05:00
Steve Riesenberg
a949998664 Add test to override schema for JdbcOAuth2AuthorizationConsentService 2021-06-30 15:58:17 -05:00
Steve Riesenberg
aa208a2d30 Add correct script for testing JdbcOAuth2AuthorizationService 2021-06-30 13:05:10 -05:00
Steve Riesenberg
99fb4c8a5f Add test to override schema for JdbcOAuth2AuthorizationService 2021-06-30 12:08:35 -05:00
Steve Riesenberg
5f994a83d8 Remove public modifier from constructor 2021-06-29 16:41:16 -05:00
Steve Riesenberg
623736d640 Add test to override schema for JdbcRegisteredClientRepository 2021-06-29 15:25:07 -05:00
Steve Riesenberg
3318874da1 Polish gh-291 2021-06-29 14:06:36 -05:00
Steve Riesenberg
6f6829bb91 Polish gh-314 2021-06-29 11:03:07 -05:00
Steve Riesenberg
ab2f1749bf Polish gh-304 2021-06-29 10:50:47 -05:00
Steve Riesenberg
232b3b7ac6 Add jackson module for authorization server
Fixes problems with serialization of complex attribute values of various framework types such as OAuth2AuthorizationRequest and OAuth2ClientAuthenticationToken.

Closes gh-324
Closes gh-328
2021-06-28 16:12:18 -05:00
Steve Riesenberg
67e62a2f21 Fix NPE saving public client
Closes gh-326
2021-06-28 12:37:01 -05:00
Steve Riesenberg
473dedb9ad Revert "Polish JdbcRegisteredClientRepository, JdbcOAuth2AuthorizationConsentService, JdbcOAuth2AuthorizationService"
This reverts commit e5e391db
2021-06-25 09:14:55 -05:00
Steve Riesenberg
e5e391db38 Polish JdbcRegisteredClientRepository, JdbcOAuth2AuthorizationConsentService, JdbcOAuth2AuthorizationService 2021-06-23 16:08:23 -05:00
Joe Grandja
4209ed7599 Polish JdbcOAuth2AuthorizationService
Issue gh-304
2021-06-23 07:09:08 -04:00
Steve Riesenberg
763ef2224b Polish gh-291 2021-06-22 12:32:25 -05:00
Rafal Lewczuk
769cf8fac7 JDBC implementation of RegisteredClientRepository
Closes gh-265
2021-06-22 11:17:25 -05:00
Joe Grandja
1f4b369912 Provide configuration for the token endpoint
Closes gh-319
2021-06-22 06:13:44 -04:00
Joe Grandja
7c97d8ede9 Extract utility methods from OAuth2AuthorizationServerConfigurer
Issue gh-319
2021-06-22 06:13:34 -04:00
Joe Grandja
5a79234677 Provide extension for processing access token request
Issue gh-319
2021-06-22 06:13:25 -04:00
Joe Grandja
23732187a9 Provide extension for processing access token response
Issue gh-319
2021-06-22 06:12:44 -04:00
Steve Riesenberg
5e79f21913 Polish gh-314
Polish gh-304
2021-06-18 12:23:03 -05:00
Ovidiu Popa
5dbe973701 Provide JDBC implementation of OAuth2AuthorizationConsentService
Add new JDBC implementation of the OAuth2AuthorizationConsentService
Add equals and hashCode methods in OAuth2AuthorizationConsent

Closes gh-313
2021-06-18 11:46:45 -05:00
Steve Riesenberg
8e9563a440 Polish gh-304 2021-06-16 10:35:56 -05:00
Steve Riesenberg
566064ba9a Revert "Polish gh-313"
This reverts commit 7f095e0a
2021-06-16 10:35:13 -05:00
Steve Riesenberg
7f095e0a6f Polish gh-313 2021-06-16 10:26:56 -05:00
Ovidiu Popa
552751bd93 Provide JDBC implementation of OAuth2AuthorizationService
Add new JDBC implementation of the OAuth2AuthorizationService

Closes gh-245
2021-06-15 17:43:25 -05:00
Joe Grandja
f3cb8f758c Polish gh-280 2021-06-04 06:36:40 -04:00
Daniel Garnier-Moiroux
683dad1443 Remember user consent and make consent page configurable
Closes gh-283
2021-06-03 09:56:13 -04:00
Joe Grandja
4688b0f879 Polish gh-161 2021-06-03 09:43:10 -04:00
Steve Riesenberg
c37ecd747f OAuth2AuthorizationCodeAuthenticationProvider checks if code has expired
Closes gh-290
2021-05-26 13:41:49 -04:00
Daniel Garnier-Moiroux
6ddb73d54f Introduce integration tests for the sample oauth server
Closes gh-277
2021-05-26 13:14:53 -04:00
Joe Grandja
5e0fe9c862 Next Development Version 2021-05-07 15:17:12 -04:00
Joe Grandja
dcf8741d16 Release 0.1.1 2021-05-07 15:02:13 -04:00
Joe Grandja
830556a19b Update Spring Boot to 2.4.5 2021-05-07 14:56:53 -04:00
Joe Grandja
e72899d827 Update Reactor to 2020.0.6 2021-05-07 14:50:19 -04:00
Joe Grandja
ec09d120ef Update Spring Security to 5.4.6 2021-05-07 14:49:38 -04:00
Joe Grandja
55dda0f767 Update Spring to 5.3.6 2021-05-07 14:48:54 -04:00
Joe Grandja
7933cb8c4b Fix sample
Issue gh-272
2021-05-07 14:45:28 -04:00
Joe Grandja
93d16d4419 Polish gh-272 2021-05-07 14:26:29 -04:00
Rafal Lewczuk
8cd954ffa2 Use PasswordEncoder in OAuth2ClientAuthenticationProvider
Closes gh-271
2021-05-07 14:25:24 -04:00
Joe Grandja
0c70e8ad3a Rename OAuth2TokenIntrospection.Builder.validateClaims() to validate()
Issue gh-161
2021-05-07 11:44:02 -04:00
Joe Grandja
e7feb6c0ed Polish gh-189 2021-05-07 11:21:41 -04:00
Ovidiu Popa
8224a0d971 Implement OpenID client registration endpoint
See: https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration

Closes gh-57
2021-05-07 11:06:11 -04:00
Daniel Garnier-Moiroux
2712a7b86c Polish ProviderSettingsTests
Issue gh-167
2021-04-30 06:15:04 -04:00
Joe Grandja
7dc9da3340 Polish gh-167 2021-04-30 05:49:38 -04:00
Daniel Garnier-Moiroux
0a4775423b Implement OAuth 2.0 Server Metadata (RFC 8414)
See See https://tools.ietf.org/html/rfc8414

Closes gh-54
2021-04-28 10:13:51 +02:00
Rob Winch
a30a1692b2 .github/workflow master->main
Issue gh-284
2021-04-27 15:46:44 -05:00
Rob Winch
77208f05d9 master->main
Closes gh-284
2021-04-27 15:44:51 -05:00
Joe Grandja
9a45ae9804 Polish gh-161 2021-04-26 09:51:39 -04:00
Gerardo Roza
92e8c08ce6 Add Token Introspection Endpoint
Closes gh-52
2021-04-26 09:51:39 -04:00
Joe Grandja
4e2626e8b7 Fix code formatter markers 2021-04-26 09:51:21 -04:00
Joe Grandja
2d8d56840c Polish oauth2-integration sample
Issue gh-267
2021-04-05 15:58:36 -04:00
Joe Grandja
85d6a12000 Login page should not be configured
Closes gh-267
2021-03-26 11:09:20 -04:00
Anoop Garlapati
8d57c893fb Redirect URI validation for loopback address
Modified redirect_uri validation as per OAuth 2.1 to
accomodate for redirections on loopback address interface.

Closes gh-243
2021-03-25 15:50:53 -04:00
Norbert Nowak
658b186381 Fix download artifacts link
Closes gh-263
2021-03-19 14:06:49 -04:00
Joe Grandja
d33ec32017 Update gh issue template config.yml 2021-03-18 04:05:38 -04:00
Daniel Garnier-Moiroux
59040a4c3d Use nimbus-jose-jwt and oauth2-oidc-sdk versions from spring-security
- Spring Security 5.4.5 downgraded nimbus-jose-jwt to 8.+ from 9.+,
  which breaks NimbusJwsEncoder.
- Bump Security to 5.4.5, and Boot to 2.4.3 to match Security

Closes gh-256
2021-03-16 10:04:42 -04:00
Joshua Casey
3b0938883b Scope "openid" should be in access token response scope
- Still does not require user consent

Closes gh-252
2021-03-15 12:00:44 -04:00
Daniel Garnier-Moiroux
1962b9c5b7 Bump Jacoco to 0.8.6 to support Java 15 2021-03-12 16:09:38 -05:00
Joe Grandja
a90d98aa1e Use artifactoryUsername/Password for plugin repositories 2021-02-11 22:11:29 -05:00
Joe Grandja
e440935c14 Next Development Version 2021-02-11 21:52:34 -05:00
Joe Grandja
1f9e0f56fb Release 0.1.0 2021-02-11 21:22:39 -05:00
Joe Grandja
7ff237ed90 Update to json-path 2.4.0
Closes gh-239
2021-02-11 21:10:43 -05:00
Joe Grandja
d6d74bc843 Update to okhttp3:okhttp 3.14.9
Closes gh-238
2021-02-11 21:10:21 -05:00
Joe Grandja
277565599a Update to okhttp3:mockwebserver 3.14.9
Closes gh-237
2021-02-11 21:09:57 -05:00
Joe Grandja
3b4cb382ec Update to mockito-core 3.6.28
Closes gh-236
2021-02-11 21:09:32 -05:00
Joe Grandja
16c6ed4fb4 Update to assertj-core 3.18.1
Closes gh-235
2021-02-11 21:09:12 -05:00
Joe Grandja
4cc1553542 Update to junit 4.13.1
Closes gh-234
2021-02-11 21:08:54 -05:00
Joe Grandja
4b45d69c17 Update to javax.servlet-api 4.0.1
Closes gh-233
2021-02-11 21:08:41 -05:00
Joe Grandja
4f3d7c4821 Update to nimbus-jose-jwt 9.1.3
Closes gh-232
2021-02-11 21:08:31 -05:00
Joe Grandja
799d790423 Update to oauth2-oidc-sdk 8.23.1
Closes gh-231
2021-02-11 21:08:14 -05:00
Joe Grandja
9f1c760876 Update to Reactor 2020.0.3
Closes gh-230
2021-02-11 21:07:57 -05:00
Joe Grandja
b25b906917 Update to Spring Security 5.4.2
Closes gh-229
2021-02-11 21:07:43 -05:00
Joe Grandja
839123409d Update to Spring Framework 5.3.3
Closes gh-228
2021-02-11 21:07:24 -05:00
Joe Grandja
03d3879817 Update to Spring Boot 2.4.2
Closes gh-227
2021-02-11 21:06:24 -05:00
Joe Grandja
ef314e5c28 Revert "Use artifactoryUsername/Password for plugin repositories"
This reverts commit 6db194da27.
2021-02-11 19:44:56 -05:00
Joe Grandja
a7d4d45658 Add update-dependencies.sh 2021-02-11 18:18:08 -05:00
Joe Grandja
69a34bce5b Fix package tangles 2021-02-11 15:16:37 -05:00
Joe Grandja
7652d0ebbe Propagate additional token request parameters
Closes gh-226
2021-02-11 10:42:26 -05:00
Joe Grandja
b5d47366ad openid scope does not require user consent
Closes gh-225
2021-02-11 08:35:17 -05:00
Joe Grandja
ece5f2b3b1 Add JwtEncodingContext.getAuthorizedScopes()
Issue gh-199
2021-02-10 20:26:01 -05:00
Joe Grandja
c00226d0c6 Store authorizedScopes attribute for client_credentials grant
Issue gh-213
2021-02-10 19:37:14 -05:00
Joe Grandja
6ffda38cb9 OAuth2AccessToken.scopes includes authorized or requested scopes
Closes gh-224
2021-02-10 15:45:26 -05:00
Joe Grandja
09846eebeb InMemoryOAuth2AuthorizationService.save() supports insert and update
Related gh-220

Closes gh-222
2021-02-10 13:36:52 -05:00
Joe Grandja
c9afc3e061 Set iss claim in Jwt using configured issuer
Closes gh-223
2021-02-10 08:47:19 -05:00
Joe Grandja
afd5491ced Improve RegisteredClient model
Closes gh-221
2021-02-09 20:48:23 -05:00
Joe Grandja
313b4cc5d3 Add OAuth2Authorization.id
Closes gh-220
2021-02-09 15:38:27 -05:00
Joe Grandja
3c6571044d Rename TokenType to OAuth2TokenType
Closes gh-219
2021-02-09 13:29:06 -05:00
Joe Grandja
2f1684d44b Polish gh-213 2021-02-09 04:55:31 -05:00
Joe Grandja
2cdb7ef0fc Remove OAuth2AuthorizationAttributeNames
Issue gh-213
2021-02-09 03:51:00 -05:00
Joe Grandja
ee1b46b9a6 Remove OAuth2AuthorizationAttributeNames.PRINCIPAL
Issue gh-213
2021-02-08 21:14:33 -05:00
Joe Grandja
cee5aacc15 Remove OAuth2AuthorizationAttributeNames.STATE
Issue gh-213
2021-02-08 20:47:14 -05:00
Joe Grandja
fd9df9e2e7 Remove OAuth2AuthorizationAttributeNames.ACCESS_TOKEN_ATTRIBUTES
Issue gh-213
2021-02-08 20:34:44 -05:00
Joe Grandja
7261b40cd5 Add OAuth2Authorization.authorizationGrantType
Issue gh-213
2021-02-08 19:36:14 -05:00
Joe Grandja
41541912e6 Remove OAuth2AuthorizationAttributeNames.CODE
Issue gh-213
2021-02-08 19:36:14 -05:00
Joe Grandja
bffcbc5440 Improve OAuth2Authorization model
This commit removes OAuth2Tokens and OAuth2TokenMetadata and consolidates the code into OAuth2Authorization.

Closes gh-213
2021-02-08 19:36:14 -05:00
Joe Grandja
218d49b134 Introduce base Authentication for authorization grant
Closes gh-216
2021-02-05 11:48:21 -05:00
Joe Grandja
1fa0161164 Add JoseHeader.builder()
Closes gh-215
2021-02-05 05:43:28 -05:00
Joe Grandja
adf96b4e25 Add OAuth2TokenCustomizer
Closes gh-199
2021-02-04 13:57:37 -05:00
Joe Grandja
3f310eec00 Polish gh-201 2021-01-29 10:42:56 -05:00
Florian Berthe
aeab08579a Use configuration from ProviderSettings in OAuth2AuthorizationServerConfigurer
Closes gh-182
2021-01-29 08:21:53 -05:00
Joe Grandja
8e5e5873f5 Update to spring-build-conventions 0.0.37 2021-01-26 13:34:19 -05:00
Joe Grandja
39ed820560 Add https://repo.spring.io/release to reference build 2021-01-22 09:02:44 -05:00
Joe Grandja
5b8d0c3301 Upgrade to spring-build-conventions 0.0.36 2021-01-22 08:50:08 -05:00
Joe Grandja
698d45cdbd Upgrade to Gradle Enterprise Plugin 3.5.1 2021-01-22 08:46:13 -05:00
Joe Grandja
dc2fe30570 Add link to feature list 2021-01-21 05:26:32 -05:00
Gerardo Roza
4bcc1afac7 OAuth2TokenRevocationAuthenticationProvider ignores token_type_hint
Closes gh-175
2021-01-20 10:25:20 -05:00
Joe Grandja
17c20e98d4 Polish NimbusJwsEncoderTests
Issue gh-196
2021-01-19 05:22:51 -05:00
Joe Grandja
b7996e26d0 Fix NimbusJwkSetEndpointFilter
Closes gh-198
2021-01-19 04:39:04 -05:00
Joe Grandja
12f4001c9d Remove CryptoKeySource
Closes gh-196
2021-01-16 05:45:06 -05:00
Joe Grandja
4b37606807 Use jackson-bom:2.12.0 2021-01-15 12:54:49 -05:00
Joe Grandja
36e66bd732 Remove unused RegisteredClientRepository from OAuth2AuthorizationCodeAuthenticationProvider 2020-12-11 11:53:31 -05:00
Joe Grandja
42a89d15b1 Remove unused OAuth2AuthorizationService from OAuth2TokenEndpointFilter 2020-12-11 11:50:07 -05:00
Joe Grandja
259b55f682 Move OidcProviderConfigurationEndpointFilter
Issue gh-143
2020-12-11 11:30:35 -05:00
Joe Grandja
b6932ed25e Add artifactory credentials to build environment 2020-12-11 11:00:37 -05:00
Joe Grandja
f9f15227d8 Add artifactoryUsername/Password to check stage 2020-12-11 10:45:55 -05:00
Joe Grandja
668bb069f2 Update sample to use OpenID Connect and Provider Configuration endpoint
Issue gh-53 gh-55
2020-12-10 15:47:12 -05:00
Joe Grandja
f2bb523105 Add OpenID Connect 1.0 Authorization Code Flow
Closes gh-53
2020-12-10 15:47:12 -05:00
Joe Grandja
8c71e56350 Polish gh-168 2020-12-08 14:12:29 -05:00
Laurentiu Spilca
7c7e664bb7 Refresh token not issued when grant type not configured
Closes gh-155
2020-12-08 13:52:50 -05:00
Laurentiu Spilca
7fae37f0b5 Ensure refresh token is not revoked
Closes gh-158
2020-12-08 11:14:06 -05:00
Joe Grandja
7f8aff7982 Ignore unknown token_type_hint
Closes gh-174
2020-12-08 08:35:29 -05:00
Joe Grandja
f077337e43 Use TokenSettings.accessTokenTimeToLive()
Closes gh-172
2020-12-08 06:02:42 -05:00
Joe Grandja
79f1cf5a50 Allow customizing Jwt claims and headers
Closes gh-173
2020-12-07 16:41:17 -05:00
Joe Grandja
f97b8b2656 Apply consistent naming to builder() methods
Issue gh-143 gh-81
2020-11-30 08:15:44 -05:00
Joe Grandja
4e4656f7bb Apply consistent naming for jwkSet
Issue gh-143
2020-11-30 07:50:05 -05:00
Joe Grandja
eb97e12f56 Rename OidcProviderMetadataClaimAccessor.getJwksUri() -> getJwkSetUri()
Issue gh-143
2020-11-27 14:09:51 -05:00
Joe Grandja
ab591dc39d Polish gh-143 2020-11-25 05:51:47 -05:00
Daniel Garnier-Moiroux
6a5e277a11 Implement OpenID Provider Configuration endpoint
- See https://openid.net/specs/openid-connect-discovery-1_0.html
  sections 3 and 4.
- We introduce here a "ProviderSettings" construct to configure
  the authorization server, starting with endpoint paths (e.g.
  token endpoint, jwk set endpont, ...)

Closes gh-55
2020-11-25 05:50:17 -05:00
Joe Grandja
43fbd9d345 Register SecurityFilterChain in sample
Issue gh-163
2020-11-19 15:05:25 -05:00
Joe Grandja
d97235d0bb Register SecurityFilterChain instead of WebSecurityConfigurerAdapter
Closes gh-163
2020-11-19 14:27:18 -05:00
Joe Grandja
9f246fc304 Update to spring-build-conventions:0.0.35.BUILD-SNAPSHOT 2020-11-19 06:30:41 -05:00
Joe Grandja
6db194da27 Use artifactoryUsername/Password for plugin repositories 2020-11-19 06:30:41 -05:00
Joe Grandja
f0ecb5b93f Update to Spring Boot 2.4.0 2020-11-19 06:06:32 -05:00
Joe Grandja
c1e9c1d76c Change JwtClaimsSet.Builder.issuer() to String
Issue gh-81
2020-11-16 19:42:11 -05:00
Joe Grandja
a9423c6b13 Replace ManagedKey with CryptoKey
Closes gh-105
2020-11-13 14:59:35 -05:00
Joe Grandja
8100568613 Polish gh-140 2020-11-10 15:34:47 -05:00
Anoop Garlapati
e1f491bd61 Add client secret POST authentication method support
Added support for client secret POST authentication method.
Added validation of client authentication method when
authenticating a client.

Closes gh-134
2020-11-10 16:56:29 +05:30
Joe Grandja
90fbbea126 Next Development Version 2020-11-09 15:12:23 -05:00
Joe Grandja
d6fc405bb1 Revert "Lock Dependency Versions for 0.0.3 release"
This reverts commit 6e2f2fe8a4.
2020-11-09 15:10:17 -05:00
Joe Grandja
db4dd3f08e Release 0.0.3 2020-11-09 14:51:23 -05:00
Joe Grandja
6e2f2fe8a4 Lock Dependency Versions for 0.0.3 release 2020-11-09 14:49:27 -05:00
Joe Grandja
061d9f8c18 Remove spring-security.version from sample 2020-11-09 14:18:36 -05:00
Joe Grandja
59f77d034e Downgrade springSecurityVersion to 5.4.+ 2020-11-09 14:11:00 -05:00
Joe Grandja
bf24cfb19e Add temporary OAuth2RefreshToken2
Issue https://github.com/spring-projects/spring-security/pull/9146
2020-11-09 14:09:34 -05:00
Joe Grandja
77a9b2ebf3 Add temporary OAuth2ErrorCodes2
Issue https://github.com/spring-projects/spring-security/issues/9184
2020-11-09 14:09:34 -05:00
Joe Grandja
d76d209124 Add temporary OAuth2ParameterNames2
Issue https://github.com/spring-projects/spring-security/issues/9183
2020-11-09 14:09:34 -05:00
Joe Grandja
58ad2d2c6c Revert "Set springSecurityVersion to 5.5.0-M1"
This reverts commit 7e2264204b.
2020-11-09 06:52:19 -05:00
Joe Grandja
1a6b3e3e59 Revert "Set springBootVersion to 2.4.0-RC1"
This reverts commit 43b44a1f77.
2020-11-09 05:57:02 -05:00
Joe Grandja
e61639bf7f Set spring-security.version to 5.5.0-M1 in sample 2020-11-09 05:42:27 -05:00
Joe Grandja
7e2264204b Set springSecurityVersion to 5.5.0-M1 2020-11-09 05:30:35 -05:00
Joe Grandja
43b44a1f77 Set springBootVersion to 2.4.0-RC1 2020-11-09 05:11:14 -05:00
Joe Grandja
19d6e97372 Revert "Use reactor-netty-http for snapshot build"
Issue https://github.com/spring-projects/spring-security/issues/8909
2020-11-09 05:10:37 -05:00
Joe Grandja
cff7b786de Set reactorVersion to 2020.0.+ 2020-11-09 05:07:48 -05:00
Joe Grandja
edf23562cb Set springVersion to 5.3.+ 2020-11-09 05:06:45 -05:00
Joe Grandja
e7909d0cdd Update javadoc OAuth2TokenEndpointFilter 2020-11-05 16:57:51 -05:00
Joe Grandja
e49d4a79b4 Polish PublicClientAuthenticationConverter
Commit 5c31fb1b7e
2020-11-05 15:54:24 -05:00
Joe Grandja
7720e275e4 Polish OAuth2ClientAuthenticationProvider
Commit 5c31fb1b7e
2020-11-05 15:23:50 -05:00
Joe Grandja
6a2c841d06 Update OAuth2TokenMetadata.TOKEN_METADATA_BASE
Issue gh-137
2020-11-04 15:39:55 -05:00
Joe Grandja
d7fe79d0ec Update TokenSettings.TOKEN_SETTING_BASE
Issue gh-117
2020-11-04 15:38:48 -05:00
Joe Grandja
40ca7a4654 Update ClientSettings.CLIENT_SETTING_BASE
Issue gh-117
2020-11-04 15:38:05 -05:00
Joe Grandja
06bf391bfa Update javadoc InMemoryOAuth2AuthorizationService
Issue gh-43
2020-11-04 14:49:55 -05:00
Joe Grandja
bfb5432b46 Update javadoc InMemoryRegisteredClientRepository
Issue gh-40
2020-11-04 14:49:55 -05:00
Joe Grandja
9818618ea3 Reuse client authentication assertion
Closes gh-144
2020-11-04 09:28:55 -05:00
Joe Grandja
cb09aef605 Use OAuth2ErrorCodes.UNSUPPORTED_TOKEN_TYPE
Issue gh-83
2020-11-04 07:36:37 -05:00
Joe Grandja
ebcdf7989d Use OAuth2ParameterNames.TOKEN
Issue gh-83
2020-11-03 20:51:46 -05:00
Joe Grandja
df8793c902 Polish tests gh-84 2020-11-02 18:55:43 -05:00
Joe Grandja
6c7486429c Polish gh-88 2020-11-02 18:49:35 -05:00
Joe Grandja
cf82c06502 Polish tests gh-128 2020-11-02 18:43:30 -05:00
Joe Grandja
a2167a5091 Polish gh-128 2020-10-30 11:27:27 -04:00
Alexey Nesterov
78d4bd0bad Add Refresh Token grant type support
Closes gh-50
2020-10-30 11:26:51 -04:00
Alexey Nesterov
1ce77d3caa Bump Spring Security to 5.5 for sample app 2020-10-29 11:16:28 +00:00
Joe Grandja
b7ddb837d6 Polish gh-84 2020-10-28 16:03:17 -04:00
Vivek Babu
dc94e5e161 Implement Token Revocation Endpoint
Closes gh-83
2020-10-24 18:45:46 -04:00
Joe Grandja
18f8b3afaa Enforce one-time use for authorization code
Closes gh-138
2020-10-22 19:43:09 -04:00
Joe Grandja
601640e4fa Set springSecurityVersion to 5.5.+ 2020-10-22 13:41:34 -04:00
Joe Grandja
af60f3d4d0 Introduce OAuth2Tokens
Closes gh-137
2020-10-20 14:43:59 -04:00
Joe Grandja
7b1b965c08 Next Development Version 2020-10-15 05:01:05 -04:00
Joe Grandja
0c6b1251ce Revert "Lock Dependency Versions for 0.0.2 release"
This reverts commit 5471c94615.
2020-10-15 04:59:49 -04:00
Joe Grandja
ad00d78e1a Release 0.0.2 2020-10-15 04:35:22 -04:00
Joe Grandja
5471c94615 Lock Dependency Versions for 0.0.2 release 2020-10-15 04:30:54 -04:00
Walid EL ALAOUY
84cdf82c0d Rename and move SpringSecurityCoreVersion2 to Version
Closes gh-116
2020-10-13 15:51:01 -04:00
Joe Grandja
45283b42b4 Expose default security configuration
Closes gh-110
2020-10-13 14:32:25 -04:00
Joe Grandja
142876068d Use jackson-bom 2020-10-13 11:40:45 -04:00
Joe Grandja
ca94d02abc Settings.setting() supports generic return type
Issue gh-117
2020-10-13 05:17:39 -04:00
Joe Grandja
628c8bece3 Polish gh-117 2020-10-09 16:08:00 -04:00
Joe Grandja
f0013fc062 Add @Nullable to Delegating*Converter.convert()
Issue gh-88, gh-45
2020-10-09 15:38:49 -04:00
Joe Grandja
aa5133e170 Add user consent page
Closes gh-42
2020-10-09 15:01:44 -04:00
Joe Grandja
5c31fb1b7e Move PKCE to OAuth2ClientAuthenticationProvider
PR gh-93
2020-10-05 21:14:47 -04:00
Joe Grandja
e5fdee3034 Polish gh-93 2020-10-02 04:03:04 -04:00
Daniel Garnier-Moiroux
ab090445b3 Implement Proof Key for Code Exchange (PKCE) RFC 7636
See https://tools.ietf.org/html/rfc7636

Closes gh-45
2020-09-30 15:27:37 -04:00
Joe Grandja
8541f6be69 Update to Spring Boot 2.4.0-M3
Closes gh-123
2020-09-25 08:13:46 -04:00
Joe Grandja
ae20f73676 Update com.nimbusds dependencies to latest.release
Issue gh-113
2020-09-21 13:45:56 -04:00
Joe Grandja
4091d69d0c Set springSecurityVersion to 5.4.+
Closes gh-119
2020-09-21 13:45:40 -04:00
Joe Grandja
c3b254579c Add client configuration settings
Closes gh-117
2020-09-14 21:02:21 -04:00
Joe Grandja
22bf1eb951 Bump SpringSecurityCoreVersion2 to 0.0.2 2020-09-14 20:50:35 -04:00
Joe Grandja
35ecdae190 Add initial template for reference manual
Issue gh-107
2020-09-08 14:35:55 -04:00
Joe Grandja
b12ffe2ae4 Constrain version for com.nimbusds:nimbus-jose-jwt
Closes gh-113
2020-09-08 11:48:15 -04:00
Joe Grandja
117b312ac4 Integrate io.spring.convention.docs plugin
Closes gh-107
2020-09-03 14:35:44 -04:00
Joe Grandja
f7c84957bb Disable proxyBeanMethods for OAuth2AuthorizationServerConfiguration
Issue gh-91
2020-09-03 06:39:58 -04:00
Joe Grandja
5a030568ce Order highest precedence for OAuth2AuthorizationServerSecurity
Closes gh-103
2020-09-03 06:05:42 -04:00
Joe Grandja
72ec2633f8 Remove formLogin() from client sample app
Closes gh-108
2020-09-02 14:52:41 -04:00
Joe Grandja
0b95672c47 Add release scripts 2020-08-20 15:10:29 -04:00
Joe Grandja
d51f12f5d2 Next Development Version 2020-08-20 14:41:45 -04:00
Joe Grandja
f3a27101be Revert "Lock Dependency Versions for 0.0.1 release"
This reverts commit cde34a9ed5.
2020-08-20 14:40:29 -04:00
494 changed files with 49181 additions and 8021 deletions

View File

@@ -3,6 +3,3 @@ contact_links:
- name: Community Support
url: https://stackoverflow.com/questions/tagged/spring-security
about: Please ask and answer questions on StackOverflow with the tag `spring-security`.
- name: Security Issues
url: https://pivotal.io/security#reporting
about: Please report security vulnerabilities here.

View File

@@ -1,54 +1,89 @@
name: CI
name: Build and Deploy
on:
push:
branches:
- master
- '**'
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.8.+' -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

@@ -3,7 +3,7 @@ name: PR build
on:
pull_request:
branches:
- master
- '**'
jobs:
build:
@@ -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.

103
Jenkinsfile vendored
View File

@@ -1,103 +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_CREDENTIALS = usernamePassword(credentialsId: 'oss-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_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([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 --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_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' -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_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,32 +1,33 @@
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=master["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 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:
- https://github.com/spring-projects/spring-security/tree/master/oauth2/oauth2-core[OAuth 2.0 Core]
- https://github.com/spring-projects/spring-security/tree/master/oauth2/oauth2-client[OAuth 2.0 Client]
- https://github.com/spring-projects/spring-security/tree/master/oauth2/oauth2-resource-server[OAuth 2.0 Resource Server]
- https://github.com/spring-projects/spring-security/tree/master/oauth2/oauth2-jose[OAuth 2.0 JOSE] (Javascript Object Signing and Encryption)
- https://github.com/spring-projects/spring-security/tree/main/oauth2/oauth2-core[OAuth 2.0 Core]
- https://github.com/spring-projects/spring-security/tree/main/oauth2/oauth2-client[OAuth 2.0 Client]
- https://github.com/spring-projects/spring-security/tree/main/oauth2/oauth2-resource-server[OAuth 2.0 Resource Server]
- https://github.com/spring-projects/spring-security/tree/main/oauth2/oauth2-jose[OAuth 2.0 JOSE] (Javascript Object Signing and Encryption)
A significant amount of effort was put into developing the https://spring.io/blog/2018/01/30/next-generation-oauth-2-0-support-with-spring-security[Next Generation OAuth 2.0 Support in Spring Security].
The goal is to leverage all the knowledge learned thus far and apply the same to the development of Spring Authorization Server.
@@ -34,16 +35,16 @@ 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-authorization-server/docs/current/reference/html/[Spring Authorization Server Reference] and 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].
JavaDoc is also available for the https://docs.spring.io/spring-authorization-server/docs/current/api/[Spring Authorization Server API] and https://docs.spring.io/spring-security/site/docs/current/api/[Spring Security API].
== Code of Conduct
This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code of conduct].
By participating, you are expected to uphold this code. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
== Downloading Artifacts
See https://github.com/spring-projects/spring-framework/wiki/Downloading-Spring-artifacts[downloading Spring artifacts] for Maven repository information.
See https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Artifacts[downloading Spring artifacts] for Maven repository information.
== Building from Source
Spring Authorization Server uses a https://gradle.org[Gradle]-based build system.
@@ -58,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,37 +1,13 @@
buildscript {
dependencies {
classpath 'io.spring.gradle:spring-build-conventions:0.0.33.RELEASE'
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
classpath 'io.spring.nohttp:nohttp-gradle:0.0.5.RELEASE'
}
repositories {
maven { url 'https://repo.spring.io/plugins-snapshot' }
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'
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.23"
}

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.4")
constraints {
api "com.nimbusds:nimbus-jose-jwt:9.24.4"
api "javax.servlet:javax.servlet-api:4.0.1"
api "junit:junit:4.13.2"
api "org.assertj:assertj-core:3.23.1"
api "org.mockito:mockito-core:4.8.0"
api "com.squareup.okhttp3:mockwebserver:4.10.0"
api "com.squareup.okhttp3:okhttp:4.10.0"
api "com.jayway.jsonpath:json-path:2.7.0"
api "org.hsqldb:hsqldb:2.5.2"
}
}

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,233 @@
[[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 authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.registeredClientRepository(registeredClientRepository) <1>
.authorizationService(authorizationService) <2>
.authorizationConsentService(authorizationConsentService) <3>
.authorizationServerSettings(authorizationServerSettings) <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> `authorizationServerSettings()`: The <<configuring-authorization-server-settings, `AuthorizationServerSettings`>> (*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-authorization-server-settings]]
== Configuring Authorization Server Settings
`AuthorizationServerSettings` contains the configuration settings for the OAuth2 authorization server.
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 AuthorizationServerSettings 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]
`AuthorizationServerSettings` is a *REQUIRED* component.
[TIP]
<<default-configuration, `@Import(OAuth2AuthorizationServerConfiguration.class)`>> automatically registers an `AuthorizationServerSettings` `@Bean`, if not already provided.
The following example shows how to customize the configuration settings and register an `AuthorizationServerSettings` `@Bean`:
[source,java]
----
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.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 `AuthorizationServerContext` is a context object that holds information of the Authorization Server runtime environment.
It provides access to the `AuthorizationServerSettings` and the "`current`" issuer identifier.
[NOTE]
If the issuer identifier is not configured in `AuthorizationServerSettings.builder().issuer(String)`, it is resolved from the current request.
[NOTE]
The `AuthorizationServerContext` is accessible through the `AuthorizationServerContextHolder`, which associates it with the current request thread by using a `ThreadLocal`.
[[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 authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.clientAuthentication(clientAuthentication ->
clientAuthentication
.authenticationConverter(authenticationConverter) <1>
.authenticationConverters(authenticationConvertersConsumer) <2>
.authenticationProvider(authenticationProvider) <3>
.authenticationProviders(authenticationProvidersConsumer) <4>
.authenticationSuccessHandler(authenticationSuccessHandler) <5>
.errorResponseHandler(errorResponseHandler) <6>
);
return http.build();
}
----
<1> `authenticationConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract client credentials from `HttpServletRequest` to an instance of `OAuth2ClientAuthenticationToken`.
<2> `authenticationConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2ClientAuthenticationToken`.
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
<5> `authenticationSuccessHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling a successful client authentication and associating the `OAuth2ClientAuthenticationToken` to the `SecurityContext`.
<6> `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,493 @@
[[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 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 Set<String> authorizedScopes; <5>
private Map<Class<? extends OAuth2Token>, Token<?>> tokens; <6>
private Map<String, Object> attributes; <7>
...
}
----
<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> `authorizedScopes`: The `Set` of scope(s) authorized for the client.
<6> `tokens`: The `OAuth2Token` instances (and associated metadata) specific to the executed authorization grant type.
<7> `attributes`: The additional attributes specific to the executed authorization grant type for example, the authenticated `Principal`, `OAuth2AuthorizationRequest`, 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 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 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 AuthorizationServerContext getAuthorizationServerContext() ... <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> `getAuthorizationServerContext()`: The xref:configuration-model.adoc#configuring-authorization-server-settings[`AuthorizationServerContext`] object that holds information of the Authorization Server runtime environment.
<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 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.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.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
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 AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
}
}

View File

@@ -0,0 +1,282 @@
/*
* 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 = 1000)
private String authorizedScopes;
@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 getAuthorizedScopes() {
return this.authorizedScopes;
}
public void setAuthorizedScopes(String authorizedScopes) {
this.authorizedScopes = authorizedScopes;
}
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,263 @@
/*
* 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.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.OAuth2Token;
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.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
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()))
.authorizedScopes(StringUtils.commaDelimitedListToSet(entity.getAuthorizedScopes()))
.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.setAuthorizedScopes(StringUtils.collectionToDelimitedString(authorization.getAuthorizedScopes(), ","));
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.jackson2.OAuth2AuthorizationServerJackson2Module;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
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.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.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
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 AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.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.server.authorization.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.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.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
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.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
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 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 AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.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.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.OAuth2TokenType;
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.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
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,148 @@
/*
* 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.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;
/**
* @author Joe Grandja
*/
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.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.OAuth2TokenType;
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.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
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,151 @@
/*
* 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.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;
/**
* @author Rob Winch
*/
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 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,56 @@
/*
* 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.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;
/**
* @author Rob Winch
*/
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.settings.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-authorization-server-settings[`AuthorizationServerSettings`] 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,406 @@
[[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 authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.authorizationEndpoint(authorizationEndpoint ->
authorizationEndpoint
.authorizationRequestConverter(authorizationRequestConverter) <1>
.authorizationRequestConverters(authorizationRequestConvertersConsumer) <2>
.authenticationProvider(authenticationProvider) <3>
.authenticationProviders(authenticationProvidersConsumer) <4>
.authorizationResponseHandler(authorizationResponseHandler) <5>
.errorResponseHandler(errorResponseHandler) <6>
.consentPage("/oauth2/v1/authorize") <7>
);
return http.build();
}
----
<1> `authorizationRequestConverter()`: Adds an `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` or `OAuth2AuthorizationConsentAuthenticationToken`.
<2> `authorizationRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2AuthorizationCodeRequestAuthenticationToken` or `OAuth2AuthorizationConsentAuthenticationToken`.
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
<5> `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].
<6> `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].
<7> `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*` -- A `DelegatingAuthenticationConverter` composed of `OAuth2AuthorizationCodeRequestAuthenticationConverter` and `OAuth2AuthorizationConsentAuthenticationConverter`.
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OAuth2AuthorizationCodeRequestAuthenticationProvider` and `OAuth2AuthorizationConsentAuthenticationProvider`.
* `*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 authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.tokenEndpoint(tokenEndpoint ->
tokenEndpoint
.accessTokenRequestConverter(accessTokenRequestConverter) <1>
.accessTokenRequestConverters(accessTokenRequestConvertersConsumer) <2>
.authenticationProvider(authenticationProvider) <3>
.authenticationProviders(authenticationProvidersConsumer) <4>
.accessTokenResponseHandler(accessTokenResponseHandler) <5>
.errorResponseHandler(errorResponseHandler) <6>
);
return http.build();
}
----
<1> `accessTokenRequestConverter()`: Adds an `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> `accessTokenRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2AuthorizationGrantAuthenticationToken`.
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
<5> `accessTokenResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an `OAuth2AccessTokenAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-5.1[`OAuth2AccessTokenResponse`].
<6> `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 authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.tokenIntrospectionEndpoint(tokenIntrospectionEndpoint ->
tokenIntrospectionEndpoint
.introspectionRequestConverter(introspectionRequestConverter) <1>
.introspectionRequestConverters(introspectionRequestConvertersConsumer) <2>
.authenticationProvider(authenticationProvider) <3>
.authenticationProviders(authenticationProvidersConsumer) <4>
.introspectionResponseHandler(introspectionResponseHandler) <5>
.errorResponseHandler(errorResponseHandler) <6>
);
return http.build();
}
----
<1> `introspectionRequestConverter()`: Adds an `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> `introspectionRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2TokenIntrospectionAuthenticationToken`.
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
<5> `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].
<6> `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 `OAuth2TokenIntrospectionAuthenticationConverter`.
* `*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 authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.tokenRevocationEndpoint(tokenRevocationEndpoint ->
tokenRevocationEndpoint
.revocationRequestConverter(revocationRequestConverter) <1>
.revocationRequestConverters(revocationRequestConvertersConsumer) <2>
.authenticationProvider(authenticationProvider) <3>
.authenticationProviders(authenticationProvidersConsumer) <4>
.revocationResponseHandler(revocationResponseHandler) <5>
.errorResponseHandler(errorResponseHandler) <6>
);
return http.build();
}
----
<1> `revocationRequestConverter()`: Adds an `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> `revocationRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2TokenRevocationAuthenticationToken`.
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
<5> `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].
<6> `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 `OAuth2TokenRevocationAuthenticationConverter`.
* `*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
`OAuth2AuthorizationServerMetadataEndpointConfigurer` provides the ability to customize the https://datatracker.ietf.org/doc/html/rfc8414#section-3[OAuth2 Authorization Server Metadata endpoint].
It defines an extension point that lets you customize the https://datatracker.ietf.org/doc/html/rfc8414#section-3.2[OAuth2 Authorization Server Metadata response].
`OAuth2AuthorizationServerMetadataEndpointConfigurer` provides the following configuration option:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.authorizationServerMetadataEndpoint(authorizationServerMetadataEndpoint ->
authorizationServerMetadataEndpoint
.authorizationServerMetadataCustomizer(authorizationServerMetadataCustomizer)); <1>
return http.build();
}
----
<1> `authorizationServerMetadataCustomizer()`: The `Consumer` providing access to the `OAuth2AuthorizationServerMetadata.Builder` allowing the ability to customize the claims of the Authorization Server's configuration.
`OAuth2AuthorizationServerMetadataEndpointConfigurer` configures the `OAuth2AuthorizationServerMetadataEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OAuth2AuthorizationServerMetadataEndpointFilter` is the `Filter` that 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
`OidcProviderConfigurationEndpointConfigurer` provides the ability to customize the https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[OpenID Connect 1.0 Provider Configuration endpoint].
It defines an extension point that lets you customize the https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse[OpenID Provider Configuration response].
`OidcProviderConfigurationEndpointConfigurer` provides the following configuration option:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.oidc(oidc ->
oidc
.providerConfigurationEndpoint(providerConfigurationEndpoint ->
providerConfigurationEndpoint
.providerConfigurationCustomizer(providerConfigurationCustomizer) <1>
)
);
return http.build();
}
----
<1> `providerConfigurationCustomizer()`: The `Consumer` providing access to the `OidcProviderConfiguration.Builder` allowing the ability to customize the claims of the OpenID Provider's configuration.
`OidcProviderConfigurationEndpointConfigurer` 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 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 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 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 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.0.1
springBootVersion=2.4.0-M2
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
version=0.4.0-M2
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
org.gradle.caching=true
springFrameworkVersion=5.3.23
springSecurityVersion=5.8.0-M3
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,67 +0,0 @@
if (!project.hasProperty("springVersion")) {
ext.springVersion = "5.2.+"
}
if (!project.hasProperty("springSecurityVersion")) {
ext.springSecurityVersion = "5.3.+"
}
if (!project.hasProperty("reactorVersion")) {
ext.reactorVersion = "Dysprosium-SR+"
}
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"
}
dependencies {
dependency "com.nimbusds:oauth2-oidc-sdk:latest.release"
dependency "com.nimbusds:nimbus-jose-jwt:latest.release"
dependency "com.fasterxml.jackson.core:jackson-databind:2.+"
dependency "javax.servlet:javax.servlet-api:4.+"
dependency 'junit:junit:latest.release'
dependency 'org.assertj:assertj-core:latest.release'
dependency 'org.mockito:mockito-core:latest.release'
dependency "com.squareup.okhttp3:mockwebserver:3.+"
dependency "com.squareup.okhttp3:okhttp:3.+"
dependency "com.jayway.jsonpath:json-path:2.+"
}
}
/*
NOTE:
The latest `reactor-netty` dependency was split into `reactor-netty-core` and `reactor-netty-http`,
which resulted in the snapshot build to fail. The below configuration fixes it.
Reference:
- https://github.com/spring-projects/spring-security/issues/8909
- https://github.com/reactor/reactor-netty/issues/739#issuecomment-667047117
*/
if (reactorVersion.startsWith('20')) {
if (reactorVersion.endsWith('SNAPSHOT') || reactorVersion.endsWith('+')) {
ext.reactorLatestVersion = "latest.integration"
} else {
ext.reactorLatestVersion = "latest.release"
}
configurations {
all {
resolutionStrategy {
eachDependency { DependencyResolveDetails details ->
if (details.requested.name == 'reactor-netty') {
details.useTarget("${details.requested.group}:reactor-netty-http:${reactorLatestVersion}")
details.because("reactor-netty is now split into reactor-netty-core and reactor-netty-http")
}
}
}
}
}
}

View File

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

View File

@@ -1,3 +0,0 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.

View File

@@ -1,3 +0,0 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.

View File

@@ -1,23 +0,0 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
antlr:antlr:2.7.7
ch.qos.logback:logback-classic:1.2.3
ch.qos.logback:logback-core:1.2.3
com.google.code.findbugs:jsr305:3.0.2
com.google.errorprone:error_prone_annotations:2.2.0
com.google.guava:failureaccess:1.0.1
com.google.guava:guava:27.1-jre
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava
com.google.j2objc:j2objc-annotations:1.1
com.puppycrawl.tools:checkstyle:8.22
commons-beanutils:commons-beanutils:1.9.3
commons-collections:commons-collections:3.2.2
info.picocli:picocli:3.9.6
io.spring.javaformat:spring-javaformat-checkstyle:0.0.15
io.spring.nohttp:nohttp-checkstyle:0.0.3.RELEASE
io.spring.nohttp:nohttp:0.0.3.RELEASE
net.sf.saxon:Saxon-HE:9.9.1-3
org.antlr:antlr4-runtime:4.7.2
org.checkerframework:checker-qual:2.5.2
org.codehaus.mojo:animal-sniffer-annotations:1.17

View File

@@ -1,23 +0,0 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.fasterxml.jackson.core:jackson-annotations:2.11.2
com.fasterxml.jackson.core:jackson-core:2.11.2
com.fasterxml.jackson.core:jackson-databind:2.11.2
com.github.stephenc.jcip:jcip-annotations:1.0-1
com.nimbusds:nimbus-jose-jwt:8.20
net.minidev:accessors-smart:1.2
net.minidev:json-smart:2.3
org.ow2.asm:asm:5.0.4
org.springframework.security:spring-security-config:5.3.4.RELEASE
org.springframework.security:spring-security-core:5.3.4.RELEASE
org.springframework.security:spring-security-oauth2-core:5.3.4.RELEASE
org.springframework.security:spring-security-oauth2-jose:5.3.4.RELEASE
org.springframework.security:spring-security-web:5.3.4.RELEASE
org.springframework:spring-aop:5.2.8.RELEASE
org.springframework:spring-beans:5.2.8.RELEASE
org.springframework:spring-context:5.2.8.RELEASE
org.springframework:spring-core:5.2.8.RELEASE
org.springframework:spring-expression:5.2.8.RELEASE
org.springframework:spring-jcl:5.2.8.RELEASE
org.springframework:spring-web:5.2.8.RELEASE

View File

@@ -1,23 +0,0 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.fasterxml.jackson.core:jackson-annotations:2.11.2
com.fasterxml.jackson.core:jackson-core:2.11.2
com.fasterxml.jackson.core:jackson-databind:2.11.2
com.github.stephenc.jcip:jcip-annotations:1.0-1
com.nimbusds:nimbus-jose-jwt:8.20
net.minidev:accessors-smart:1.2
net.minidev:json-smart:2.3
org.ow2.asm:asm:5.0.4
org.springframework.security:spring-security-config:5.3.4.RELEASE
org.springframework.security:spring-security-core:5.3.4.RELEASE
org.springframework.security:spring-security-oauth2-core:5.3.4.RELEASE
org.springframework.security:spring-security-oauth2-jose:5.3.4.RELEASE
org.springframework.security:spring-security-web:5.3.4.RELEASE
org.springframework:spring-aop:5.2.8.RELEASE
org.springframework:spring-beans:5.2.8.RELEASE
org.springframework:spring-context:5.2.8.RELEASE
org.springframework:spring-core:5.2.8.RELEASE
org.springframework:spring-expression:5.2.8.RELEASE
org.springframework:spring-jcl:5.2.8.RELEASE
org.springframework:spring-web:5.2.8.RELEASE

View File

@@ -1,3 +0,0 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.

View File

@@ -1,23 +0,0 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.fasterxml.jackson.core:jackson-annotations:2.11.2
com.fasterxml.jackson.core:jackson-core:2.11.2
com.fasterxml.jackson.core:jackson-databind:2.11.2
com.github.stephenc.jcip:jcip-annotations:1.0-1
com.nimbusds:nimbus-jose-jwt:8.20
net.minidev:accessors-smart:1.2
net.minidev:json-smart:2.3
org.ow2.asm:asm:5.0.4
org.springframework.security:spring-security-config:5.3.4.RELEASE
org.springframework.security:spring-security-core:5.3.4.RELEASE
org.springframework.security:spring-security-oauth2-core:5.3.4.RELEASE
org.springframework.security:spring-security-oauth2-jose:5.3.4.RELEASE
org.springframework.security:spring-security-web:5.3.4.RELEASE
org.springframework:spring-aop:5.2.8.RELEASE
org.springframework:spring-beans:5.2.8.RELEASE
org.springframework:spring-context:5.2.8.RELEASE
org.springframework:spring-core:5.2.8.RELEASE
org.springframework:spring-expression:5.2.8.RELEASE
org.springframework:spring-jcl:5.2.8.RELEASE
org.springframework:spring-web:5.2.8.RELEASE

View File

@@ -1,4 +0,0 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
org.jacoco:org.jacoco.agent:0.8.5

View File

@@ -1,11 +0,0 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
org.jacoco:org.jacoco.agent:0.8.5
org.jacoco:org.jacoco.ant:0.8.5
org.jacoco:org.jacoco.core:0.8.5
org.jacoco:org.jacoco.report:0.8.5
org.ow2.asm:asm-analysis:7.2
org.ow2.asm:asm-commons:7.2
org.ow2.asm:asm-tree:7.2
org.ow2.asm:asm:7.2

View File

@@ -1,3 +0,0 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.

View File

@@ -1,3 +0,0 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.

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