342 Commits
0.0.3 ... 0.2.3

Author SHA1 Message Date
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
419 changed files with 39288 additions and 8990 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

@@ -3,7 +3,7 @@ name: CI
on:
push:
branches:
- master
- main
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC

View File

@@ -3,7 +3,7 @@ name: PR build
on:
pull_request:
branches:
- master
- main
jobs:
build:

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].
@@ -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.

12
Jenkinsfile vendored
View File

@@ -15,10 +15,11 @@ def GRADLE_ENTERPRISE_SECRET_ACCESS_KEY = string(credentialsId: 'gradle_enterpri
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 OSSRH_S01_CREDENTIALS = usernamePassword(credentialsId: 'oss-s01-token', passwordVariable: 'OSSRH_S01_TOKEN_PASSWORD', usernameVariable: 'OSSRH_S01_TOKEN_USERNAME')
def ARTIFACTORY_CREDENTIALS = usernamePassword(credentialsId: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD')
def JENKINS_PRIVATE_SSH_KEY = file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')
def SONAR_LOGIN_CREDENTIALS = string(credentialsId: 'spring-sonar.login', variable: 'SONAR_LOGIN')
def JENKINS_USER = '-Duser.name="spring-builds+jenkins"'
def jdkEnv(String jdk = 'jdk8') {
def jdkTool = tool(jdk)
@@ -32,13 +33,14 @@ try {
checkout scm
sh "git clean -dfx"
try {
withCredentials([GRADLE_ENTERPRISE_CACHE_USER,
withCredentials([ARTIFACTORY_CREDENTIALS,
GRADLE_ENTERPRISE_CACHE_USER,
GRADLE_ENTERPRISE_SECRET_ACCESS_KEY]) {
withEnv([jdkEnv(),
"GRADLE_ENTERPRISE_CACHE_USERNAME=${GRADLE_ENTERPRISE_CACHE_USERNAME}",
"GRADLE_ENTERPRISE_CACHE_PASSWORD=${GRADLE_ENTERPRISE_CACHE_PASSWORD}",
"GRADLE_ENTERPRISE_ACCESS_KEY=${GRADLE_ENTERPRISE_ACCESS_KEY}"]) {
sh "./gradlew check --stacktrace"
sh "./gradlew $JENKINS_USER check -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --stacktrace"
}
}
} catch(Exception e) {
@@ -59,7 +61,7 @@ try {
sh "git clean -dfx"
withCredentials([SPRING_SIGNING_SECRING,
SPRING_GPG_PASSPHRASE,
OSSRH_CREDENTIALS,
OSSRH_S01_CREDENTIALS,
ARTIFACTORY_CREDENTIALS,
GRADLE_ENTERPRISE_CACHE_USER,
GRADLE_ENTERPRISE_SECRET_ACCESS_KEY]) {
@@ -67,7 +69,7 @@ try {
"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"
sh "./gradlew $JENKINS_USER deployArtifacts finalizeDeployArtifacts -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password='$SIGNING_PASSWORD' -PossrhTokenUsername=$OSSRH_S01_TOKEN_USERNAME -PossrhTokenPassword=$OSSRH_S01_TOKEN_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --stacktrace"
}
}
}

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 completed and upcoming feature list can be viewed in the https://github.com/spring-projects/spring-authorization-server/wiki/Feature-List[wiki].
== 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,7 +35,7 @@ The goal is to leverage all the knowledge learned thus far and apply the same to
Submitted work via pull requests should follow the same coding style/conventions and adopt the same or similar design patterns that have been established in Spring Security's OAuth 2.0 support.
== Documentation
Be sure to read the https://docs.spring.io/spring-security/site/docs/current/reference/html5/[Spring Security Reference], as well as the https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2[OAuth 2.0 Reference], which describes the Client and Resource Server features available.
Be sure to read the https://docs.spring.io/spring-security/reference[Spring Security Reference], as well as the https://docs.spring.io/spring-security/reference/servlet/oauth2/index.html[OAuth 2.0 Reference], which describes the Client and Resource Server features available.
Extensive JavaDoc for the Spring Security code is also available in the https://docs.spring.io/spring-security/site/docs/current/api/[Spring Security API Documentation].
@@ -43,7 +44,7 @@ This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code
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,11 +1,19 @@
buildscript {
dependencies {
classpath 'io.spring.gradle:spring-build-conventions:0.0.33.RELEASE'
classpath 'io.spring.gradle:spring-build-conventions:0.0.38'
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
classpath 'io.spring.nohttp:nohttp-gradle:0.0.5.RELEASE'
classpath 'io.spring.nohttp:nohttp-gradle:0.0.8'
}
repositories {
maven { url 'https://repo.spring.io/plugins-snapshot' }
maven {
url = 'https://repo.spring.io/plugins-snapshot'
if (project.hasProperty('artifactoryUsername')) {
credentials {
username "$artifactoryUsername"
password "$artifactoryPassword"
}
}
}
maven { url 'https://plugins.gradle.org/m2/' }
}
}
@@ -14,7 +22,7 @@ apply plugin: 'io.spring.nohttp'
apply plugin: 'locks'
apply plugin: 'io.spring.convention.root'
group = 'org.springframework.security.experimental'
group = 'org.springframework.security'
description = 'Spring Authorization Server'
ext.snapshotBuild = version.contains("SNAPSHOT")

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,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.
io.spring.asciidoctor:spring-asciidoctor-extensions-block-switch:0.4.2.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,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,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,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.
io.spring.docresources:spring-doc-resources:0.2.1.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,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,13 +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.
junit:junit:4.13.1
net.bytebuddy:byte-buddy-agent:1.10.15
net.bytebuddy:byte-buddy:1.10.15
org.assertj:assertj-core:3.18.0
org.hamcrest:hamcrest-core:1.3
org.mockito:mockito-core:3.6.0
org.objenesis:objenesis:3.1
org.springframework:spring-core:5.3.0
org.springframework:spring-jcl:5.3.0
org.springframework:spring-test:5.3.0

View File

@@ -1,13 +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.
junit:junit:4.13.1
net.bytebuddy:byte-buddy-agent:1.10.15
net.bytebuddy:byte-buddy:1.10.15
org.assertj:assertj-core:3.18.0
org.hamcrest:hamcrest-core:1.3
org.mockito:mockito-core:3.6.0
org.objenesis:objenesis:3.1
org.springframework:spring-core:5.3.0
org.springframework:spring-jcl:5.3.0
org.springframework:spring-test:5.3.0

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,13 +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.
junit:junit:4.13.1
net.bytebuddy:byte-buddy-agent:1.10.15
net.bytebuddy:byte-buddy:1.10.15
org.assertj:assertj-core:3.18.0
org.hamcrest:hamcrest-core:1.3
org.mockito:mockito-core:3.6.0
org.objenesis:objenesis:3.1
org.springframework:spring-core:5.3.0
org.springframework:spring-jcl:5.3.0
org.springframework:spring-test:5.3.0

View File

@@ -1,13 +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.
junit:junit:4.13.1
net.bytebuddy:byte-buddy-agent:1.10.15
net.bytebuddy:byte-buddy:1.10.15
org.assertj:assertj-core:3.18.0
org.hamcrest:hamcrest-core:1.3
org.mockito:mockito-core:3.6.0
org.objenesis:objenesis:3.1
org.springframework:spring-core:5.3.0
org.springframework:spring-jcl:5.3.0
org.springframework:spring-test:5.3.0

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
version=0.0.3
springBootVersion=2.4.0-M3
version=0.2.3
springBootVersion=2.5.10
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
org.gradle.caching=true

View File

@@ -1,13 +1,13 @@
if (!project.hasProperty("springVersion")) {
ext.springVersion = "5.3.+"
ext.springVersion = "5.3.16"
}
if (!project.hasProperty("springSecurityVersion")) {
ext.springSecurityVersion = "5.4.+"
ext.springSecurityVersion = "5.5.5"
}
if (!project.hasProperty("reactorVersion")) {
ext.reactorVersion = "2020.0.+"
ext.reactorVersion = "2020.0.16"
}
if (!project.hasProperty("locksDisabled")) {
@@ -21,18 +21,18 @@ dependencyManagement {
mavenBom "org.springframework:spring-framework-bom:$springVersion"
mavenBom "org.springframework.security:spring-security-bom:$springSecurityVersion"
mavenBom "io.projectreactor:reactor-bom:$reactorVersion"
mavenBom "com.fasterxml.jackson:jackson-bom:2.+"
mavenBom "com.fasterxml.jackson:jackson-bom:2.12.6"
}
dependencies {
dependency "com.nimbusds:oauth2-oidc-sdk:latest.release"
dependency "com.nimbusds:nimbus-jose-jwt:latest.release"
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.+"
dependency "com.nimbusds:nimbus-jose-jwt:9.10.1"
dependency "javax.servlet:javax.servlet-api:4.0.1"
dependency 'junit:junit:4.13.2'
dependency 'org.assertj:assertj-core:3.19.0'
dependency 'org.mockito:mockito-core:3.9.0'
dependency "com.squareup.okhttp3:mockwebserver:3.14.9"
dependency "com.squareup.okhttp3:okhttp:3.14.9"
dependency "com.jayway.jsonpath:json-path:2.5.0"
dependency "org.hsqldb:hsqldb:2.5.2"
}
}

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-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,21 +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.12.0-rc1
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
com.github.stephenc.jcip:jcip-annotations:1.0-1
com.nimbusds:nimbus-jose-jwt:9.1.2
org.springframework.security:spring-security-config:5.4.1
org.springframework.security:spring-security-core:5.4.1
org.springframework.security:spring-security-oauth2-core:5.4.1
org.springframework.security:spring-security-oauth2-jose:5.4.1
org.springframework.security:spring-security-web:5.4.1
org.springframework:spring-aop:5.3.0
org.springframework:spring-beans:5.3.0
org.springframework:spring-context:5.3.0
org.springframework:spring-core:5.3.0
org.springframework:spring-expression:5.3.0
org.springframework:spring-jcl:5.3.0
org.springframework:spring-web:5.3.0

View File

@@ -1,21 +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.12.0-rc1
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
com.github.stephenc.jcip:jcip-annotations:1.0-1
com.nimbusds:nimbus-jose-jwt:9.1.2
org.springframework.security:spring-security-config:5.4.1
org.springframework.security:spring-security-core:5.4.1
org.springframework.security:spring-security-oauth2-core:5.4.1
org.springframework.security:spring-security-oauth2-jose:5.4.1
org.springframework.security:spring-security-web:5.4.1
org.springframework:spring-aop:5.3.0
org.springframework:spring-beans:5.3.0
org.springframework:spring-context:5.3.0
org.springframework:spring-core:5.3.0
org.springframework:spring-expression:5.3.0
org.springframework:spring-jcl:5.3.0
org.springframework:spring-web:5.3.0

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,21 +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.12.0-rc1
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
com.github.stephenc.jcip:jcip-annotations:1.0-1
com.nimbusds:nimbus-jose-jwt:9.1.2
org.springframework.security:spring-security-config:5.4.1
org.springframework.security:spring-security-core:5.4.1
org.springframework.security:spring-security-oauth2-core:5.4.1
org.springframework.security:spring-security-oauth2-jose:5.4.1
org.springframework.security:spring-security-web:5.4.1
org.springframework:spring-aop:5.3.0
org.springframework:spring-beans:5.3.0
org.springframework:spring-context:5.3.0
org.springframework:spring-core:5.3.0
org.springframework:spring-expression:5.3.0
org.springframework:spring-jcl:5.3.0
org.springframework:spring-web:5.3.0

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.

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.
javax.servlet:javax.servlet-api:4.0.1

View File

@@ -1,21 +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.12.0-rc1
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
com.github.stephenc.jcip:jcip-annotations:1.0-1
com.nimbusds:nimbus-jose-jwt:9.1.2
org.springframework.security:spring-security-config:5.4.1
org.springframework.security:spring-security-core:5.4.1
org.springframework.security:spring-security-oauth2-core:5.4.1
org.springframework.security:spring-security-oauth2-jose:5.4.1
org.springframework.security:spring-security-web:5.4.1
org.springframework:spring-aop:5.3.0
org.springframework:spring-beans:5.3.0
org.springframework:spring-context:5.3.0
org.springframework:spring-core:5.3.0
org.springframework:spring-expression:5.3.0
org.springframework:spring-jcl:5.3.0
org.springframework:spring-web:5.3.0

View File

@@ -1,21 +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.12.0-rc1
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
com.github.stephenc.jcip:jcip-annotations:1.0-1
com.nimbusds:nimbus-jose-jwt:9.1.2
org.springframework.security:spring-security-config:5.4.1
org.springframework.security:spring-security-core:5.4.1
org.springframework.security:spring-security-oauth2-core:5.4.1
org.springframework.security:spring-security-oauth2-jose:5.4.1
org.springframework.security:spring-security-web:5.4.1
org.springframework:spring-aop:5.3.0
org.springframework:spring-beans:5.3.0
org.springframework:spring-context:5.3.0
org.springframework:spring-core:5.3.0
org.springframework:spring-expression:5.3.0
org.springframework:spring-jcl:5.3.0
org.springframework:spring-web:5.3.0

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,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,36 +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.12.0-rc1
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
com.github.stephenc.jcip:jcip-annotations:1.0-1
com.jayway.jsonpath:json-path:2.4.0
com.nimbusds:nimbus-jose-jwt:9.1.2
junit:junit:4.13.1
net.bytebuddy:byte-buddy-agent:1.10.15
net.bytebuddy:byte-buddy:1.10.15
net.minidev:accessors-smart:1.2
net.minidev:json-smart:2.3
org.assertj:assertj-core:3.18.0
org.hamcrest:hamcrest-core:1.3
org.mockito:mockito-core:3.6.0
org.objenesis:objenesis:3.1
org.ow2.asm:asm:5.0.4
org.slf4j:slf4j-api:1.7.25
org.springframework.security:spring-security-config:5.4.1
org.springframework.security:spring-security-core:5.4.1
org.springframework.security:spring-security-oauth2-core:5.4.1
org.springframework.security:spring-security-oauth2-jose:5.4.1
org.springframework.security:spring-security-test:5.4.1
org.springframework.security:spring-security-web:5.4.1
org.springframework:spring-aop:5.3.0
org.springframework:spring-beans:5.3.0
org.springframework:spring-context:5.3.0
org.springframework:spring-core:5.3.0
org.springframework:spring-expression:5.3.0
org.springframework:spring-jcl:5.3.0
org.springframework:spring-test:5.3.0
org.springframework:spring-web:5.3.0
org.springframework:spring-webmvc:5.3.0

View File

@@ -1,36 +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.12.0-rc1
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
com.github.stephenc.jcip:jcip-annotations:1.0-1
com.jayway.jsonpath:json-path:2.4.0
com.nimbusds:nimbus-jose-jwt:9.1.2
junit:junit:4.13.1
net.bytebuddy:byte-buddy-agent:1.10.15
net.bytebuddy:byte-buddy:1.10.15
net.minidev:accessors-smart:1.2
net.minidev:json-smart:2.3
org.assertj:assertj-core:3.18.0
org.hamcrest:hamcrest-core:1.3
org.mockito:mockito-core:3.6.0
org.objenesis:objenesis:3.1
org.ow2.asm:asm:5.0.4
org.slf4j:slf4j-api:1.7.25
org.springframework.security:spring-security-config:5.4.1
org.springframework.security:spring-security-core:5.4.1
org.springframework.security:spring-security-oauth2-core:5.4.1
org.springframework.security:spring-security-oauth2-jose:5.4.1
org.springframework.security:spring-security-test:5.4.1
org.springframework.security:spring-security-web:5.4.1
org.springframework:spring-aop:5.3.0
org.springframework:spring-beans:5.3.0
org.springframework:spring-context:5.3.0
org.springframework:spring-core:5.3.0
org.springframework:spring-expression:5.3.0
org.springframework:spring-jcl:5.3.0
org.springframework:spring-test:5.3.0
org.springframework:spring-web:5.3.0
org.springframework:spring-webmvc:5.3.0

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,36 +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.12.0-rc1
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
com.github.stephenc.jcip:jcip-annotations:1.0-1
com.jayway.jsonpath:json-path:2.4.0
com.nimbusds:nimbus-jose-jwt:9.1.2
junit:junit:4.13.1
net.bytebuddy:byte-buddy-agent:1.10.15
net.bytebuddy:byte-buddy:1.10.15
net.minidev:accessors-smart:1.2
net.minidev:json-smart:2.3
org.assertj:assertj-core:3.18.0
org.hamcrest:hamcrest-core:1.3
org.mockito:mockito-core:3.6.0
org.objenesis:objenesis:3.1
org.ow2.asm:asm:5.0.4
org.slf4j:slf4j-api:1.7.25
org.springframework.security:spring-security-config:5.4.1
org.springframework.security:spring-security-core:5.4.1
org.springframework.security:spring-security-oauth2-core:5.4.1
org.springframework.security:spring-security-oauth2-jose:5.4.1
org.springframework.security:spring-security-test:5.4.1
org.springframework.security:spring-security-web:5.4.1
org.springframework:spring-aop:5.3.0
org.springframework:spring-beans:5.3.0
org.springframework:spring-context:5.3.0
org.springframework:spring-core:5.3.0
org.springframework:spring-expression:5.3.0
org.springframework:spring-jcl:5.3.0
org.springframework:spring-test:5.3.0
org.springframework:spring-web:5.3.0
org.springframework:spring-webmvc:5.3.0

View File

@@ -1,36 +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.12.0-rc1
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
com.github.stephenc.jcip:jcip-annotations:1.0-1
com.jayway.jsonpath:json-path:2.4.0
com.nimbusds:nimbus-jose-jwt:9.1.2
junit:junit:4.13.1
net.bytebuddy:byte-buddy-agent:1.10.15
net.bytebuddy:byte-buddy:1.10.15
net.minidev:accessors-smart:1.2
net.minidev:json-smart:2.3
org.assertj:assertj-core:3.18.0
org.hamcrest:hamcrest-core:1.3
org.mockito:mockito-core:3.6.0
org.objenesis:objenesis:3.1
org.ow2.asm:asm:5.0.4
org.slf4j:slf4j-api:1.7.25
org.springframework.security:spring-security-config:5.4.1
org.springframework.security:spring-security-core:5.4.1
org.springframework.security:spring-security-oauth2-core:5.4.1
org.springframework.security:spring-security-oauth2-jose:5.4.1
org.springframework.security:spring-security-test:5.4.1
org.springframework.security:spring-security-web:5.4.1
org.springframework:spring-aop:5.3.0
org.springframework:spring-beans:5.3.0
org.springframework:spring-context:5.3.0
org.springframework:spring-core:5.3.0
org.springframework:spring-expression:5.3.0
org.springframework:spring-jcl:5.3.0
org.springframework:spring-test:5.3.0
org.springframework:spring-web:5.3.0
org.springframework:spring-webmvc:5.3.0

View File

@@ -1,36 +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.12.0-rc1
com.fasterxml.jackson.core:jackson-core:2.12.0-rc1
com.fasterxml.jackson.core:jackson-databind:2.12.0-rc1
com.fasterxml.jackson:jackson-bom:2.12.0-rc1
com.github.stephenc.jcip:jcip-annotations:1.0-1
com.jayway.jsonpath:json-path:2.4.0
com.nimbusds:nimbus-jose-jwt:9.1.2
junit:junit:4.13.1
net.bytebuddy:byte-buddy-agent:1.10.15
net.bytebuddy:byte-buddy:1.10.15
net.minidev:accessors-smart:1.2
net.minidev:json-smart:2.3
org.assertj:assertj-core:3.18.0
org.hamcrest:hamcrest-core:1.3
org.mockito:mockito-core:3.6.0
org.objenesis:objenesis:3.1
org.ow2.asm:asm:5.0.4
org.slf4j:slf4j-api:1.7.25
org.springframework.security:spring-security-config:5.4.1
org.springframework.security:spring-security-core:5.4.1
org.springframework.security:spring-security-oauth2-core:5.4.1
org.springframework.security:spring-security-oauth2-jose:5.4.1
org.springframework.security:spring-security-test:5.4.1
org.springframework.security:spring-security-web:5.4.1
org.springframework:spring-aop:5.3.0
org.springframework:spring-beans:5.3.0
org.springframework:spring-context:5.3.0
org.springframework:spring-core:5.3.0
org.springframework:spring-expression:5.3.0
org.springframework:spring-jcl:5.3.0
org.springframework:spring-test:5.3.0
org.springframework:spring-web:5.3.0
org.springframework:spring-webmvc:5.3.0

View File

@@ -5,20 +5,27 @@ dependencies {
compile 'org.springframework.security:spring-security-web'
compile 'org.springframework.security:spring-security-oauth2-core'
compile 'org.springframework.security:spring-security-oauth2-jose'
compile 'org.springframework.security:spring-security-oauth2-resource-server'
compile springCoreDependency
compile 'com.nimbusds:nimbus-jose-jwt'
compile 'com.fasterxml.jackson.core:jackson-databind'
optional 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
optional 'org.springframework:spring-jdbc'
testCompile 'org.springframework.security:spring-security-test'
testCompile 'org.springframework:spring-webmvc'
testCompile 'junit:junit'
testCompile 'org.assertj:assertj-core'
testCompile 'org.mockito:mockito-core'
testCompile 'com.jayway.jsonpath:json-path'
testCompile 'com.squareup.okhttp3:mockwebserver'
testRuntime 'org.hsqldb:hsqldb'
provided 'javax.servlet:javax.servlet-api'
}
jacoco {
toolVersion = '0.8.5'
toolVersion = '0.8.6'
}

View File

@@ -1,5 +1,5 @@
/*
* 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.
@@ -15,23 +15,83 @@
*/
package org.springframework.security.config.annotation.web.configuration;
import java.util.HashSet;
import java.util.Set;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.JWSKeySelector;
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.RequestMatcher;
/**
* {@link Configuration} for OAuth 2.0 Authorization Server support.
*
* @author Joe Grandja
* @since 0.0.1
* @see OAuth2AuthorizationServerConfigurer
*/
@Configuration(proxyBeanMethods = false)
public class OAuth2AuthorizationServerConfiguration {
@Bean
public WebSecurityConfigurer<WebSecurity> defaultOAuth2AuthorizationServerSecurity() {
return new OAuth2AuthorizationServerSecurity();
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
applyDefaultSecurity(http);
return http.build();
}
// @formatter:off
public static void applyDefaultSecurity(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
RequestMatcher endpointsMatcher = authorizationServerConfigurer
.getEndpointsMatcher();
http
.requestMatcher(endpointsMatcher)
.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
.apply(authorizationServerConfigurer);
}
// @formatter:on
public static JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
Set<JWSAlgorithm> jwsAlgs = new HashSet<>();
jwsAlgs.addAll(JWSAlgorithm.Family.RSA);
jwsAlgs.addAll(JWSAlgorithm.Family.EC);
jwsAlgs.addAll(JWSAlgorithm.Family.HMAC_SHA);
ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
JWSKeySelector<SecurityContext> jwsKeySelector =
new JWSVerificationKeySelector<>(jwsAlgs, jwkSource);
jwtProcessor.setJWSKeySelector(jwsKeySelector);
// Override the default Nimbus claims set verifier as NimbusJwtDecoder handles it instead
jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {
});
return new NimbusJwtDecoder(jwtProcessor);
}
@Bean
RegisterMissingBeanPostProcessor registerMissingBeanPostProcessor() {
RegisterMissingBeanPostProcessor postProcessor = new RegisterMissingBeanPostProcessor();
postProcessor.addBeanDefinition(ProviderSettings.class, () -> ProviderSettings.builder().build());
return postProcessor;
}
}

View File

@@ -1,58 +0,0 @@
/*
* Copyright 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.security.config.annotation.web.configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import static org.springframework.security.config.Customizer.withDefaults;
/**
* {@link WebSecurityConfigurerAdapter} providing default security configuration for OAuth 2.0 Authorization Server.
*
* @author Joe Grandja
* @since 0.0.1
*/
@Order(Ordered.HIGHEST_PRECEDENCE)
public class OAuth2AuthorizationServerSecurity extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
applyDefaultConfiguration(http);
}
// @formatter:off
public static void applyDefaultConfiguration(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
RequestMatcher[] endpointMatchers = authorizationServerConfigurer
.getEndpointMatchers().toArray(new RequestMatcher[0]);
http
.requestMatcher(new OrRequestMatcher(endpointMatchers))
.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.formLogin(withDefaults())
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointMatchers))
.apply(authorizationServerConfigurer);
}
// @formatter:on
}

View File

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

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.web.util.matcher.RequestMatcher;
/**
* Base configurer for an OAuth 2.0 component (e.g. protocol endpoint).
*
* @author Joe Grandja
* @since 0.1.2
*/
abstract class AbstractOAuth2Configurer {
private final ObjectPostProcessor<Object> objectPostProcessor;
AbstractOAuth2Configurer(ObjectPostProcessor<Object> objectPostProcessor) {
this.objectPostProcessor = objectPostProcessor;
}
abstract <B extends HttpSecurityBuilder<B>> void init(B builder);
abstract <B extends HttpSecurityBuilder<B>> void configure(B builder);
abstract RequestMatcher getRequestMatcher();
protected final <T> T postProcess(T object) {
return (T) this.objectPostProcessor.postProcess(object);
}
protected final ObjectPostProcessor<Object> getObjectPostProcessor() {
return this.objectPostProcessor;
}
}

View File

@@ -0,0 +1,211 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Configurer for the OAuth 2.0 Authorization Endpoint.
*
* @author Joe Grandja
* @since 0.1.2
* @see OAuth2AuthorizationServerConfigurer#authorizationEndpoint
* @see OAuth2AuthorizationEndpointFilter
*/
public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2Configurer {
private RequestMatcher requestMatcher;
private AuthenticationConverter authorizationRequestConverter;
private final List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
private AuthenticationSuccessHandler authorizationResponseHandler;
private AuthenticationFailureHandler errorResponseHandler;
private String consentPage;
/**
* Restrict for internal use only.
*/
OAuth2AuthorizationEndpointConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor);
}
/**
* Sets the {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
* to an instance of {@link OAuth2AuthorizationCodeRequestAuthenticationToken} used for authenticating the request.
*
* @param authorizationRequestConverter the {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
* @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration
*/
public OAuth2AuthorizationEndpointConfigurer authorizationRequestConverter(AuthenticationConverter authorizationRequestConverter) {
this.authorizationRequestConverter = authorizationRequestConverter;
return this;
}
/**
* Adds an {@link AuthenticationProvider} used for authenticating an {@link OAuth2AuthorizationCodeRequestAuthenticationToken}.
*
* @param authenticationProvider an {@link AuthenticationProvider} used for authenticating an {@link OAuth2AuthorizationCodeRequestAuthenticationToken}
* @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration
*/
public OAuth2AuthorizationEndpointConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) {
Assert.notNull(authenticationProvider, "authenticationProvider cannot be null");
this.authenticationProviders.add(authenticationProvider);
return this;
}
/**
* Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2AuthorizationCodeRequestAuthenticationToken}
* and returning the {@link OAuth2AuthorizationResponse Authorization Response}.
*
* @param authorizationResponseHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2AuthorizationCodeRequestAuthenticationToken}
* @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration
*/
public OAuth2AuthorizationEndpointConfigurer authorizationResponseHandler(AuthenticationSuccessHandler authorizationResponseHandler) {
this.authorizationResponseHandler = authorizationResponseHandler;
return this;
}
/**
* Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthorizationCodeRequestAuthenticationException}
* and returning the {@link OAuth2Error Error Response}.
*
* @param errorResponseHandler the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthorizationCodeRequestAuthenticationException}
* @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration
*/
public OAuth2AuthorizationEndpointConfigurer errorResponseHandler(AuthenticationFailureHandler errorResponseHandler) {
this.errorResponseHandler = errorResponseHandler;
return this;
}
/**
* Specify the URI to redirect Resource Owners to if consent is required during
* the {@code authorization_code} flow. A default consent page will be generated when
* this attribute is not specified.
*
* If a URI is specified, applications are required to process the specified URI to generate
* a consent page. The query string will contain the following parameters:
*
* <ul>
* <li>{@code client_id} - the client identifier</li>
* <li>{@code scope} - a space-delimited list of scopes present in the authorization request</li>
* <li>{@code state} - a CSRF protection token</li>
* </ul>
*
* In general, the consent page should create a form that submits
* a request with the following requirements:
*
* <ul>
* <li>It must be an HTTP POST</li>
* <li>It must be submitted to {@link ProviderSettings#getAuthorizationEndpoint()} ()}</li>
* <li>It must include the received {@code client_id} as an HTTP parameter</li>
* <li>It must include the received {@code state} as an HTTP parameter</li>
* <li>It must include the list of {@code scope}s the {@code Resource Owner}
* consented to as an HTTP parameter</li>
* </ul>
*
* @param consentPage the URI of the custom consent page to redirect to if consent is required (e.g. "/oauth2/consent")
* @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration
*/
public OAuth2AuthorizationEndpointConfigurer consentPage(String consentPage) {
this.consentPage = consentPage;
return this;
}
@Override
<B extends HttpSecurityBuilder<B>> void init(B builder) {
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
this.requestMatcher = new OrRequestMatcher(
new AntPathRequestMatcher(
providerSettings.getAuthorizationEndpoint(),
HttpMethod.GET.name()),
new AntPathRequestMatcher(
providerSettings.getAuthorizationEndpoint(),
HttpMethod.POST.name()));
List<AuthenticationProvider> authenticationProviders =
!this.authenticationProviders.isEmpty() ?
this.authenticationProviders :
createDefaultAuthenticationProviders(builder);
authenticationProviders.forEach(authenticationProvider ->
builder.authenticationProvider(postProcess(authenticationProvider)));
}
@Override
<B extends HttpSecurityBuilder<B>> void configure(B builder) {
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
OAuth2AuthorizationEndpointFilter authorizationEndpointFilter =
new OAuth2AuthorizationEndpointFilter(
authenticationManager,
providerSettings.getAuthorizationEndpoint());
if (this.authorizationRequestConverter != null) {
authorizationEndpointFilter.setAuthenticationConverter(this.authorizationRequestConverter);
}
if (this.authorizationResponseHandler != null) {
authorizationEndpointFilter.setAuthenticationSuccessHandler(this.authorizationResponseHandler);
}
if (this.errorResponseHandler != null) {
authorizationEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler);
}
if (StringUtils.hasText(this.consentPage)) {
authorizationEndpointFilter.setConsentPage(this.consentPage);
}
builder.addFilterBefore(postProcess(authorizationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
}
@Override
RequestMatcher getRequestMatcher() {
return this.requestMatcher;
}
private <B extends HttpSecurityBuilder<B>> List<AuthenticationProvider> createDefaultAuthenticationProviders(B builder) {
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
OAuth2AuthorizationCodeRequestAuthenticationProvider authorizationCodeRequestAuthenticationProvider =
new OAuth2AuthorizationCodeRequestAuthenticationProvider(
OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
OAuth2ConfigurerUtils.getAuthorizationService(builder),
OAuth2ConfigurerUtils.getAuthorizationConsentService(builder));
authenticationProviders.add(authorizationCodeRequestAuthenticationProvider);
return authenticationProviders;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,71 +15,88 @@
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import java.net.URI;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.AsyncContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import com.nimbusds.jose.jwk.source.JWKSource;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
import org.springframework.security.crypto.keys.KeyManager;
import org.springframework.security.oauth2.jose.jws.NimbusJwsEncoder;
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.Transient;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.web.JwkSetEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.ProviderContextFilter;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SaveContextOnUpdateOrErrorResponseWrapper;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* An {@link AbstractHttpConfigurer} for OAuth 2.0 Authorization Server support.
*
* @author Joe Grandja
* @author Daniel Garnier-Moiroux
* @author Gerardo Roza
* @author Ovidiu Popa
* @author Gaurav Tiwari
* @since 0.0.1
* @see AbstractHttpConfigurer
* @see OAuth2ClientAuthenticationConfigurer
* @see OAuth2AuthorizationEndpointConfigurer
* @see OAuth2TokenEndpointConfigurer
* @see OAuth2TokenIntrospectionEndpointConfigurer
* @see OAuth2TokenRevocationEndpointConfigurer
* @see OidcConfigurer
* @see RegisteredClientRepository
* @see OAuth2AuthorizationService
* @see OAuth2AuthorizationEndpointFilter
* @see OAuth2TokenEndpointFilter
* @see OAuth2ClientAuthenticationFilter
* @see OAuth2AuthorizationConsentService
* @see NimbusJwkSetEndpointFilter
* @see OAuth2AuthorizationServerMetadataEndpointFilter
*/
public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBuilder<B>>
extends AbstractHttpConfigurer<OAuth2AuthorizationServerConfigurer<B>, B> {
private final RequestMatcher authorizationEndpointMatcher = new OrRequestMatcher(
new AntPathRequestMatcher(
OAuth2AuthorizationEndpointFilter.DEFAULT_AUTHORIZATION_ENDPOINT_URI,
HttpMethod.GET.name()),
new AntPathRequestMatcher(
OAuth2AuthorizationEndpointFilter.DEFAULT_AUTHORIZATION_ENDPOINT_URI,
HttpMethod.POST.name()));
private final RequestMatcher tokenEndpointMatcher = new AntPathRequestMatcher(
OAuth2TokenEndpointFilter.DEFAULT_TOKEN_ENDPOINT_URI, HttpMethod.POST.name());
private final RequestMatcher tokenRevocationEndpointMatcher = new AntPathRequestMatcher(
OAuth2TokenRevocationEndpointFilter.DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI, HttpMethod.POST.name());
private final RequestMatcher jwkSetEndpointMatcher = new AntPathRequestMatcher(
JwkSetEndpointFilter.DEFAULT_JWK_SET_ENDPOINT_URI, HttpMethod.GET.name());
private final Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = createConfigurers();
private RequestMatcher jwkSetEndpointMatcher;
private RequestMatcher authorizationServerMetadataEndpointMatcher;
private final RequestMatcher endpointsMatcher = (request) ->
getRequestMatcher(OAuth2AuthorizationEndpointConfigurer.class).matches(request) ||
getRequestMatcher(OAuth2TokenEndpointConfigurer.class).matches(request) ||
getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class).matches(request) ||
getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class).matches(request) ||
getRequestMatcher(OidcConfigurer.class).matches(request) ||
this.jwkSetEndpointMatcher.matches(request) ||
this.authorizationServerMetadataEndpointMatcher.matches(request);
/**
* Sets the repository of registered clients.
@@ -89,7 +106,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
*/
public OAuth2AuthorizationServerConfigurer<B> registeredClientRepository(RegisteredClientRepository registeredClientRepository) {
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
this.getBuilder().setSharedObject(RegisteredClientRepository.class, registeredClientRepository);
getBuilder().setSharedObject(RegisteredClientRepository.class, registeredClientRepository);
return this;
}
@@ -101,152 +118,321 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
*/
public OAuth2AuthorizationServerConfigurer<B> authorizationService(OAuth2AuthorizationService authorizationService) {
Assert.notNull(authorizationService, "authorizationService cannot be null");
this.getBuilder().setSharedObject(OAuth2AuthorizationService.class, authorizationService);
getBuilder().setSharedObject(OAuth2AuthorizationService.class, authorizationService);
return this;
}
/**
* Sets the key manager.
* Sets the authorization consent service.
*
* @param keyManager the key manager
* @param authorizationConsentService the authorization consent service
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
*/
public OAuth2AuthorizationServerConfigurer<B> keyManager(KeyManager keyManager) {
Assert.notNull(keyManager, "keyManager cannot be null");
this.getBuilder().setSharedObject(KeyManager.class, keyManager);
public OAuth2AuthorizationServerConfigurer<B> authorizationConsentService(OAuth2AuthorizationConsentService authorizationConsentService) {
Assert.notNull(authorizationConsentService, "authorizationConsentService cannot be null");
getBuilder().setSharedObject(OAuth2AuthorizationConsentService.class, authorizationConsentService);
return this;
}
/**
* Returns a {@code List} of {@link RequestMatcher}'s for the authorization server endpoints.
* Sets the provider settings.
*
* @return a {@code List} of {@link RequestMatcher}'s for the authorization server endpoints
* @param providerSettings the provider settings
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
*/
public List<RequestMatcher> getEndpointMatchers() {
return Arrays.asList(this.authorizationEndpointMatcher, this.tokenEndpointMatcher,
this.tokenRevocationEndpointMatcher, this.jwkSetEndpointMatcher);
public OAuth2AuthorizationServerConfigurer<B> providerSettings(ProviderSettings providerSettings) {
Assert.notNull(providerSettings, "providerSettings cannot be null");
getBuilder().setSharedObject(ProviderSettings.class, providerSettings);
return this;
}
/**
* Sets the token generator.
*
* @param tokenGenerator the token generator
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
* @since 0.2.3
*/
public OAuth2AuthorizationServerConfigurer<B> tokenGenerator(OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
getBuilder().setSharedObject(OAuth2TokenGenerator.class, tokenGenerator);
return this;
}
/**
* Configures OAuth 2.0 Client Authentication.
*
* @param clientAuthenticationCustomizer the {@link Customizer} providing access to the {@link OAuth2ClientAuthenticationConfigurer}
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
*/
public OAuth2AuthorizationServerConfigurer<B> clientAuthentication(Customizer<OAuth2ClientAuthenticationConfigurer> clientAuthenticationCustomizer) {
clientAuthenticationCustomizer.customize(getConfigurer(OAuth2ClientAuthenticationConfigurer.class));
return this;
}
/**
* Configures the OAuth 2.0 Authorization Endpoint.
*
* @param authorizationEndpointCustomizer the {@link Customizer} providing access to the {@link OAuth2AuthorizationEndpointConfigurer}
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
*/
public OAuth2AuthorizationServerConfigurer<B> authorizationEndpoint(Customizer<OAuth2AuthorizationEndpointConfigurer> authorizationEndpointCustomizer) {
authorizationEndpointCustomizer.customize(getConfigurer(OAuth2AuthorizationEndpointConfigurer.class));
return this;
}
/**
* Configures the OAuth 2.0 Token Endpoint.
*
* @param tokenEndpointCustomizer the {@link Customizer} providing access to the {@link OAuth2TokenEndpointConfigurer}
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
*/
public OAuth2AuthorizationServerConfigurer<B> tokenEndpoint(Customizer<OAuth2TokenEndpointConfigurer> tokenEndpointCustomizer) {
tokenEndpointCustomizer.customize(getConfigurer(OAuth2TokenEndpointConfigurer.class));
return this;
}
/**
* Configures the OAuth 2.0 Token Introspection Endpoint.
*
* @param tokenIntrospectionEndpointCustomizer the {@link Customizer} providing access to the {@link OAuth2TokenIntrospectionEndpointConfigurer}
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
* @since 0.2.3
*/
public OAuth2AuthorizationServerConfigurer<B> tokenIntrospectionEndpoint(Customizer<OAuth2TokenIntrospectionEndpointConfigurer> tokenIntrospectionEndpointCustomizer) {
tokenIntrospectionEndpointCustomizer.customize(getConfigurer(OAuth2TokenIntrospectionEndpointConfigurer.class));
return this;
}
/**
* Configures the OAuth 2.0 Token Revocation Endpoint.
*
* @param tokenRevocationEndpointCustomizer the {@link Customizer} providing access to the {@link OAuth2TokenRevocationEndpointConfigurer}
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
* @since 0.2.2
*/
public OAuth2AuthorizationServerConfigurer<B> tokenRevocationEndpoint(Customizer<OAuth2TokenRevocationEndpointConfigurer> tokenRevocationEndpointCustomizer) {
tokenRevocationEndpointCustomizer.customize(getConfigurer(OAuth2TokenRevocationEndpointConfigurer.class));
return this;
}
/**
* Configures OpenID Connect 1.0 support.
*
* @param oidcCustomizer the {@link Customizer} providing access to the {@link OidcConfigurer}
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
*/
public OAuth2AuthorizationServerConfigurer<B> oidc(Customizer<OidcConfigurer> oidcCustomizer) {
oidcCustomizer.customize(getConfigurer(OidcConfigurer.class));
return this;
}
/**
* Returns a {@link RequestMatcher} for the authorization server endpoints.
*
* @return a {@link RequestMatcher} for the authorization server endpoints
*/
public RequestMatcher getEndpointsMatcher() {
return this.endpointsMatcher;
}
@Override
public void init(B builder) {
OAuth2ClientAuthenticationProvider clientAuthenticationProvider =
new OAuth2ClientAuthenticationProvider(
getRegisteredClientRepository(builder),
getAuthorizationService(builder));
builder.authenticationProvider(postProcess(clientAuthenticationProvider));
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
validateProviderSettings(providerSettings);
initEndpointMatchers(providerSettings);
NimbusJwsEncoder jwtEncoder = new NimbusJwsEncoder(getKeyManager(builder));
OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider =
new OAuth2AuthorizationCodeAuthenticationProvider(
getRegisteredClientRepository(builder),
getAuthorizationService(builder),
jwtEncoder);
builder.authenticationProvider(postProcess(authorizationCodeAuthenticationProvider));
OAuth2RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider =
new OAuth2RefreshTokenAuthenticationProvider(
getAuthorizationService(builder),
jwtEncoder);
builder.authenticationProvider(postProcess(refreshTokenAuthenticationProvider));
OAuth2ClientCredentialsAuthenticationProvider clientCredentialsAuthenticationProvider =
new OAuth2ClientCredentialsAuthenticationProvider(
getAuthorizationService(builder),
jwtEncoder);
builder.authenticationProvider(postProcess(clientCredentialsAuthenticationProvider));
OAuth2TokenRevocationAuthenticationProvider tokenRevocationAuthenticationProvider =
new OAuth2TokenRevocationAuthenticationProvider(
getAuthorizationService(builder));
builder.authenticationProvider(postProcess(tokenRevocationAuthenticationProvider));
this.configurers.values().forEach(configurer -> configurer.init(builder));
ExceptionHandlingConfigurer<B> exceptionHandling = builder.getConfigurer(ExceptionHandlingConfigurer.class);
if (exceptionHandling != null) {
// Register the default AuthenticationEntryPoint for the token endpoint and token revocation endpoint
exceptionHandling.defaultAuthenticationEntryPointFor(
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
new OrRequestMatcher(this.tokenEndpointMatcher, this.tokenRevocationEndpointMatcher));
new OrRequestMatcher(
getRequestMatcher(OAuth2TokenEndpointConfigurer.class),
getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class),
getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class))
);
}
// gh-482
initSecurityContextRepository(builder);
}
private void initSecurityContextRepository(B builder) {
// TODO This is a temporary fix and should be removed after upgrading to Spring Security 5.7.0 GA.
//
// See:
// Prevent Save @Transient Authentication with existing HttpSession
// https://github.com/spring-projects/spring-security/pull/9993
final SecurityContextRepository securityContextRepository = builder.getSharedObject(SecurityContextRepository.class);
if (!(securityContextRepository instanceof HttpSessionSecurityContextRepository)) {
return;
}
SecurityContextRepository securityContextRepositoryTransientNotSaved = new SecurityContextRepository() {
private final RequestMatcher clientAuthenticationRequestMatcher = initClientAuthenticationRequestMatcher();
private final RequestMatcher jwtAuthenticationRequestMatcher = initJwtAuthenticationRequestMatcher();
@Override
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
final HttpServletRequest unwrappedRequest = requestResponseHolder.getRequest();
final HttpServletResponse unwrappedResponse = requestResponseHolder.getResponse();
SecurityContext securityContext = securityContextRepository.loadContext(requestResponseHolder);
if (this.clientAuthenticationRequestMatcher.matches(unwrappedRequest) ||
this.jwtAuthenticationRequestMatcher.matches(unwrappedRequest)) {
final SaveContextOnUpdateOrErrorResponseWrapper transientAuthenticationResponseWrapper =
new SaveContextOnUpdateOrErrorResponseWrapper(unwrappedResponse, false) {
@Override
protected void saveContext(SecurityContext context) {
// @Transient Authentication should not be saved
if (context.getAuthentication() != null) {
Assert.state(isTransientAuthentication(context.getAuthentication()), "Expected @Transient Authentication");
}
}
};
// Override the default HttpSessionSecurityContextRepository.SaveToSessionResponseWrapper
requestResponseHolder.setResponse(transientAuthenticationResponseWrapper);
final HttpServletRequestWrapper transientAuthenticationRequestWrapper =
new HttpServletRequestWrapper(unwrappedRequest) {
@Override
public AsyncContext startAsync() {
transientAuthenticationResponseWrapper.disableSaveOnResponseCommitted();
return super.startAsync();
}
@Override
public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse)
throws IllegalStateException {
transientAuthenticationResponseWrapper.disableSaveOnResponseCommitted();
return super.startAsync(servletRequest, servletResponse);
}
};
// Override the default HttpSessionSecurityContextRepository.SaveToSessionRequestWrapper
requestResponseHolder.setRequest(transientAuthenticationRequestWrapper);
}
return securityContext;
}
@Override
public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
Authentication authentication = context.getAuthentication();
if (authentication == null || isTransientAuthentication(authentication)) {
return;
}
securityContextRepository.saveContext(context, request, response);
}
@Override
public boolean containsContext(HttpServletRequest request) {
return securityContextRepository.containsContext(request);
}
private boolean isTransientAuthentication(Authentication authentication) {
return AnnotationUtils.getAnnotation(authentication.getClass(), Transient.class) != null;
}
private RequestMatcher initClientAuthenticationRequestMatcher() {
// OAuth2ClientAuthenticationToken is @Transient and is accepted by
// OAuth2TokenEndpointFilter, OAuth2TokenIntrospectionEndpointFilter and OAuth2TokenRevocationEndpointFilter
List<RequestMatcher> requestMatchers = new ArrayList<>();
requestMatchers.add(getRequestMatcher(OAuth2TokenEndpointConfigurer.class));
requestMatchers.add(getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class));
requestMatchers.add(getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class));
return new OrRequestMatcher(requestMatchers);
}
private RequestMatcher initJwtAuthenticationRequestMatcher() {
// JwtAuthenticationToken is @Transient and is accepted by
// OidcUserInfoEndpointFilter and OidcClientRegistrationEndpointFilter
List<RequestMatcher> requestMatchers = new ArrayList<>();
requestMatchers.add(
getConfigurer(OidcConfigurer.class)
.getConfigurer(OidcUserInfoEndpointConfigurer.class).getRequestMatcher()
);
OidcClientRegistrationEndpointConfigurer clientRegistrationEndpointConfigurer =
getConfigurer(OidcConfigurer.class)
.getConfigurer(OidcClientRegistrationEndpointConfigurer.class);
if (clientRegistrationEndpointConfigurer != null) {
requestMatchers.add(clientRegistrationEndpointConfigurer.getRequestMatcher());
}
return new OrRequestMatcher(requestMatchers);
}
};
builder.setSharedObject(SecurityContextRepository.class, securityContextRepositoryTransientNotSaved);
}
@Override
public void configure(B builder) {
JwkSetEndpointFilter jwkSetEndpointFilter = new JwkSetEndpointFilter(getKeyManager(builder));
builder.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
this.configurers.values().forEach(configurer -> configurer.configure(builder));
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
OAuth2ClientAuthenticationFilter clientAuthenticationFilter =
new OAuth2ClientAuthenticationFilter(
authenticationManager,
new OrRequestMatcher(this.tokenEndpointMatcher, this.tokenRevocationEndpointMatcher));
builder.addFilterAfter(postProcess(clientAuthenticationFilter), AbstractPreAuthenticatedProcessingFilter.class);
ProviderContextFilter providerContextFilter = new ProviderContextFilter(providerSettings);
builder.addFilterAfter(postProcess(providerContextFilter), SecurityContextPersistenceFilter.class);
OAuth2AuthorizationEndpointFilter authorizationEndpointFilter =
new OAuth2AuthorizationEndpointFilter(
getRegisteredClientRepository(builder),
getAuthorizationService(builder));
builder.addFilterBefore(postProcess(authorizationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
OAuth2TokenEndpointFilter tokenEndpointFilter =
new OAuth2TokenEndpointFilter(
authenticationManager,
getAuthorizationService(builder));
builder.addFilterAfter(postProcess(tokenEndpointFilter), FilterSecurityInterceptor.class);
OAuth2TokenRevocationEndpointFilter tokenRevocationEndpointFilter =
new OAuth2TokenRevocationEndpointFilter(
authenticationManager);
builder.addFilterAfter(postProcess(tokenRevocationEndpointFilter), OAuth2TokenEndpointFilter.class);
}
private static <B extends HttpSecurityBuilder<B>> RegisteredClientRepository getRegisteredClientRepository(B builder) {
RegisteredClientRepository registeredClientRepository = builder.getSharedObject(RegisteredClientRepository.class);
if (registeredClientRepository == null) {
registeredClientRepository = getRegisteredClientRepositoryBean(builder);
builder.setSharedObject(RegisteredClientRepository.class, registeredClientRepository);
JWKSource<com.nimbusds.jose.proc.SecurityContext> jwkSource = OAuth2ConfigurerUtils.getJwkSource(builder);
if (jwkSource != null) {
NimbusJwkSetEndpointFilter jwkSetEndpointFilter = new NimbusJwkSetEndpointFilter(
jwkSource, providerSettings.getJwkSetEndpoint());
builder.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
}
return registeredClientRepository;
OAuth2AuthorizationServerMetadataEndpointFilter authorizationServerMetadataEndpointFilter =
new OAuth2AuthorizationServerMetadataEndpointFilter(providerSettings);
builder.addFilterBefore(postProcess(authorizationServerMetadataEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
}
private static <B extends HttpSecurityBuilder<B>> RegisteredClientRepository getRegisteredClientRepositoryBean(B builder) {
return builder.getSharedObject(ApplicationContext.class).getBean(RegisteredClientRepository.class);
private Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> createConfigurers() {
Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = new LinkedHashMap<>();
configurers.put(OAuth2ClientAuthenticationConfigurer.class, new OAuth2ClientAuthenticationConfigurer(this::postProcess));
configurers.put(OAuth2AuthorizationEndpointConfigurer.class, new OAuth2AuthorizationEndpointConfigurer(this::postProcess));
configurers.put(OAuth2TokenEndpointConfigurer.class, new OAuth2TokenEndpointConfigurer(this::postProcess));
configurers.put(OAuth2TokenIntrospectionEndpointConfigurer.class, new OAuth2TokenIntrospectionEndpointConfigurer(this::postProcess));
configurers.put(OAuth2TokenRevocationEndpointConfigurer.class, new OAuth2TokenRevocationEndpointConfigurer(this::postProcess));
configurers.put(OidcConfigurer.class, new OidcConfigurer(this::postProcess));
return configurers;
}
private static <B extends HttpSecurityBuilder<B>> OAuth2AuthorizationService getAuthorizationService(B builder) {
OAuth2AuthorizationService authorizationService = builder.getSharedObject(OAuth2AuthorizationService.class);
if (authorizationService == null) {
authorizationService = getAuthorizationServiceBean(builder);
if (authorizationService == null) {
authorizationService = new InMemoryOAuth2AuthorizationService();
@SuppressWarnings("unchecked")
private <T> T getConfigurer(Class<T> type) {
return (T) this.configurers.get(type);
}
private <T extends AbstractOAuth2Configurer> RequestMatcher getRequestMatcher(Class<T> configurerType) {
return getConfigurer(configurerType).getRequestMatcher();
}
private void initEndpointMatchers(ProviderSettings providerSettings) {
this.jwkSetEndpointMatcher = new AntPathRequestMatcher(
providerSettings.getJwkSetEndpoint(), HttpMethod.GET.name());
this.authorizationServerMetadataEndpointMatcher = new AntPathRequestMatcher(
"/.well-known/oauth-authorization-server", HttpMethod.GET.name());
}
private static void validateProviderSettings(ProviderSettings providerSettings) {
if (providerSettings.getIssuer() != null) {
try {
new URI(providerSettings.getIssuer()).toURL();
} catch (Exception ex) {
throw new IllegalArgumentException("issuer must be a valid URL", ex);
}
builder.setSharedObject(OAuth2AuthorizationService.class, authorizationService);
}
return authorizationService;
}
private static <B extends HttpSecurityBuilder<B>> OAuth2AuthorizationService getAuthorizationServiceBean(B builder) {
Map<String, OAuth2AuthorizationService> authorizationServiceMap = BeanFactoryUtils.beansOfTypeIncludingAncestors(
builder.getSharedObject(ApplicationContext.class), OAuth2AuthorizationService.class);
if (authorizationServiceMap.size() > 1) {
throw new NoUniqueBeanDefinitionException(OAuth2AuthorizationService.class, authorizationServiceMap.size(),
"Expected single matching bean of type '" + OAuth2AuthorizationService.class.getName() + "' but found " +
authorizationServiceMap.size() + ": " + StringUtils.collectionToCommaDelimitedString(authorizationServiceMap.keySet()));
}
return (!authorizationServiceMap.isEmpty() ? authorizationServiceMap.values().iterator().next() : null);
}
private static <B extends HttpSecurityBuilder<B>> KeyManager getKeyManager(B builder) {
KeyManager keyManager = builder.getSharedObject(KeyManager.class);
if (keyManager == null) {
keyManager = getKeyManagerBean(builder);
builder.setSharedObject(KeyManager.class, keyManager);
}
return keyManager;
}
private static <B extends HttpSecurityBuilder<B>> KeyManager getKeyManagerBean(B builder) {
return builder.getSharedObject(ApplicationContext.class).getBean(KeyManager.class);
}
}

View File

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

View File

@@ -0,0 +1,221 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import java.util.Map;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwsEncoder;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.JwtGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2AccessTokenGenerator;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.util.StringUtils;
/**
* Utility methods for the OAuth 2.0 Configurers.
*
* @author Joe Grandja
* @since 0.1.2
*/
final class OAuth2ConfigurerUtils {
private OAuth2ConfigurerUtils() {
}
static <B extends HttpSecurityBuilder<B>> RegisteredClientRepository getRegisteredClientRepository(B builder) {
RegisteredClientRepository registeredClientRepository = builder.getSharedObject(RegisteredClientRepository.class);
if (registeredClientRepository == null) {
registeredClientRepository = getBean(builder, RegisteredClientRepository.class);
builder.setSharedObject(RegisteredClientRepository.class, registeredClientRepository);
}
return registeredClientRepository;
}
static <B extends HttpSecurityBuilder<B>> OAuth2AuthorizationService getAuthorizationService(B builder) {
OAuth2AuthorizationService authorizationService = builder.getSharedObject(OAuth2AuthorizationService.class);
if (authorizationService == null) {
authorizationService = getOptionalBean(builder, OAuth2AuthorizationService.class);
if (authorizationService == null) {
authorizationService = new InMemoryOAuth2AuthorizationService();
}
builder.setSharedObject(OAuth2AuthorizationService.class, authorizationService);
}
return authorizationService;
}
static <B extends HttpSecurityBuilder<B>> OAuth2AuthorizationConsentService getAuthorizationConsentService(B builder) {
OAuth2AuthorizationConsentService authorizationConsentService = builder.getSharedObject(OAuth2AuthorizationConsentService.class);
if (authorizationConsentService == null) {
authorizationConsentService = getOptionalBean(builder, OAuth2AuthorizationConsentService.class);
if (authorizationConsentService == null) {
authorizationConsentService = new InMemoryOAuth2AuthorizationConsentService();
}
builder.setSharedObject(OAuth2AuthorizationConsentService.class, authorizationConsentService);
}
return authorizationConsentService;
}
@SuppressWarnings("unchecked")
static <B extends HttpSecurityBuilder<B>> OAuth2TokenGenerator<? extends OAuth2Token> getTokenGenerator(B builder) {
OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator = builder.getSharedObject(OAuth2TokenGenerator.class);
if (tokenGenerator == null) {
tokenGenerator = getOptionalBean(builder, OAuth2TokenGenerator.class);
if (tokenGenerator == null) {
JwtGenerator jwtGenerator = getJwtGenerator(builder);
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer = getAccessTokenCustomizer(builder);
if (accessTokenCustomizer != null) {
accessTokenGenerator.setAccessTokenCustomizer(accessTokenCustomizer);
}
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
if (jwtGenerator != null) {
tokenGenerator = new DelegatingOAuth2TokenGenerator(
jwtGenerator, accessTokenGenerator, refreshTokenGenerator);
} else {
tokenGenerator = new DelegatingOAuth2TokenGenerator(
accessTokenGenerator, refreshTokenGenerator);
}
}
builder.setSharedObject(OAuth2TokenGenerator.class, tokenGenerator);
}
return tokenGenerator;
}
private static <B extends HttpSecurityBuilder<B>> JwtGenerator getJwtGenerator(B builder) {
JwtGenerator jwtGenerator = builder.getSharedObject(JwtGenerator.class);
if (jwtGenerator == null) {
JwtEncoder jwtEncoder = getJwtEncoder(builder);
if (jwtEncoder != null) {
jwtGenerator = new JwtGenerator(jwtEncoder);
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = getJwtCustomizer(builder);
if (jwtCustomizer != null) {
jwtGenerator.setJwtCustomizer(jwtCustomizer);
}
builder.setSharedObject(JwtGenerator.class, jwtGenerator);
}
}
return jwtGenerator;
}
private static <B extends HttpSecurityBuilder<B>> JwtEncoder getJwtEncoder(B builder) {
JwtEncoder jwtEncoder = builder.getSharedObject(JwtEncoder.class);
if (jwtEncoder == null) {
jwtEncoder = getOptionalBean(builder, JwtEncoder.class);
if (jwtEncoder == null) {
JWKSource<SecurityContext> jwkSource = getJwkSource(builder);
if (jwkSource != null) {
jwtEncoder = new NimbusJwsEncoder(jwkSource);
}
}
if (jwtEncoder != null) {
builder.setSharedObject(JwtEncoder.class, jwtEncoder);
}
}
return jwtEncoder;
}
@SuppressWarnings("unchecked")
static <B extends HttpSecurityBuilder<B>> JWKSource<SecurityContext> getJwkSource(B builder) {
JWKSource<SecurityContext> jwkSource = builder.getSharedObject(JWKSource.class);
if (jwkSource == null) {
ResolvableType type = ResolvableType.forClassWithGenerics(JWKSource.class, SecurityContext.class);
jwkSource = getOptionalBean(builder, type);
if (jwkSource != null) {
builder.setSharedObject(JWKSource.class, jwkSource);
}
}
return jwkSource;
}
private static <B extends HttpSecurityBuilder<B>> OAuth2TokenCustomizer<JwtEncodingContext> getJwtCustomizer(B builder) {
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, JwtEncodingContext.class);
return getOptionalBean(builder, type);
}
private static <B extends HttpSecurityBuilder<B>> OAuth2TokenCustomizer<OAuth2TokenClaimsContext> getAccessTokenCustomizer(B builder) {
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, OAuth2TokenClaimsContext.class);
return getOptionalBean(builder, type);
}
static <B extends HttpSecurityBuilder<B>> ProviderSettings getProviderSettings(B builder) {
ProviderSettings providerSettings = builder.getSharedObject(ProviderSettings.class);
if (providerSettings == null) {
providerSettings = getBean(builder, ProviderSettings.class);
builder.setSharedObject(ProviderSettings.class, providerSettings);
}
return providerSettings;
}
static <B extends HttpSecurityBuilder<B>, T> T getBean(B builder, Class<T> type) {
return builder.getSharedObject(ApplicationContext.class).getBean(type);
}
@SuppressWarnings("unchecked")
static <B extends HttpSecurityBuilder<B>, T> T getBean(B builder, ResolvableType type) {
ApplicationContext context = builder.getSharedObject(ApplicationContext.class);
String[] names = context.getBeanNamesForType(type);
if (names.length == 1) {
return (T) context.getBean(names[0]);
}
if (names.length > 1) {
throw new NoUniqueBeanDefinitionException(type, names);
}
throw new NoSuchBeanDefinitionException(type);
}
static <B extends HttpSecurityBuilder<B>, T> T getOptionalBean(B builder, Class<T> type) {
Map<String, T> beansMap = BeanFactoryUtils.beansOfTypeIncludingAncestors(
builder.getSharedObject(ApplicationContext.class), type);
if (beansMap.size() > 1) {
throw new NoUniqueBeanDefinitionException(type, beansMap.size(),
"Expected single matching bean of type '" + type.getName() + "' but found " +
beansMap.size() + ": " + StringUtils.collectionToCommaDelimitedString(beansMap.keySet()));
}
return (!beansMap.isEmpty() ? beansMap.values().iterator().next() : null);
}
@SuppressWarnings("unchecked")
static <B extends HttpSecurityBuilder<B>, T> T getOptionalBean(B builder, ResolvableType type) {
ApplicationContext context = builder.getSharedObject(ApplicationContext.class);
String[] names = context.getBeanNamesForType(type);
if (names.length > 1) {
throw new NoUniqueBeanDefinitionException(type, names);
}
return names.length == 1 ? (T) context.getBean(names[0]) : null;
}
}

View File

@@ -0,0 +1,181 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
/**
* Configurer for the OAuth 2.0 Token Endpoint.
*
* @author Joe Grandja
* @since 0.1.2
* @see OAuth2AuthorizationServerConfigurer#tokenEndpoint
* @see OAuth2TokenEndpointFilter
*/
public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configurer {
private RequestMatcher requestMatcher;
private AuthenticationConverter accessTokenRequestConverter;
private final List<AuthenticationProvider> authenticationProviders = new LinkedList<>();
private AuthenticationSuccessHandler accessTokenResponseHandler;
private AuthenticationFailureHandler errorResponseHandler;
/**
* Restrict for internal use only.
*/
OAuth2TokenEndpointConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor);
}
/**
* Sets the {@link AuthenticationConverter} used when attempting to extract an Access Token Request from {@link HttpServletRequest}
* to an instance of {@link OAuth2AuthorizationGrantAuthenticationToken} used for authenticating the authorization grant.
*
* @param accessTokenRequestConverter the {@link AuthenticationConverter} used when attempting to extract an Access Token Request from {@link HttpServletRequest}
* @return the {@link OAuth2TokenEndpointConfigurer} for further configuration
*/
public OAuth2TokenEndpointConfigurer accessTokenRequestConverter(AuthenticationConverter accessTokenRequestConverter) {
this.accessTokenRequestConverter = accessTokenRequestConverter;
return this;
}
/**
* Adds an {@link AuthenticationProvider} used for authenticating a type of {@link OAuth2AuthorizationGrantAuthenticationToken}.
*
* @param authenticationProvider an {@link AuthenticationProvider} used for authenticating a type of {@link OAuth2AuthorizationGrantAuthenticationToken}
* @return the {@link OAuth2TokenEndpointConfigurer} for further configuration
*/
public OAuth2TokenEndpointConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) {
Assert.notNull(authenticationProvider, "authenticationProvider cannot be null");
this.authenticationProviders.add(authenticationProvider);
return this;
}
/**
* Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2AccessTokenAuthenticationToken}
* and returning the {@link OAuth2AccessTokenResponse Access Token Response}.
*
* @param accessTokenResponseHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2AccessTokenAuthenticationToken}
* @return the {@link OAuth2TokenEndpointConfigurer} for further configuration
*/
public OAuth2TokenEndpointConfigurer accessTokenResponseHandler(AuthenticationSuccessHandler accessTokenResponseHandler) {
this.accessTokenResponseHandler = accessTokenResponseHandler;
return this;
}
/**
* Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
* and returning the {@link OAuth2Error Error Response}.
*
* @param errorResponseHandler the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
* @return the {@link OAuth2TokenEndpointConfigurer} for further configuration
*/
public OAuth2TokenEndpointConfigurer errorResponseHandler(AuthenticationFailureHandler errorResponseHandler) {
this.errorResponseHandler = errorResponseHandler;
return this;
}
@Override
<B extends HttpSecurityBuilder<B>> void init(B builder) {
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
this.requestMatcher = new AntPathRequestMatcher(
providerSettings.getTokenEndpoint(), HttpMethod.POST.name());
List<AuthenticationProvider> authenticationProviders =
!this.authenticationProviders.isEmpty() ?
this.authenticationProviders :
createDefaultAuthenticationProviders(builder);
authenticationProviders.forEach(authenticationProvider ->
builder.authenticationProvider(postProcess(authenticationProvider)));
}
@Override
<B extends HttpSecurityBuilder<B>> void configure(B builder) {
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
OAuth2TokenEndpointFilter tokenEndpointFilter =
new OAuth2TokenEndpointFilter(
authenticationManager,
providerSettings.getTokenEndpoint());
if (this.accessTokenRequestConverter != null) {
tokenEndpointFilter.setAuthenticationConverter(this.accessTokenRequestConverter);
}
if (this.accessTokenResponseHandler != null) {
tokenEndpointFilter.setAuthenticationSuccessHandler(this.accessTokenResponseHandler);
}
if (this.errorResponseHandler != null) {
tokenEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler);
}
builder.addFilterAfter(postProcess(tokenEndpointFilter), FilterSecurityInterceptor.class);
}
@Override
RequestMatcher getRequestMatcher() {
return this.requestMatcher;
}
private <B extends HttpSecurityBuilder<B>> List<AuthenticationProvider> createDefaultAuthenticationProviders(B builder) {
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
OAuth2AuthorizationService authorizationService = OAuth2ConfigurerUtils.getAuthorizationService(builder);
OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator = OAuth2ConfigurerUtils.getTokenGenerator(builder);
OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider =
new OAuth2AuthorizationCodeAuthenticationProvider(authorizationService, tokenGenerator);
authenticationProviders.add(authorizationCodeAuthenticationProvider);
OAuth2RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider =
new OAuth2RefreshTokenAuthenticationProvider(authorizationService, tokenGenerator);
authenticationProviders.add(refreshTokenAuthenticationProvider);
OAuth2ClientCredentialsAuthenticationProvider clientCredentialsAuthenticationProvider =
new OAuth2ClientCredentialsAuthenticationProvider(authorizationService, tokenGenerator);
authenticationProviders.add(clientCredentialsAuthenticationProvider);
return authenticationProviders;
}
}

View File

@@ -0,0 +1,164 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
/**
* Configurer for the OAuth 2.0 Token Introspection Endpoint.
*
* @author Gaurav Tiwari
* @since 0.2.3
* @see OAuth2AuthorizationServerConfigurer#tokenIntrospectionEndpoint(Customizer)
* @see OAuth2TokenIntrospectionEndpointFilter
*/
public final class OAuth2TokenIntrospectionEndpointConfigurer extends AbstractOAuth2Configurer {
private RequestMatcher requestMatcher;
private AuthenticationConverter introspectionRequestConverter;
private final List<AuthenticationProvider> authenticationProviders = new LinkedList<>();
private AuthenticationSuccessHandler introspectionResponseHandler;
private AuthenticationFailureHandler errorResponseHandler;
/**
* Restrict for internal use only.
*/
OAuth2TokenIntrospectionEndpointConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor);
}
/**
* Sets the {@link AuthenticationConverter} used when attempting to extract an Introspection Request from {@link HttpServletRequest}
* to an instance of {@link OAuth2TokenIntrospectionAuthenticationToken} used for authenticating the request.
*
* @param introspectionRequestConverter the {@link AuthenticationConverter} used when attempting to extract an Introspection Request from {@link HttpServletRequest}
* @return the {@link OAuth2TokenIntrospectionEndpointConfigurer} for further configuration
*/
public OAuth2TokenIntrospectionEndpointConfigurer introspectionRequestConverter(AuthenticationConverter introspectionRequestConverter) {
this.introspectionRequestConverter = introspectionRequestConverter;
return this;
}
/**
* Adds an {@link AuthenticationProvider} used for authenticating a type of {@link OAuth2TokenIntrospectionAuthenticationToken}.
*
* @param authenticationProvider an {@link AuthenticationProvider} used for authenticating a type of {@link OAuth2TokenIntrospectionAuthenticationToken}
* @return the {@link OAuth2TokenIntrospectionEndpointConfigurer} for further configuration
*/
public OAuth2TokenIntrospectionEndpointConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) {
Assert.notNull(authenticationProvider, "authenticationProvider cannot be null");
this.authenticationProviders.add(authenticationProvider);
return this;
}
/**
* Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2TokenIntrospectionAuthenticationToken}.
*
* @param introspectionResponseHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2TokenIntrospectionAuthenticationToken}
* @return the {@link OAuth2TokenIntrospectionEndpointConfigurer} for further configuration
*/
public OAuth2TokenIntrospectionEndpointConfigurer introspectionResponseHandler(AuthenticationSuccessHandler introspectionResponseHandler) {
this.introspectionResponseHandler = introspectionResponseHandler;
return this;
}
/**
* Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
* and returning the {@link OAuth2Error Error Response}.
*
* @param errorResponseHandler the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
* @return the {@link OAuth2TokenIntrospectionEndpointConfigurer} for further configuration
*/
public OAuth2TokenIntrospectionEndpointConfigurer errorResponseHandler(AuthenticationFailureHandler errorResponseHandler) {
this.errorResponseHandler = errorResponseHandler;
return this;
}
@Override
<B extends HttpSecurityBuilder<B>> void init(B builder) {
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
this.requestMatcher = new AntPathRequestMatcher(
providerSettings.getTokenIntrospectionEndpoint(), HttpMethod.POST.name());
List<AuthenticationProvider> authenticationProviders =
!this.authenticationProviders.isEmpty() ?
this.authenticationProviders :
createDefaultAuthenticationProviders(builder);
authenticationProviders.forEach(authenticationProvider ->
builder.authenticationProvider(postProcess(authenticationProvider)));
}
@Override
<B extends HttpSecurityBuilder<B>> void configure(B builder) {
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
OAuth2TokenIntrospectionEndpointFilter introspectionEndpointFilter =
new OAuth2TokenIntrospectionEndpointFilter(
authenticationManager, providerSettings.getTokenIntrospectionEndpoint());
if (this.introspectionRequestConverter != null) {
introspectionEndpointFilter.setAuthenticationConverter(this.introspectionRequestConverter);
}
if (this.introspectionResponseHandler != null) {
introspectionEndpointFilter.setAuthenticationSuccessHandler(this.introspectionResponseHandler);
}
if (this.errorResponseHandler != null) {
introspectionEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler);
}
builder.addFilterAfter(postProcess(introspectionEndpointFilter), FilterSecurityInterceptor.class);
}
@Override
public RequestMatcher getRequestMatcher() {
return this.requestMatcher;
}
private <B extends HttpSecurityBuilder<B>> List<AuthenticationProvider> createDefaultAuthenticationProviders(B builder) {
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
OAuth2TokenIntrospectionAuthenticationProvider tokenIntrospectionAuthenticationProvider =
new OAuth2TokenIntrospectionAuthenticationProvider(
OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
OAuth2ConfigurerUtils.getAuthorizationService(builder));
authenticationProviders.add(tokenIntrospectionAuthenticationProvider);
return authenticationProviders;
}
}

View File

@@ -0,0 +1,161 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
/**
* Configurer for the OAuth 2.0 Token Revocation Endpoint.
*
* @author Arfat Chaus
* @since 0.2.2
* @see OAuth2AuthorizationServerConfigurer#tokenRevocationEndpoint
* @see OAuth2TokenRevocationEndpointFilter
*/
public final class OAuth2TokenRevocationEndpointConfigurer extends AbstractOAuth2Configurer {
private RequestMatcher requestMatcher;
private AuthenticationConverter revocationRequestConverter;
private final List<AuthenticationProvider> authenticationProviders = new LinkedList<>();
private AuthenticationSuccessHandler revocationResponseHandler;
private AuthenticationFailureHandler errorResponseHandler;
/**
* Restrict for internal use only.
*/
OAuth2TokenRevocationEndpointConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor);
}
/**
* Sets the {@link AuthenticationConverter} used when attempting to extract a Revoke Token Request from {@link HttpServletRequest}
* to an instance of {@link OAuth2TokenRevocationAuthenticationToken} used for authenticating the client.
*
* @param revocationRequestConverter the {@link AuthenticationConverter} used when attempting to extract client credentials from {@link HttpServletRequest}
* @return the {@link OAuth2TokenRevocationEndpointConfigurer} for further configuration
*/
public OAuth2TokenRevocationEndpointConfigurer revocationRequestConverter(AuthenticationConverter revocationRequestConverter) {
this.revocationRequestConverter = revocationRequestConverter;
return this;
}
/**
* Adds an {@link AuthenticationProvider} used for authenticating a type of {@link OAuth2TokenRevocationAuthenticationToken}.
*
* @param authenticationProvider an {@link AuthenticationProvider} used for authenticating a type of {@link OAuth2TokenRevocationAuthenticationToken}
* @return the {@link OAuth2TokenRevocationEndpointConfigurer} for further configuration
*/
public OAuth2TokenRevocationEndpointConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) {
Assert.notNull(authenticationProvider, "authenticationProvider cannot be null");
this.authenticationProviders.add(authenticationProvider);
return this;
}
/**
* Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2TokenRevocationAuthenticationToken}.
*
* @param revocationResponseHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2TokenRevocationAuthenticationToken}
* @return the {@link OAuth2TokenRevocationEndpointConfigurer} for further configuration
*/
public OAuth2TokenRevocationEndpointConfigurer revocationResponseHandler(AuthenticationSuccessHandler revocationResponseHandler) {
this.revocationResponseHandler = revocationResponseHandler;
return this;
}
/**
* Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
* and returning the {@link OAuth2Error Error Response}.
*
* @param errorResponseHandler the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
* @return the {@link OAuth2TokenRevocationEndpointConfigurer} for further configuration
*/
public OAuth2TokenRevocationEndpointConfigurer errorResponseHandler(AuthenticationFailureHandler errorResponseHandler) {
this.errorResponseHandler = errorResponseHandler;
return this;
}
@Override
<B extends HttpSecurityBuilder<B>> void init(B builder) {
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
this.requestMatcher = new AntPathRequestMatcher(
providerSettings.getTokenRevocationEndpoint(), HttpMethod.POST.name());
List<AuthenticationProvider> authenticationProviders =
!this.authenticationProviders.isEmpty() ?
this.authenticationProviders :
createDefaultAuthenticationProviders(builder);
authenticationProviders.forEach(authenticationProvider ->
builder.authenticationProvider(postProcess(authenticationProvider)));
}
@Override
<B extends HttpSecurityBuilder<B>> void configure(B builder) {
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
OAuth2TokenRevocationEndpointFilter revocationEndpointFilter =
new OAuth2TokenRevocationEndpointFilter(
authenticationManager, providerSettings.getTokenRevocationEndpoint());
if (this.revocationRequestConverter != null) {
revocationEndpointFilter.setAuthenticationConverter(this.revocationRequestConverter);
}
if (this.revocationResponseHandler != null) {
revocationEndpointFilter.setAuthenticationSuccessHandler(this.revocationResponseHandler);
}
if (this.errorResponseHandler != null) {
revocationEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler);
}
builder.addFilterAfter(postProcess(revocationEndpointFilter), FilterSecurityInterceptor.class);
}
@Override
RequestMatcher getRequestMatcher() {
return this.requestMatcher;
}
private <B extends HttpSecurityBuilder<B>> List<AuthenticationProvider> createDefaultAuthenticationProviders(B builder) {
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
OAuth2TokenRevocationAuthenticationProvider tokenRevocationAuthenticationProvider =
new OAuth2TokenRevocationAuthenticationProvider(OAuth2ConfigurerUtils.getAuthorizationService(builder));
authenticationProviders.add(tokenRevocationAuthenticationProvider);
return authenticationProviders;
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
/**
* Configurer for OpenID Connect Dynamic Client Registration 1.0 Endpoint.
*
* @author Joe Grandja
* @since 0.2.0
* @see OidcConfigurer#clientRegistrationEndpoint
* @see OidcClientRegistrationEndpointFilter
*/
public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAuth2Configurer {
private RequestMatcher requestMatcher;
/**
* Restrict for internal use only.
*/
OidcClientRegistrationEndpointConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor);
}
@Override
<B extends HttpSecurityBuilder<B>> void init(B builder) {
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
this.requestMatcher = new OrRequestMatcher(
new AntPathRequestMatcher(providerSettings.getOidcClientRegistrationEndpoint(), HttpMethod.POST.name()),
new AntPathRequestMatcher(providerSettings.getOidcClientRegistrationEndpoint(), HttpMethod.GET.name())
);
OidcClientRegistrationAuthenticationProvider oidcClientRegistrationAuthenticationProvider =
new OidcClientRegistrationAuthenticationProvider(
OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
OAuth2ConfigurerUtils.getAuthorizationService(builder),
OAuth2ConfigurerUtils.getTokenGenerator(builder));
builder.authenticationProvider(postProcess(oidcClientRegistrationAuthenticationProvider));
}
@Override
<B extends HttpSecurityBuilder<B>> void configure(B builder) {
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
OidcClientRegistrationEndpointFilter oidcClientRegistrationEndpointFilter =
new OidcClientRegistrationEndpointFilter(
authenticationManager,
providerSettings.getOidcClientRegistrationEndpoint());
builder.addFilterAfter(postProcess(oidcClientRegistrationEndpointFilter), FilterSecurityInterceptor.class);
}
@Override
RequestMatcher getRequestMatcher() {
return this.requestMatcher;
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
/**
* Configurer for OpenID Connect 1.0 support.
*
* @author Joe Grandja
* @since 0.2.0
* @see OAuth2AuthorizationServerConfigurer#oidc
* @see OidcClientRegistrationEndpointConfigurer
* @see OidcUserInfoEndpointConfigurer
* @see OidcProviderConfigurationEndpointFilter
*/
public final class OidcConfigurer extends AbstractOAuth2Configurer {
private final Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = new LinkedHashMap<>();
private RequestMatcher requestMatcher;
/**
* Restrict for internal use only.
*/
OidcConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor);
addConfigurer(OidcUserInfoEndpointConfigurer.class, new OidcUserInfoEndpointConfigurer(objectPostProcessor));
}
/**
* Configures the OpenID Connect Dynamic Client Registration 1.0 Endpoint.
*
* @param clientRegistrationEndpointCustomizer the {@link Customizer} providing access to the {@link OidcClientRegistrationEndpointConfigurer}
* @return the {@link OidcConfigurer} for further configuration
*/
public OidcConfigurer clientRegistrationEndpoint(Customizer<OidcClientRegistrationEndpointConfigurer> clientRegistrationEndpointCustomizer) {
OidcClientRegistrationEndpointConfigurer clientRegistrationEndpointConfigurer =
getConfigurer(OidcClientRegistrationEndpointConfigurer.class);
if (clientRegistrationEndpointConfigurer == null) {
addConfigurer(OidcClientRegistrationEndpointConfigurer.class,
new OidcClientRegistrationEndpointConfigurer(getObjectPostProcessor()));
clientRegistrationEndpointConfigurer = getConfigurer(OidcClientRegistrationEndpointConfigurer.class);
}
clientRegistrationEndpointCustomizer.customize(clientRegistrationEndpointConfigurer);
return this;
}
/**
* Configures the OpenID Connect 1.0 UserInfo Endpoint.
*
* @param userInfoEndpointCustomizer the {@link Customizer} providing access to the {@link OidcUserInfoEndpointConfigurer}
* @return the {@link OidcConfigurer} for further configuration
*/
public OidcConfigurer userInfoEndpoint(Customizer<OidcUserInfoEndpointConfigurer> userInfoEndpointCustomizer) {
userInfoEndpointCustomizer.customize(getConfigurer(OidcUserInfoEndpointConfigurer.class));
return this;
}
@Override
<B extends HttpSecurityBuilder<B>> void init(B builder) {
OidcUserInfoEndpointConfigurer userInfoEndpointConfigurer =
getConfigurer(OidcUserInfoEndpointConfigurer.class);
userInfoEndpointConfigurer.init(builder);
OidcClientRegistrationEndpointConfigurer clientRegistrationEndpointConfigurer =
getConfigurer(OidcClientRegistrationEndpointConfigurer.class);
if (clientRegistrationEndpointConfigurer != null) {
clientRegistrationEndpointConfigurer.init(builder);
}
List<RequestMatcher> requestMatchers = new ArrayList<>();
requestMatchers.add(new AntPathRequestMatcher(
"/.well-known/openid-configuration", HttpMethod.GET.name()));
requestMatchers.add(userInfoEndpointConfigurer.getRequestMatcher());
if (clientRegistrationEndpointConfigurer != null) {
requestMatchers.add(clientRegistrationEndpointConfigurer.getRequestMatcher());
}
this.requestMatcher = new OrRequestMatcher(requestMatchers);
}
@Override
<B extends HttpSecurityBuilder<B>> void configure(B builder) {
OidcUserInfoEndpointConfigurer userInfoEndpointConfigurer =
getConfigurer(OidcUserInfoEndpointConfigurer.class);
userInfoEndpointConfigurer.configure(builder);
OidcClientRegistrationEndpointConfigurer clientRegistrationEndpointConfigurer =
getConfigurer(OidcClientRegistrationEndpointConfigurer.class);
if (clientRegistrationEndpointConfigurer != null) {
clientRegistrationEndpointConfigurer.configure(builder);
}
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
OidcProviderConfigurationEndpointFilter oidcProviderConfigurationEndpointFilter =
new OidcProviderConfigurationEndpointFilter(providerSettings);
builder.addFilterBefore(postProcess(oidcProviderConfigurationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
}
@Override
RequestMatcher getRequestMatcher() {
return this.requestMatcher;
}
@SuppressWarnings("unchecked")
<T> T getConfigurer(Class<T> type) {
return (T) this.configurers.get(type);
}
private <T extends AbstractOAuth2Configurer> void addConfigurer(Class<T> configurerType, T configurer) {
this.configurers.put(configurerType, configurer);
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import java.util.function.Function;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcUserInfoEndpointFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
/**
* Configurer for OpenID Connect 1.0 UserInfo Endpoint.
*
* @author Steve Riesenberg
* @since 0.2.1
* @see OidcConfigurer#userInfoEndpoint
* @see OidcUserInfoEndpointFilter
*/
public final class OidcUserInfoEndpointConfigurer extends AbstractOAuth2Configurer {
private RequestMatcher requestMatcher;
private Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper;
/**
* Restrict for internal use only.
*/
OidcUserInfoEndpointConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor);
}
/**
* Sets the {@link Function} used to extract claims from {@link OidcUserInfoAuthenticationContext}
* to an instance of {@link OidcUserInfo} for the UserInfo response.
*
* <p>
* The {@link OidcUserInfoAuthenticationContext} gives the mapper access to the {@link OidcUserInfoAuthenticationToken},
* as well as, the following context attributes:
* <ul>
* <li>{@link OidcUserInfoAuthenticationContext#getAccessToken()} containing the bearer token used to make the request.</li>
* <li>{@link OidcUserInfoAuthenticationContext#getAuthorization()} containing the {@link OidcIdToken} and
* {@link OAuth2AccessToken} associated with the bearer token used to make the request.</li>
* </ul>
*
* @param userInfoMapper the {@link Function} used to extract claims from {@link OidcUserInfoAuthenticationContext} to an instance of {@link OidcUserInfo}
* @return the {@link OidcUserInfoEndpointConfigurer} for further configuration
*/
public OidcUserInfoEndpointConfigurer userInfoMapper(Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper) {
this.userInfoMapper = userInfoMapper;
return this;
}
@Override
<B extends HttpSecurityBuilder<B>> void init(B builder) {
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
String userInfoEndpointUri = providerSettings.getOidcUserInfoEndpoint();
this.requestMatcher = new OrRequestMatcher(
new AntPathRequestMatcher(userInfoEndpointUri, HttpMethod.GET.name()),
new AntPathRequestMatcher(userInfoEndpointUri, HttpMethod.POST.name()));
OidcUserInfoAuthenticationProvider oidcUserInfoAuthenticationProvider =
new OidcUserInfoAuthenticationProvider(
OAuth2ConfigurerUtils.getAuthorizationService(builder));
if (this.userInfoMapper != null) {
oidcUserInfoAuthenticationProvider.setUserInfoMapper(this.userInfoMapper);
}
builder.authenticationProvider(postProcess(oidcUserInfoAuthenticationProvider));
}
@Override
<B extends HttpSecurityBuilder<B>> void configure(B builder) {
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
OidcUserInfoEndpointFilter oidcUserInfoEndpointFilter =
new OidcUserInfoEndpointFilter(
authenticationManager,
providerSettings.getOidcUserInfoEndpoint());
builder.addFilterAfter(postProcess(oidcUserInfoEndpointFilter), FilterSecurityInterceptor.class);
}
@Override
RequestMatcher getRequestMatcher() {
return this.requestMatcher;
}
}

View File

@@ -1,58 +0,0 @@
/*
* Copyright 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.security.crypto.keys;
import org.springframework.lang.Nullable;
import java.util.Set;
/**
* Implementations of this interface are responsible for the management of {@link ManagedKey}(s),
* e.g. {@code javax.crypto.SecretKey}, {@code java.security.PrivateKey}, {@code java.security.PublicKey}, etc.
*
* @author Joe Grandja
* @since 0.0.1
* @see ManagedKey
*/
public interface KeyManager {
/**
* Returns the {@link ManagedKey} identified by the provided {@code keyId},
* or {@code null} if not found.
*
* @param keyId the key ID
* @return the {@link ManagedKey}, or {@code null} if not found
*/
@Nullable
ManagedKey findByKeyId(String keyId);
/**
* Returns a {@code Set} of {@link ManagedKey}(s) having the provided key {@code algorithm},
* or an empty {@code Set} if not found.
*
* @param algorithm the key algorithm
* @return a {@code Set} of {@link ManagedKey}(s), or an empty {@code Set} if not found
*/
Set<ManagedKey> findByAlgorithm(String algorithm);
/**
* Returns a {@code Set} of the {@link ManagedKey}(s).
*
* @return a {@code Set} of the {@link ManagedKey}(s)
*/
Set<ManagedKey> getKeys();
}

View File

@@ -1,246 +0,0 @@
/*
* Copyright 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.security.crypto.keys;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.server.authorization.Version;
import org.springframework.util.Assert;
import javax.crypto.SecretKey;
import java.io.Serializable;
import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.time.Instant;
import java.util.Objects;
/**
* A {@code java.security.Key} that is managed by a {@link KeyManager}.
*
* @author Joe Grandja
* @since 0.0.1
* @see KeyManager
*/
public final class ManagedKey implements Serializable {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
private Key key;
private PublicKey publicKey;
private String keyId;
private Instant activatedOn;
private Instant deactivatedOn;
private ManagedKey() {
}
/**
* Returns {@code true} if this is a symmetric key, {@code false} otherwise.
*
* @return {@code true} if this is a symmetric key, {@code false} otherwise
*/
public boolean isSymmetric() {
return SecretKey.class.isAssignableFrom(this.key.getClass());
}
/**
* Returns {@code true} if this is a asymmetric key, {@code false} otherwise.
*
* @return {@code true} if this is a asymmetric key, {@code false} otherwise
*/
public boolean isAsymmetric() {
return PrivateKey.class.isAssignableFrom(this.key.getClass());
}
/**
* Returns a type of {@code java.security.Key},
* e.g. {@code javax.crypto.SecretKey} or {@code java.security.PrivateKey}.
*
* @param <T> the type of {@code java.security.Key}
* @return the type of {@code java.security.Key}
*/
@SuppressWarnings("unchecked")
public <T extends Key> T getKey() {
return (T) this.key;
}
/**
* Returns the {@code java.security.PublicKey} if this is a asymmetric key, {@code null} otherwise.
*
* @return the {@code java.security.PublicKey} if this is a asymmetric key, {@code null} otherwise
*/
@Nullable
public PublicKey getPublicKey() {
return this.publicKey;
}
/**
* Returns the key ID.
*
* @return the key ID
*/
public String getKeyId() {
return this.keyId;
}
/**
* Returns the time when this key was activated.
*
* @return the time when this key was activated
*/
public Instant getActivatedOn() {
return this.activatedOn;
}
/**
* Returns the time when this key was deactivated, {@code null} if still active.
*
* @return the time when this key was deactivated, {@code null} if still active
*/
@Nullable
public Instant getDeactivatedOn() {
return this.deactivatedOn;
}
/**
* Returns {@code true} if this key is active, {@code false} otherwise.
*
* @return {@code true} if this key is active, {@code false} otherwise
*/
public boolean isActive() {
return getDeactivatedOn() == null;
}
/**
* Returns the key algorithm.
*
* @return the key algorithm
*/
public String getAlgorithm() {
return this.key.getAlgorithm();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
ManagedKey that = (ManagedKey) obj;
return Objects.equals(this.keyId, that.keyId);
}
@Override
public int hashCode() {
return Objects.hash(this.keyId);
}
/**
* Returns a new {@link Builder}, initialized with the provided {@code javax.crypto.SecretKey}.
*
* @param secretKey the {@code javax.crypto.SecretKey}
* @return the {@link Builder}
*/
public static Builder withSymmetricKey(SecretKey secretKey) {
return new Builder(secretKey);
}
/**
* Returns a new {@link Builder}, initialized with the provided
* {@code java.security.PublicKey} and {@code java.security.PrivateKey}.
*
* @param publicKey the {@code java.security.PublicKey}
* @param privateKey the {@code java.security.PrivateKey}
* @return the {@link Builder}
*/
public static Builder withAsymmetricKey(PublicKey publicKey, PrivateKey privateKey) {
return new Builder(publicKey, privateKey);
}
/**
* A builder for {@link ManagedKey}.
*/
public static class Builder {
private Key key;
private PublicKey publicKey;
private String keyId;
private Instant activatedOn;
private Instant deactivatedOn;
private Builder(SecretKey secretKey) {
Assert.notNull(secretKey, "secretKey cannot be null");
this.key = secretKey;
}
private Builder(PublicKey publicKey, PrivateKey privateKey) {
Assert.notNull(publicKey, "publicKey cannot be null");
Assert.notNull(privateKey, "privateKey cannot be null");
this.key = privateKey;
this.publicKey = publicKey;
}
/**
* Sets the key ID.
*
* @param keyId the key ID
* @return the {@link Builder}
*/
public Builder keyId(String keyId) {
this.keyId = keyId;
return this;
}
/**
* Sets the time when this key was activated.
*
* @param activatedOn the time when this key was activated
* @return the {@link Builder}
*/
public Builder activatedOn(Instant activatedOn) {
this.activatedOn = activatedOn;
return this;
}
/**
* Sets the time when this key was deactivated.
*
* @param deactivatedOn the time when this key was deactivated
* @return the {@link Builder}
*/
public Builder deactivatedOn(Instant deactivatedOn) {
this.deactivatedOn = deactivatedOn;
return this;
}
/**
* Builds a new {@link ManagedKey}.
*
* @return a {@link ManagedKey}
*/
public ManagedKey build() {
Assert.hasText(this.keyId, "keyId cannot be empty");
Assert.notNull(this.activatedOn, "activatedOn cannot be null");
ManagedKey managedKey = new ManagedKey();
managedKey.key = this.key;
managedKey.publicKey = this.publicKey;
managedKey.keyId = this.keyId;
managedKey.activatedOn = this.activatedOn;
managedKey.deactivatedOn = this.deactivatedOn;
return managedKey;
}
}
}

View File

@@ -1,89 +0,0 @@
/*
* Copyright 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.security.crypto.keys;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import javax.crypto.SecretKey;
import java.security.KeyPair;
import java.time.Instant;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.springframework.security.crypto.keys.KeyGeneratorUtils.generateRsaKey;
import static org.springframework.security.crypto.keys.KeyGeneratorUtils.generateSecretKey;
/**
* An implementation of a {@link KeyManager} that generates the {@link ManagedKey}(s) when constructed.
*
* <p>
* <b>NOTE:</b> This implementation should ONLY be used during development/testing.
*
* @author Joe Grandja
* @since 0.0.1
* @see KeyManager
*/
public final class StaticKeyGeneratingKeyManager implements KeyManager {
private final Map<String, ManagedKey> keys;
public StaticKeyGeneratingKeyManager() {
this.keys = Collections.unmodifiableMap(new HashMap<>(generateKeys()));
}
@Nullable
@Override
public ManagedKey findByKeyId(String keyId) {
Assert.hasText(keyId, "keyId cannot be empty");
return this.keys.get(keyId);
}
@Override
public Set<ManagedKey> findByAlgorithm(String algorithm) {
Assert.hasText(algorithm, "algorithm cannot be empty");
return this.keys.values().stream()
.filter(managedKey -> managedKey.getAlgorithm().equals(algorithm))
.collect(Collectors.toSet());
}
@Override
public Set<ManagedKey> getKeys() {
return new HashSet<>(this.keys.values());
}
private static Map<String, ManagedKey> generateKeys() {
KeyPair rsaKeyPair = generateRsaKey();
ManagedKey rsaManagedKey = ManagedKey.withAsymmetricKey(rsaKeyPair.getPublic(), rsaKeyPair.getPrivate())
.keyId(UUID.randomUUID().toString())
.activatedOn(Instant.now())
.build();
SecretKey hmacKey = generateSecretKey();
ManagedKey secretManagedKey = ManagedKey.withSymmetricKey(hmacKey)
.keyId(UUID.randomUUID().toString())
.activatedOn(Instant.now())
.build();
return Stream.of(rsaManagedKey, secretManagedKey)
.collect(Collectors.toMap(ManagedKey::getKeyId, v -> v));
}
}

View File

@@ -0,0 +1,407 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core;
import java.io.Serializable;
import java.net.URI;
import java.net.URL;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.springframework.util.Assert;
/**
* A base representation of OAuth 2.0 Authorization Server metadata,
* returned by an endpoint defined in OAuth 2.0 Authorization Server Metadata and OpenID Connect Discovery 1.0.
* The metadata endpoint returns a set of claims an Authorization Server describes about its configuration.
*
* @author Daniel Garnier-Moiroux
* @see OAuth2AuthorizationServerMetadataClaimAccessor
* @since 0.1.1
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-3.2">3.2. Authorization Server Metadata Response</a>
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse">4.2. OpenID Provider Configuration Response</a>
*/
public abstract class AbstractOAuth2AuthorizationServerMetadata implements OAuth2AuthorizationServerMetadataClaimAccessor, Serializable {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
private final Map<String, Object> claims;
protected AbstractOAuth2AuthorizationServerMetadata(Map<String, Object> claims) {
Assert.notEmpty(claims, "claims cannot be empty");
this.claims = Collections.unmodifiableMap(new LinkedHashMap<>(claims));
}
/**
* Returns the metadata as claims.
*
* @return a {@code Map} of the metadata as claims
*/
@Override
public Map<String, Object> getClaims() {
return this.claims;
}
/**
* A builder for subclasses of {@link AbstractOAuth2AuthorizationServerMetadata}.
*/
protected static abstract class AbstractBuilder<T extends AbstractOAuth2AuthorizationServerMetadata, B extends AbstractBuilder<T, B>> {
private final Map<String, Object> claims = new LinkedHashMap<>();
protected AbstractBuilder() {
}
protected Map<String, Object> getClaims() {
return this.claims;
}
@SuppressWarnings("unchecked")
protected final B getThis() {
return (B) this; // avoid unchecked casts in subclasses by using "getThis()" instead of "(B) this"
}
/**
* Use this {@code issuer} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, REQUIRED.
*
* @param issuer the {@code URL} of the Authorization Server's Issuer Identifier
* @return the {@link AbstractBuilder} for further configuration
*/
public B issuer(String issuer) {
return claim(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, issuer);
}
/**
* Use this {@code authorization_endpoint} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, REQUIRED.
*
* @param authorizationEndpoint the {@code URL} of the OAuth 2.0 Authorization Endpoint
* @return the {@link AbstractBuilder} for further configuration
*/
public B authorizationEndpoint(String authorizationEndpoint) {
return claim(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, authorizationEndpoint);
}
/**
* Use this {@code token_endpoint} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, REQUIRED.
*
* @param tokenEndpoint the {@code URL} of the OAuth 2.0 Token Endpoint
* @return the {@link AbstractBuilder} for further configuration
*/
public B tokenEndpoint(String tokenEndpoint) {
return claim(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, tokenEndpoint);
}
/**
* Add this client authentication method to the collection of {@code token_endpoint_auth_methods_supported}
* in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
*
* @param authenticationMethod the client authentication method supported by the OAuth 2.0 Token Endpoint
* @return the {@link AbstractBuilder} for further configuration
*/
public B tokenEndpointAuthenticationMethod(String authenticationMethod) {
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethod);
return getThis();
}
/**
* A {@code Consumer} of the client authentication method(s) allowing the ability to add, replace, or remove.
*
* @param authenticationMethodsConsumer a {@code Consumer} of the client authentication method(s) supported by the OAuth 2.0 Token Endpoint
* @return the {@link AbstractBuilder} for further configuration
*/
public B tokenEndpointAuthenticationMethods(Consumer<List<String>> authenticationMethodsConsumer) {
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethodsConsumer);
return getThis();
}
/**
* Use this {@code jwks_uri} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
*
* @param jwkSetUrl the {@code URL} of the JSON Web Key Set
* @return the {@link AbstractBuilder} for further configuration
*/
public B jwkSetUrl(String jwkSetUrl) {
return claim(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI, jwkSetUrl);
}
/**
* Add this OAuth 2.0 {@code scope} to the collection of {@code scopes_supported}
* in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, RECOMMENDED.
*
* @param scope the OAuth 2.0 {@code scope} value supported
* @return the {@link AbstractBuilder} for further configuration
*/
public B scope(String scope) {
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, scope);
return getThis();
}
/**
* A {@code Consumer} of the OAuth 2.0 {@code scope} values supported allowing the ability to add, replace, or remove.
*
* @param scopesConsumer a {@code Consumer} of the OAuth 2.0 {@code scope} values supported
* @return the {@link AbstractBuilder} for further configuration
*/
public B scopes(Consumer<List<String>> scopesConsumer) {
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, scopesConsumer);
return getThis();
}
/**
* Add this OAuth 2.0 {@code response_type} to the collection of {@code response_types_supported}
* in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, REQUIRED.
*
* @param responseType the OAuth 2.0 {@code response_type} value supported
* @return the {@link AbstractBuilder} for further configuration
*/
public B responseType(String responseType) {
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, responseType);
return getThis();
}
/**
* A {@code Consumer} of the OAuth 2.0 {@code response_type} values supported allowing the ability to add, replace, or remove.
*
* @param responseTypesConsumer a {@code Consumer} of the OAuth 2.0 {@code response_type} values supported
* @return the {@link AbstractBuilder} for further configuration
*/
public B responseTypes(Consumer<List<String>> responseTypesConsumer) {
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, responseTypesConsumer);
return getThis();
}
/**
* Add this OAuth 2.0 {@code grant_type} to the collection of {@code grant_types_supported}
* in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
*
* @param grantType the OAuth 2.0 {@code grant_type} value supported
* @return the {@link AbstractBuilder} for further configuration
*/
public B grantType(String grantType) {
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, grantType);
return getThis();
}
/**
* A {@code Consumer} of the OAuth 2.0 {@code grant_type} values supported allowing the ability to add, replace, or remove.
*
* @param grantTypesConsumer a {@code Consumer} of the OAuth 2.0 {@code grant_type} values supported
* @return the {@link AbstractBuilder} for further configuration
*/
public B grantTypes(Consumer<List<String>> grantTypesConsumer) {
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, grantTypesConsumer);
return getThis();
}
/**
* Use this {@code revocation_endpoint} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
*
* @param tokenRevocationEndpoint the {@code URL} of the OAuth 2.0 Token Revocation Endpoint
* @return the {@link AbstractBuilder} for further configuration
*/
public B tokenRevocationEndpoint(String tokenRevocationEndpoint) {
return claim(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT, tokenRevocationEndpoint);
}
/**
* Add this client authentication method to the collection of {@code revocation_endpoint_auth_methods_supported}
* in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
*
* @param authenticationMethod the client authentication method supported by the OAuth 2.0 Token Revocation Endpoint
* @return the {@link AbstractBuilder} for further configuration
*/
public B tokenRevocationEndpointAuthenticationMethod(String authenticationMethod) {
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethod);
return getThis();
}
/**
* A {@code Consumer} of the client authentication method(s) allowing the ability to add, replace, or remove.
*
* @param authenticationMethodsConsumer a {@code Consumer} of the client authentication method(s) supported by the OAuth 2.0 Token Revocation Endpoint
* @return the {@link AbstractBuilder} for further configuration
*/
public B tokenRevocationEndpointAuthenticationMethods(Consumer<List<String>> authenticationMethodsConsumer) {
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethodsConsumer);
return getThis();
}
/**
* Use this {@code introspection_endpoint} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
*
* @param tokenIntrospectionEndpoint the {@code URL} of the OAuth 2.0 Token Introspection Endpoint
* @return the {@link AbstractBuilder} for further configuration
*/
public B tokenIntrospectionEndpoint(String tokenIntrospectionEndpoint) {
return claim(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT, tokenIntrospectionEndpoint);
}
/**
* Add this client authentication method to the collection of {@code introspection_endpoint_auth_methods_supported}
* in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
*
* @param authenticationMethod the client authentication method supported by the OAuth 2.0 Token Introspection Endpoint
* @return the {@link AbstractBuilder} for further configuration
*/
public B tokenIntrospectionEndpointAuthenticationMethod(String authenticationMethod) {
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethod);
return getThis();
}
/**
* A {@code Consumer} of the client authentication method(s) allowing the ability to add, replace, or remove.
*
* @param authenticationMethodsConsumer a {@code Consumer} of the client authentication method(s) supported by the OAuth 2.0 Token Introspection Endpoint
* @return the {@link AbstractBuilder} for further configuration
*/
public B tokenIntrospectionEndpointAuthenticationMethods(Consumer<List<String>> authenticationMethodsConsumer) {
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethodsConsumer);
return getThis();
}
/**
* Add this Proof Key for Code Exchange (PKCE) {@code code_challenge_method} to the collection of {@code code_challenge_methods_supported}
* in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
*
* @param codeChallengeMethod the {@code code_challenge_method} value supported
* @return the {@link AbstractBuilder} for further configuration
*/
public B codeChallengeMethod(String codeChallengeMethod) {
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED, codeChallengeMethod);
return getThis();
}
/**
* A {@code Consumer} of the Proof Key for Code Exchange (PKCE) {@code code_challenge_method} values supported allowing the ability to add, replace, or remove.
*
* @param codeChallengeMethodsConsumer a {@code Consumer} of the {@code code_challenge_method} values supported
* @return the {@link AbstractBuilder} for further configuration
*/
public B codeChallengeMethods(Consumer<List<String>> codeChallengeMethodsConsumer) {
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED, codeChallengeMethodsConsumer);
return getThis();
}
/**
* Use this claim in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}.
*
* @param name the claim name
* @param value the claim value
* @return the {@link AbstractBuilder} for further configuration
*/
public B claim(String name, Object value) {
Assert.hasText(name, "name cannot be empty");
Assert.notNull(value, "value cannot be null");
this.claims.put(name, value);
return getThis();
}
/**
* Provides access to every {@link #claim(String, Object)} declared so far with
* the possibility to add, replace, or remove.
*
* @param claimsConsumer a {@code Consumer} of the claims
* @return the {@link AbstractBuilder} for further configurations
*/
public B claims(Consumer<Map<String, Object>> claimsConsumer) {
claimsConsumer.accept(this.claims);
return getThis();
}
/**
* Creates the {@link AbstractOAuth2AuthorizationServerMetadata}.
*
* @return the {@link AbstractOAuth2AuthorizationServerMetadata}
*/
public abstract T build();
protected void validate() {
Assert.notNull(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.ISSUER), "issuer cannot be null");
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.ISSUER), "issuer must be a valid URL");
Assert.notNull(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT), "authorizationEndpoint cannot be null");
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT), "authorizationEndpoint must be a valid URL");
Assert.notNull(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT), "tokenEndpoint cannot be null");
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT), "tokenEndpoint must be a valid URL");
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED) != null) {
Assert.isInstanceOf(List.class, getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED), "tokenEndpointAuthenticationMethods must be of type List");
Assert.notEmpty((List<?>) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED), "tokenEndpointAuthenticationMethods cannot be empty");
}
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI) != null) {
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI), "jwksUri must be a valid URL");
}
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED) != null) {
Assert.isInstanceOf(List.class, getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED), "scopes must be of type List");
Assert.notEmpty((List<?>) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED), "scopes cannot be empty");
}
Assert.notNull(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED), "responseTypes cannot be null");
Assert.isInstanceOf(List.class, getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED), "responseTypes must be of type List");
Assert.notEmpty((List<?>) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED), "responseTypes cannot be empty");
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED) != null) {
Assert.isInstanceOf(List.class, getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED), "grantTypes must be of type List");
Assert.notEmpty((List<?>) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED), "grantTypes cannot be empty");
}
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT) != null) {
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT), "tokenRevocationEndpoint must be a valid URL");
}
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED) != null) {
Assert.isInstanceOf(List.class, getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED), "tokenRevocationEndpointAuthenticationMethods must be of type List");
Assert.notEmpty((List<?>) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED), "tokenRevocationEndpointAuthenticationMethods cannot be empty");
}
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT) != null) {
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT), "tokenIntrospectionEndpoint must be a valid URL");
}
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED) != null) {
Assert.isInstanceOf(List.class, getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED), "tokenIntrospectionEndpointAuthenticationMethods must be of type List");
Assert.notEmpty((List<?>) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED), "tokenIntrospectionEndpointAuthenticationMethods cannot be empty");
}
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED) != null) {
Assert.isInstanceOf(List.class, getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED), "codeChallengeMethods must be of type List");
Assert.notEmpty((List<?>) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED), "codeChallengeMethods cannot be empty");
}
}
@SuppressWarnings("unchecked")
private void addClaimToClaimList(String name, String value) {
Assert.hasText(name, "name cannot be empty");
Assert.notNull(value, "value cannot be null");
getClaims().computeIfAbsent(name, k -> new LinkedList<String>());
((List<String>) getClaims().get(name)).add(value);
}
@SuppressWarnings("unchecked")
private void acceptClaimValues(String name, Consumer<List<String>> valuesConsumer) {
Assert.hasText(name, "name cannot be empty");
Assert.notNull(valuesConsumer, "valuesConsumer cannot be null");
getClaims().computeIfAbsent(name, k -> new LinkedList<String>());
List<String> values = (List<String>) getClaims().get(name);
valuesConsumer.accept(values);
}
protected static void validateURL(Object url, String errorMessage) {
if (URL.class.isAssignableFrom(url.getClass())) {
return;
}
try {
new URI(url.toString()).toURL();
} catch (Exception ex) {
throw new IllegalArgumentException(errorMessage, ex);
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* 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.
@@ -13,9 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.token;
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
package org.springframework.security.oauth2.core;
import java.time.Instant;

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core;
import java.util.Map;
import org.springframework.util.Assert;
/**
* A representation of an OAuth 2.0 Authorization Server Metadata response,
* which is returned from an OAuth 2.0 Authorization Server's Metadata Endpoint,
* and contains a set of claims about the Authorization Server's configuration.
* The claims are defined by the OAuth 2.0 Authorization Server Metadata
* specification (RFC 8414).
*
* @author Daniel Garnier-Moiroux
* @since 0.1.1
* @see AbstractOAuth2AuthorizationServerMetadata
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-3.2">3.2. Authorization Server Metadata Response</a>
*/
public final class OAuth2AuthorizationServerMetadata extends AbstractOAuth2AuthorizationServerMetadata {
private OAuth2AuthorizationServerMetadata(Map<String, Object> claims) {
super(claims);
}
/**
* Constructs a new {@link Builder} with empty claims.
*
* @return the {@link Builder}
*/
public static Builder builder() {
return new Builder();
}
/**
* Constructs a new {@link Builder} with the provided claims.
*
* @param claims the claims to initialize the builder
* @return the {@link Builder}
*/
public static Builder withClaims(Map<String, Object> claims) {
Assert.notEmpty(claims, "claims cannot be empty");
return new Builder()
.claims(c -> c.putAll(claims));
}
/**
* Helps configure an {@link OAuth2AuthorizationServerMetadata}.
*/
public static class Builder extends AbstractBuilder<OAuth2AuthorizationServerMetadata, Builder> {
private Builder() {
}
/**
* Validate the claims and build the {@link OAuth2AuthorizationServerMetadata}.
* <p>
* The following claims are REQUIRED:
* {@code issuer}, {@code authorization_endpoint}, {@code token_endpoint}
* and {@code response_types_supported}.
*
* @return the {@link OAuth2AuthorizationServerMetadata}
*/
@Override
public OAuth2AuthorizationServerMetadata build() {
validate();
return new OAuth2AuthorizationServerMetadata(getClaims());
}
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core;
import java.net.URL;
import java.util.List;
/**
* A {@link ClaimAccessor} for the "claims" an Authorization Server describes about its configuration,
* used in OAuth 2.0 Authorization Server Metadata and OpenID Connect Discovery 1.0.
*
* @author Daniel Garnier-Moiroux
* @since 0.1.1
* @see ClaimAccessor
* @see OAuth2AuthorizationServerMetadataClaimNames
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-2">2. Authorization Server Metadata</a>
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata">3. OpenID Provider Metadata</a>
*/
public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAccessor {
/**
* Returns the {@code URL} the Authorization Server asserts as its Issuer Identifier {@code (issuer)}.
*
* @return the {@code URL} the Authorization Server asserts as its Issuer Identifier
*/
default URL getIssuer() {
return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.ISSUER);
}
/**
* Returns the {@code URL} of the OAuth 2.0 Authorization Endpoint {@code (authorization_endpoint)}.
*
* @return the {@code URL} of the OAuth 2.0 Authorization Endpoint
*/
default URL getAuthorizationEndpoint() {
return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT);
}
/**
* Returns the {@code URL} of the OAuth 2.0 Token Endpoint {@code (token_endpoint)}.
*
* @return the {@code URL} of the OAuth 2.0 Token Endpoint
*/
default URL getTokenEndpoint() {
return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT);
}
/**
* Returns the client authentication methods supported by the OAuth 2.0 Token Endpoint {@code (token_endpoint_auth_methods_supported)}.
*
* @return the client authentication methods supported by the OAuth 2.0 Token Endpoint
*/
default List<String> getTokenEndpointAuthenticationMethods() {
return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED);
}
/**
* Returns the {@code URL} of the JSON Web Key Set {@code (jwks_uri)}.
*
* @return the {@code URL} of the JSON Web Key Set
*/
default URL getJwkSetUrl() {
return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI);
}
/**
* Returns the OAuth 2.0 {@code scope} values supported {@code (scopes_supported)}.
*
* @return the OAuth 2.0 {@code scope} values supported
*/
default List<String> getScopes() {
return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED);
}
/**
* Returns the OAuth 2.0 {@code response_type} values supported {@code (response_types_supported)}.
*
* @return the OAuth 2.0 {@code response_type} values supported
*/
default List<String> getResponseTypes() {
return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED);
}
/**
* Returns the OAuth 2.0 {@code grant_type} values supported {@code (grant_types_supported)}.
*
* @return the OAuth 2.0 {@code grant_type} values supported
*/
default List<String> getGrantTypes() {
return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED);
}
/**
* Returns the {@code URL} of the OAuth 2.0 Token Revocation Endpoint {@code (revocation_endpoint)}.
*
* @return the {@code URL} of the OAuth 2.0 Token Revocation Endpoint
*/
default URL getTokenRevocationEndpoint() {
return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT);
}
/**
* Returns the client authentication methods supported by the OAuth 2.0 Token Revocation Endpoint {@code (revocation_endpoint_auth_methods_supported)}.
*
* @return the client authentication methods supported by the OAuth 2.0 Token Revocation Endpoint
*/
default List<String> getTokenRevocationEndpointAuthenticationMethods() {
return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED);
}
/**
* Returns the {@code URL} of the OAuth 2.0 Token Introspection Endpoint {@code (introspection_endpoint)}.
*
* @return the {@code URL} of the OAuth 2.0 Token Introspection Endpoint
*/
default URL getTokenIntrospectionEndpoint() {
return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT);
}
/**
* Returns the client authentication methods supported by the OAuth 2.0 Token Introspection Endpoint {@code (introspection_endpoint_auth_methods_supported)}.
*
* @return the client authentication methods supported by the OAuth 2.0 Token Introspection Endpoint
*/
default List<String> getTokenIntrospectionEndpointAuthenticationMethods() {
return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED);
}
/**
* Returns the Proof Key for Code Exchange (PKCE) {@code code_challenge_method} values supported {@code (code_challenge_methods_supported)}.
*
* @return the {@code code_challenge_method} values supported
*/
default List<String> getCodeChallengeMethods() {
return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED);
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core;
/**
* The names of the "claims" an Authorization Server describes about its configuration,
* used in OAuth 2.0 Authorization Server Metadata and OpenID Connect Discovery 1.0.
*
* @author Daniel Garnier-Moiroux
* @since 0.1.1
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-2">2. Authorization Server Metadata</a>
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata">3. OpenID Provider Metadata</a>
*/
public interface OAuth2AuthorizationServerMetadataClaimNames {
/**
* {@code issuer} - the {@code URL} the Authorization Server asserts as its Issuer Identifier
*/
String ISSUER = "issuer";
/**
* {@code authorization_endpoint} - the {@code URL} of the OAuth 2.0 Authorization Endpoint
*/
String AUTHORIZATION_ENDPOINT = "authorization_endpoint";
/**
* {@code token_endpoint} - the {@code URL} of the OAuth 2.0 Token Endpoint
*/
String TOKEN_ENDPOINT = "token_endpoint";
/**
* {@code token_endpoint_auth_methods_supported} - the client authentication methods supported by the OAuth 2.0 Token Endpoint
*/
String TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED = "token_endpoint_auth_methods_supported";
/**
* {@code jwks_uri} - the {@code URL} of the JSON Web Key Set
*/
String JWKS_URI = "jwks_uri";
/**
* {@code scopes_supported} - the OAuth 2.0 {@code scope} values supported
*/
String SCOPES_SUPPORTED = "scopes_supported";
/**
* {@code response_types_supported} - the OAuth 2.0 {@code response_type} values supported
*/
String RESPONSE_TYPES_SUPPORTED = "response_types_supported";
/**
* {@code grant_types_supported} - the OAuth 2.0 {@code grant_type} values supported
*/
String GRANT_TYPES_SUPPORTED = "grant_types_supported";
/**
* {@code revocation_endpoint} - the {@code URL} of the OAuth 2.0 Token Revocation Endpoint
*/
String REVOCATION_ENDPOINT = "revocation_endpoint";
/**
* {@code revocation_endpoint_auth_methods_supported} - the client authentication methods supported by the OAuth 2.0 Token Revocation Endpoint
*/
String REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED = "revocation_endpoint_auth_methods_supported";
/**
* {@code introspection_endpoint} - the {@code URL} of the OAuth 2.0 Token Introspection Endpoint
*/
String INTROSPECTION_ENDPOINT = "introspection_endpoint";
/**
* {@code introspection_endpoint_auth_methods_supported} - the client authentication methods supported by the OAuth 2.0 Token Introspection Endpoint
*/
String INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED = "introspection_endpoint_auth_methods_supported";
/**
* {@code code_challenge_methods_supported} - the Proof Key for Code Exchange (PKCE) {@code code_challenge_method} values supported
*/
String CODE_CHALLENGE_METHODS_SUPPORTED = "code_challenge_methods_supported";
}

View File

@@ -1,40 +0,0 @@
/*
* Copyright 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.security.oauth2.core;
import java.time.Instant;
/**
* TODO
* This class is temporary and will be removed after upgrading to Spring Security 5.5.0 GA.
*
* @author Joe Grandja
* @since 0.0.3
* @see <a target="_blank" href="https://github.com/spring-projects/spring-security/pull/9146">Issue gh-9146</a>
*/
public class OAuth2RefreshToken2 extends OAuth2RefreshToken {
private final Instant expiresAt;
public OAuth2RefreshToken2(String tokenValue, Instant issuedAt, Instant expiresAt) {
super(tokenValue, issuedAt);
this.expiresAt = expiresAt;
}
@Override
public Instant getExpiresAt() {
return this.expiresAt;
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core;
import java.io.Serializable;
import org.springframework.util.Assert;
/**
* Standard data formats for OAuth 2.0 Tokens.
*
* @author Joe Grandja
* @since 0.2.3
*/
public final class OAuth2TokenFormat implements Serializable {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
/**
* Self-contained tokens use a protected, time-limited data structure that contains token metadata
* and claims of the user and/or client. JSON Web Token (JWT) is a widely used format.
*/
public static final OAuth2TokenFormat SELF_CONTAINED = new OAuth2TokenFormat("self-contained");
/**
* Reference (opaque) tokens are unique identifiers that serve as a reference
* to the token metadata and claims of the user and/or client, stored at the provider.
*/
public static final OAuth2TokenFormat REFERENCE = new OAuth2TokenFormat("reference");
private final String value;
/**
* Constructs an {@code OAuth2TokenFormat} using the provided value.
*
* @param value the value of the token format
*/
public OAuth2TokenFormat(String value) {
Assert.hasText(value, "value cannot be empty");
this.value = value;
}
/**
* Returns the value of the token format.
*
* @return the value of the token format
*/
public String getValue() {
return this.value;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || this.getClass() != obj.getClass()) {
return false;
}
OAuth2TokenFormat that = (OAuth2TokenFormat) obj;
return getValue().equals(that.getValue());
}
@Override
public int hashCode() {
return getValue().hashCode();
}
}

View File

@@ -0,0 +1,336 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core;
import java.io.Serializable;
import java.net.URI;
import java.net.URL;
import java.time.Instant;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.springframework.util.Assert;
/**
* A representation of the claims returned in an OAuth 2.0 Token Introspection Response.
*
* @author Gerardo Roza
* @author Joe Grandja
* @since 0.1.1
* @see OAuth2TokenIntrospectionClaimAccessor
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7662#section-2.2">Section 2.2 Introspection Response</a>
*/
public final class OAuth2TokenIntrospection implements OAuth2TokenIntrospectionClaimAccessor, Serializable {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
private final Map<String, Object> claims;
private OAuth2TokenIntrospection(Map<String, Object> claims) {
this.claims = Collections.unmodifiableMap(new LinkedHashMap<>(claims));
}
/**
* Returns the claims in the Token Introspection Response.
*
* @return a {@code Map} of the claims
*/
@Override
public Map<String, Object> getClaims() {
return this.claims;
}
/**
* Constructs a new {@link Builder} initialized with the {@link #isActive() active} claim to {@code false}.
*
* @return the {@link Builder}
*/
public static Builder builder() {
return builder(false);
}
/**
* Constructs a new {@link Builder} initialized with the provided {@link #isActive() active} claim.
*
* @param active {@code true} if the token is currently active, {@code false} otherwise
* @return the {@link Builder}
*/
public static Builder builder(boolean active) {
return new Builder(active);
}
/**
* Constructs a new {@link Builder} initialized with the provided claims.
*
* @param claims the claims to initialize the builder
* @return the {@link Builder}
*/
public static Builder withClaims(Map<String, Object> claims) {
Assert.notEmpty(claims, "claims cannot be empty");
return builder().claims(c -> c.putAll(claims));
}
/**
* A builder for {@link OAuth2TokenIntrospection}.
*/
public static class Builder {
private final Map<String, Object> claims = new LinkedHashMap<>();
private Builder(boolean active) {
active(active);
}
/**
* Sets the indicator of whether or not the presented token is currently active, REQUIRED.
*
* @param active {@code true} if the token is currently active, {@code false} otherwise
* @return the {@link Builder} for further configuration
*/
public Builder active(boolean active) {
return claim(OAuth2TokenIntrospectionClaimNames.ACTIVE, active);
}
/**
* Add the scope associated with this token, OPTIONAL.
*
* @param scope the scope associated with this token
* @return the {@link Builder} for further configuration
*/
public Builder scope(String scope) {
addClaimToClaimList(OAuth2TokenIntrospectionClaimNames.SCOPE, scope);
return this;
}
/**
* A {@code Consumer} of the scope(s) associated with this token,
* allowing the ability to add, replace, or remove, OPTIONAL.
*
* @param scopesConsumer a {@code Consumer} of the scope(s) associated with this token
* @return the {@link Builder} for further configuration
*/
public Builder scopes(Consumer<List<String>> scopesConsumer) {
acceptClaimValues(OAuth2TokenIntrospectionClaimNames.SCOPE, scopesConsumer);
return this;
}
/**
* Sets the client identifier for the OAuth 2.0 client that requested this token, OPTIONAL.
*
* @param clientId the client identifier for the OAuth 2.0 client that requested this token
* @return the {@link Builder} for further configuration
*/
public Builder clientId(String clientId) {
return claim(OAuth2TokenIntrospectionClaimNames.CLIENT_ID, clientId);
}
/**
* Sets the human-readable identifier for the resource owner who authorized this token, OPTIONAL.
*
* @param username the human-readable identifier for the resource owner who authorized this token
* @return the {@link Builder} for further configuration
*/
public Builder username(String username) {
return claim(OAuth2TokenIntrospectionClaimNames.USERNAME, username);
}
/**
* Sets the token type (e.g. bearer), OPTIONAL.
*
* @param tokenType the token type
* @return the {@link Builder} for further configuration
*/
public Builder tokenType(String tokenType) {
return claim(OAuth2TokenIntrospectionClaimNames.TOKEN_TYPE, tokenType);
}
/**
* Sets the time indicating when this token will expire, OPTIONAL.
*
* @param expiresAt the time indicating when this token will expire
* @return the {@link Builder} for further configuration
*/
public Builder expiresAt(Instant expiresAt) {
return claim(OAuth2TokenIntrospectionClaimNames.EXP, expiresAt);
}
/**
* Sets the time indicating when this token was originally issued, OPTIONAL.
*
* @param issuedAt the time indicating when this token was originally issued
* @return the {@link Builder} for further configuration
*/
public Builder issuedAt(Instant issuedAt) {
return claim(OAuth2TokenIntrospectionClaimNames.IAT, issuedAt);
}
/**
* Sets the time indicating when this token is not to be used before, OPTIONAL.
*
* @param notBefore the time indicating when this token is not to be used before
* @return the {@link Builder} for further configuration
*/
public Builder notBefore(Instant notBefore) {
return claim(OAuth2TokenIntrospectionClaimNames.NBF, notBefore);
}
/**
* Sets the subject of the token, usually a machine-readable identifier
* of the resource owner who authorized this token, OPTIONAL.
*
* @param subject the subject of the token
* @return the {@link Builder} for further configuration
*/
public Builder subject(String subject) {
return claim(OAuth2TokenIntrospectionClaimNames.SUB, subject);
}
/**
* Add the identifier representing the intended audience for this token, OPTIONAL.
*
* @param audience the identifier representing the intended audience for this token
* @return the {@link Builder} for further configuration
*/
public Builder audience(String audience) {
addClaimToClaimList(OAuth2TokenIntrospectionClaimNames.AUD, audience);
return this;
}
/**
* A {@code Consumer} of the intended audience(s) for this token,
* allowing the ability to add, replace, or remove, OPTIONAL.
*
* @param audiencesConsumer a {@code Consumer} of the intended audience(s) for this token
* @return the {@link Builder} for further configuration
*/
public Builder audiences(Consumer<List<String>> audiencesConsumer) {
acceptClaimValues(OAuth2TokenIntrospectionClaimNames.AUD, audiencesConsumer);
return this;
}
/**
* Sets the issuer of this token, OPTIONAL.
*
* @param issuer the issuer of this token
* @return the {@link Builder} for further configuration
*/
public Builder issuer(String issuer) {
return claim(OAuth2TokenIntrospectionClaimNames.ISS, issuer);
}
/**
* Sets the identifier for the token, OPTIONAL.
*
* @param jti the identifier for the token
* @return the {@link Builder} for further configuration
*/
public Builder id(String jti) {
return claim(OAuth2TokenIntrospectionClaimNames.JTI, jti);
}
/**
* Sets the claim.
*
* @param name the claim name
* @param value the claim value
* @return the {@link Builder} for further configuration
*/
public Builder claim(String name, Object value) {
Assert.hasText(name, "name cannot be empty");
Assert.notNull(value, "value cannot be null");
this.claims.put(name, value);
return this;
}
/**
* Provides access to every {@link #claim(String, Object)} declared so far with
* the possibility to add, replace, or remove.
*
* @param claimsConsumer a {@code Consumer} of the claims
* @return the {@link Builder} for further configurations
*/
public Builder claims(Consumer<Map<String, Object>> claimsConsumer) {
claimsConsumer.accept(this.claims);
return this;
}
/**
* Validate the claims and build the {@link OAuth2TokenIntrospection}.
* <p>
* The following claims are REQUIRED: {@code active}
*
* @return the {@link OAuth2TokenIntrospection}
*/
public OAuth2TokenIntrospection build() {
validate();
return new OAuth2TokenIntrospection(this.claims);
}
private void validate() {
Assert.notNull(this.claims.get(OAuth2TokenIntrospectionClaimNames.ACTIVE), "active cannot be null");
Assert.isInstanceOf(Boolean.class, this.claims.get(OAuth2TokenIntrospectionClaimNames.ACTIVE), "active must be of type boolean");
if (this.claims.containsKey(OAuth2TokenIntrospectionClaimNames.SCOPE)) {
Assert.isInstanceOf(List.class, this.claims.get(OAuth2TokenIntrospectionClaimNames.SCOPE), "scope must be of type List");
}
if (this.claims.containsKey(OAuth2TokenIntrospectionClaimNames.EXP)) {
Assert.isInstanceOf(Instant.class, this.claims.get(OAuth2TokenIntrospectionClaimNames.EXP), "exp must be of type Instant");
}
if (this.claims.containsKey(OAuth2TokenIntrospectionClaimNames.IAT)) {
Assert.isInstanceOf(Instant.class, this.claims.get(OAuth2TokenIntrospectionClaimNames.IAT), "iat must be of type Instant");
}
if (this.claims.containsKey(OAuth2TokenIntrospectionClaimNames.NBF)) {
Assert.isInstanceOf(Instant.class, this.claims.get(OAuth2TokenIntrospectionClaimNames.NBF), "nbf must be of type Instant");
}
if (this.claims.containsKey(OAuth2TokenIntrospectionClaimNames.AUD)) {
Assert.isInstanceOf(List.class, this.claims.get(OAuth2TokenIntrospectionClaimNames.AUD), "aud must be of type List");
}
if (this.claims.containsKey(OAuth2TokenIntrospectionClaimNames.ISS)) {
validateURL(this.claims.get(OAuth2TokenIntrospectionClaimNames.ISS), "iss must be a valid URL");
}
}
@SuppressWarnings("unchecked")
private void addClaimToClaimList(String name, String value) {
Assert.hasText(name, "name cannot be empty");
Assert.notNull(value, "value cannot be null");
this.claims.computeIfAbsent(name, k -> new LinkedList<String>());
((List<String>) this.claims.get(name)).add(value);
}
@SuppressWarnings("unchecked")
private void acceptClaimValues(String name, Consumer<List<String>> valuesConsumer) {
Assert.hasText(name, "name cannot be empty");
Assert.notNull(valuesConsumer, "valuesConsumer cannot be null");
this.claims.computeIfAbsent(name, k -> new LinkedList<String>());
List<String> values = (List<String>) this.claims.get(name);
valuesConsumer.accept(values);
}
private static void validateURL(Object url, String errorMessage) {
if (URL.class.isAssignableFrom(url.getClass())) {
return;
}
try {
new URI(url.toString()).toURL();
} catch (Exception ex) {
throw new IllegalArgumentException(errorMessage, ex);
}
}
}
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright 2002-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.security.oauth2.core;
import java.net.URL;
import java.time.Instant;
import java.util.List;
/*
* TODO
* This class is "mostly" a copy from Spring Security and should be removed after upgrading to Spring Security 5.6.0 GA.
* The major changes made between the Spring Security class and this one are:
* 1) Class renamed from `OAuth2IntrospectionClaimAccessor` to `OAuth2TokenIntrospectionClaimAccessor`
* 2) Moved from package `org.springframework.security.oauth2.server.resource.introspection` to `org.springframework.security.oauth2.core`
*
* gh-9647 Move and rename OAuth2IntrospectionClaimAccessor/Names
* https://github.com/spring-projects/spring-security/issues/9647
*/
/**
* A {@link ClaimAccessor} for the &quot;claims&quot; that may be contained in the
* Introspection Response.
*
* @author David Kovac
* @since 5.4
* @see ClaimAccessor
* @see OAuth2TokenIntrospectionClaimNames
* @see <a target="_blank" href=
* "https://tools.ietf.org/html/rfc7662#section-2.2">Introspection Response</a>
* @deprecated See <a target="_blank" href="https://github.com/spring-projects/spring-authorization-server/issues/597">gh-597</a>
*/
@Deprecated
public interface OAuth2TokenIntrospectionClaimAccessor extends ClaimAccessor {
/**
* Returns the indicator {@code (active)} whether or not the token is currently active
* @return the indicator whether or not the token is currently active
*/
default boolean isActive() {
return Boolean.TRUE.equals(getClaimAsBoolean(OAuth2TokenIntrospectionClaimNames.ACTIVE));
}
/**
* Returns the scopes {@code (scope)} associated with the token
* @return the scopes associated with the token
*/
default List<String> getScopes() {
return getClaimAsStringList(OAuth2TokenIntrospectionClaimNames.SCOPE);
}
/**
* Returns the client identifier {@code (client_id)} for the token
* @return the client identifier for the token
*/
default String getClientId() {
return getClaimAsString(OAuth2TokenIntrospectionClaimNames.CLIENT_ID);
}
/**
* Returns a human-readable identifier {@code (username)} for the resource owner that
* authorized the token
* @return a human-readable identifier for the resource owner that authorized the
* token
*/
default String getUsername() {
return getClaimAsString(OAuth2TokenIntrospectionClaimNames.USERNAME);
}
/**
* Returns the type of the token {@code (token_type)}, for example {@code bearer}.
* @return the type of the token, for example {@code bearer}.
*/
default String getTokenType() {
return getClaimAsString(OAuth2TokenIntrospectionClaimNames.TOKEN_TYPE);
}
/**
* Returns a timestamp {@code (exp)} indicating when the token expires
* @return a timestamp indicating when the token expires
*/
default Instant getExpiresAt() {
return getClaimAsInstant(OAuth2TokenIntrospectionClaimNames.EXP);
}
/**
* Returns a timestamp {@code (iat)} indicating when the token was issued
* @return a timestamp indicating when the token was issued
*/
default Instant getIssuedAt() {
return getClaimAsInstant(OAuth2TokenIntrospectionClaimNames.IAT);
}
/**
* Returns a timestamp {@code (nbf)} indicating when the token is not to be used
* before
* @return a timestamp indicating when the token is not to be used before
*/
default Instant getNotBefore() {
return getClaimAsInstant(OAuth2TokenIntrospectionClaimNames.NBF);
}
/**
* Returns usually a machine-readable identifier {@code (sub)} of the resource owner
* who authorized the token
* @return usually a machine-readable identifier of the resource owner who authorized
* the token
*/
default String getSubject() {
return getClaimAsString(OAuth2TokenIntrospectionClaimNames.SUB);
}
/**
* Returns the intended audience {@code (aud)} for the token
* @return the intended audience for the token
*/
default List<String> getAudience() {
return getClaimAsStringList(OAuth2TokenIntrospectionClaimNames.AUD);
}
/**
* Returns the issuer {@code (iss)} of the token
* @return the issuer of the token
*/
default URL getIssuer() {
return getClaimAsURL(OAuth2TokenIntrospectionClaimNames.ISS);
}
/**
* Returns the identifier {@code (jti)} for the token
* @return the identifier for the token
*/
default String getId() {
return getClaimAsString(OAuth2TokenIntrospectionClaimNames.JTI);
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core;
/*
* TODO
* This class is "mostly" a copy from Spring Security and should be removed after upgrading to Spring Security 5.6.0 GA.
* The major changes made between the Spring Security class and this one are:
* 1) Class renamed from `OAuth2IntrospectionClaimNames` to `OAuth2TokenIntrospectionClaimNames`
* 2) Moved from package `org.springframework.security.oauth2.server.resource.introspection` to `org.springframework.security.oauth2.core`
*
* gh-9647 Move and rename OAuth2IntrospectionClaimAccessor/Names
* https://github.com/spring-projects/spring-security/issues/9647
*/
/**
* The names of the &quot;Introspection Claims&quot; defined by an
* <a target="_blank" href="https://tools.ietf.org/html/rfc7662#section-2.2">Introspection
* Response</a>.
*
* @author Josh Cummings
* @since 5.2
* @deprecated See <a target="_blank" href="https://github.com/spring-projects/spring-authorization-server/issues/597">gh-597</a>
*/
@Deprecated
public interface OAuth2TokenIntrospectionClaimNames {
/**
* {@code active} - Indicator whether or not the token is currently active
*/
String ACTIVE = "active";
/**
* {@code scope} - The scopes for the token
*/
String SCOPE = "scope";
/**
* {@code client_id} - The Client identifier for the token
*/
String CLIENT_ID = "client_id";
/**
* {@code username} - A human-readable identifier for the resource owner that
* authorized the token
*/
String USERNAME = "username";
/**
* {@code token_type} - The type of the token, for example {@code bearer}.
*/
String TOKEN_TYPE = "token_type";
/**
* {@code exp} - A timestamp indicating when the token expires
*/
String EXP = "exp";
/**
* {@code iat} - A timestamp indicating when the token was issued
*/
String IAT = "iat";
/**
* {@code nbf} - A timestamp indicating when the token is not to be used before
*/
String NBF = "nbf";
/**
* {@code sub} - Usually a machine-readable identifier of the resource owner who
* authorized the token
*/
String SUB = "sub";
/**
* {@code aud} - The intended audience for the token
*/
String AUD = "aud";
/**
* {@code iss} - The issuer of the token
*/
String ISS = "iss";
/**
* {@code jti} - The identifier for the token
*/
String JTI = "jti";
}

View File

@@ -1,5 +1,5 @@
/*
* 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.
@@ -13,27 +13,40 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization;
import org.springframework.util.Assert;
package org.springframework.security.oauth2.core;
import java.io.Serializable;
import org.springframework.util.Assert;
/**
* Standard token types defined in the OAuth Token Type Hints Registry.
*
* @author Joe Grandja
* @since 0.0.1
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7009#section-4.1.2">4.1.2 OAuth Token Type Hints Registry</a>
*/
public final class TokenType implements Serializable {
public final class OAuth2TokenType implements Serializable {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
public static final TokenType ACCESS_TOKEN = new TokenType("access_token");
public static final TokenType REFRESH_TOKEN = new TokenType("refresh_token");
public static final TokenType AUTHORIZATION_CODE = new TokenType("authorization_code");
public static final OAuth2TokenType ACCESS_TOKEN = new OAuth2TokenType("access_token");
public static final OAuth2TokenType REFRESH_TOKEN = new OAuth2TokenType("refresh_token");
private final String value;
public TokenType(String value) {
/**
* Constructs an {@code OAuth2TokenType} using the provided value.
*
* @param value the value of the token type
*/
public OAuth2TokenType(String value) {
Assert.hasText(value, "value cannot be empty");
this.value = value;
}
/**
* Returns the value of the token type.
*
* @return the value of the token type
*/
public String getValue() {
return this.value;
}
@@ -46,12 +59,12 @@ public final class TokenType implements Serializable {
if (obj == null || this.getClass() != obj.getClass()) {
return false;
}
TokenType that = (TokenType) obj;
return this.getValue().equals(that.getValue());
OAuth2TokenType that = (OAuth2TokenType) obj;
return getValue().equals(that.getValue());
}
@Override
public int hashCode() {
return this.getValue().hashCode();
return getValue().hashCode();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization;
package org.springframework.security.oauth2.core;
/**
* Internal class used for serialization across Spring Security Authorization Server classes.
@@ -23,7 +23,7 @@ package org.springframework.security.oauth2.server.authorization;
*/
public final class Version {
private static final int MAJOR = 0;
private static final int MINOR = 0;
private static final int MINOR = 2;
private static final int PATCH = 3;
/**

View File

@@ -0,0 +1,155 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.authentication;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import org.springframework.lang.Nullable;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.context.Context;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* A context that holds an {@link Authentication} and (optionally) additional information.
*
* @author Joe Grandja
* @since 0.2.0
* @see Context
*/
public class OAuth2AuthenticationContext implements Context {
private final Map<Object, Object> context;
/**
* Constructs an {@code OAuth2AuthenticationContext} using the provided parameters.
*
* @param authentication the {@code Authentication}
* @param context a {@code Map} of additional context information
*/
public OAuth2AuthenticationContext(Authentication authentication, @Nullable Map<Object, Object> context) {
Assert.notNull(authentication, "authentication cannot be null");
Map<Object, Object> ctx = new HashMap<>();
if (!CollectionUtils.isEmpty(context)) {
ctx.putAll(context);
}
ctx.put(Authentication.class, authentication);
this.context = Collections.unmodifiableMap(ctx);
}
/**
* Constructs an {@code OAuth2AuthenticationContext} using the provided parameters.
*
* @param context a {@code Map} of context information, must contain the {@code Authentication}
* @since 0.2.1
*/
public OAuth2AuthenticationContext(Map<Object, Object> context) {
Assert.notEmpty(context, "context cannot be empty");
Assert.notNull(context.get(Authentication.class), "authentication cannot be null");
this.context = Collections.unmodifiableMap(new HashMap<>(context));
}
/**
* Returns the {@link Authentication} associated to the context.
*
* @param <T> the type of the {@code Authentication}
* @return the {@link Authentication}
*/
@SuppressWarnings("unchecked")
public <T extends Authentication> T getAuthentication() {
return (T) get(Authentication.class);
}
@SuppressWarnings("unchecked")
@Nullable
@Override
public <V> V get(Object key) {
return hasKey(key) ? (V) this.context.get(key) : null;
}
@Override
public boolean hasKey(Object key) {
Assert.notNull(key, "key cannot be null");
return this.context.containsKey(key);
}
/**
* A builder for subclasses of {@link OAuth2AuthenticationContext}.
*
* @param <T> the type of the authentication context
* @param <B> the type of the builder
* @since 0.2.1
*/
protected static abstract class AbstractBuilder<T extends OAuth2AuthenticationContext, B extends AbstractBuilder<T, B>> {
private final Map<Object, Object> context = new HashMap<>();
protected AbstractBuilder(Authentication authentication) {
Assert.notNull(authentication, "authentication cannot be null");
put(Authentication.class, authentication);
}
/**
* Associates an attribute.
*
* @param key the key for the attribute
* @param value the value of the attribute
* @return the {@link AbstractBuilder} for further configuration
*/
public B put(Object key, Object value) {
Assert.notNull(key, "key cannot be null");
Assert.notNull(value, "value cannot be null");
getContext().put(key, value);
return getThis();
}
/**
* A {@code Consumer} of the attributes {@code Map}
* allowing the ability to add, replace, or remove.
*
* @param contextConsumer a {@link Consumer} of the attributes {@code Map}
* @return the {@link AbstractBuilder} for further configuration
*/
public B context(Consumer<Map<Object, Object>> contextConsumer) {
contextConsumer.accept(getContext());
return getThis();
}
@SuppressWarnings("unchecked")
protected <V> V get(Object key) {
return (V) getContext().get(key);
}
protected Map<Object, Object> getContext() {
return this.context;
}
@SuppressWarnings("unchecked")
protected final B getThis() {
return (B) this;
}
/**
* Builds a new {@link OAuth2AuthenticationContext}.
*
* @return the {@link OAuth2AuthenticationContext}
*/
public abstract T build();
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.authentication;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
/**
* Implementations of this interface are responsible for validating the attribute(s)
* of the {@link Authentication} associated to the {@link OAuth2AuthenticationContext}.
*
* @author Joe Grandja
* @since 0.2.0
* @see OAuth2AuthenticationContext
*/
@FunctionalInterface
public interface OAuth2AuthenticationValidator {
/**
* Validate the attribute(s) of the {@link Authentication}.
*
* @param authenticationContext the authentication context
* @throws OAuth2AuthenticationException if the attribute(s) of the {@code Authentication} is invalid
*/
void validate(OAuth2AuthenticationContext authenticationContext) throws OAuth2AuthenticationException;
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.context;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* A facility for holding information associated to a specific context.
*
* @author Joe Grandja
* @since 0.1.0
*/
public interface Context {
/**
* Returns the value of the attribute associated to the key.
*
* @param key the key for the attribute
* @param <V> the type of the value for the attribute
* @return the value of the attribute associated to the key, or {@code null} if not available
*/
@Nullable
<V> V get(Object key);
/**
* Returns the value of the attribute associated to the key.
*
* @param key the key for the attribute
* @param <V> the type of the value for the attribute
* @return the value of the attribute associated to the key, or {@code null} if not available or not of the specified type
*/
@Nullable
default <V> V get(Class<V> key) {
Assert.notNull(key, "key cannot be null");
V value = get((Object) key);
return key.isInstance(value) ? value : null;
}
/**
* Returns {@code true} if an attribute associated to the key exists, {@code false} otherwise.
*
* @param key the key for the attribute
* @return {@code true} if an attribute associated to the key exists, {@code false} otherwise
*/
boolean hasKey(Object key);
}

View File

@@ -0,0 +1,164 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.http.converter;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.security.oauth2.core.OAuth2AuthorizationServerMetadata;
import org.springframework.security.oauth2.core.OAuth2AuthorizationServerMetadataClaimNames;
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
import org.springframework.util.Assert;
/**
* A {@link HttpMessageConverter} for an {@link OAuth2AuthorizationServerMetadata OAuth 2.0 Authorization Server Metadata Response}.
*
* @author Daniel Garnier-Moiroux
* @since 0.1.1
* @see AbstractHttpMessageConverter
* @see OAuth2AuthorizationServerMetadata
*/
public class OAuth2AuthorizationServerMetadataHttpMessageConverter
extends AbstractHttpMessageConverter<OAuth2AuthorizationServerMetadata> {
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP =
new ParameterizedTypeReference<Map<String, Object>>() {};
private final GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter();
private Converter<Map<String, Object>, OAuth2AuthorizationServerMetadata> authorizationServerMetadataConverter = new OAuth2AuthorizationServerMetadataConverter();
private Converter<OAuth2AuthorizationServerMetadata, Map<String, Object>> authorizationServerMetadataParametersConverter = OAuth2AuthorizationServerMetadata::getClaims;
public OAuth2AuthorizationServerMetadataHttpMessageConverter() {
super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
}
@Override
protected boolean supports(Class<?> clazz) {
return OAuth2AuthorizationServerMetadata.class.isAssignableFrom(clazz);
}
@Override
@SuppressWarnings("unchecked")
protected OAuth2AuthorizationServerMetadata readInternal(Class<? extends OAuth2AuthorizationServerMetadata> clazz, HttpInputMessage inputMessage)
throws HttpMessageNotReadableException {
try {
Map<String, Object> authorizationServerMetadataParameters =
(Map<String, Object>) this.jsonMessageConverter.read(STRING_OBJECT_MAP.getType(), null, inputMessage);
return this.authorizationServerMetadataConverter.convert(authorizationServerMetadataParameters);
} catch (Exception ex) {
throw new HttpMessageNotReadableException(
"An error occurred reading the OAuth 2.0 Authorization Server Metadata: " + ex.getMessage(), ex, inputMessage);
}
}
@Override
protected void writeInternal(OAuth2AuthorizationServerMetadata authorizationServerMetadata, HttpOutputMessage outputMessage)
throws HttpMessageNotWritableException {
try {
Map<String, Object> authorizationServerMetadataResponseParameters =
this.authorizationServerMetadataParametersConverter.convert(authorizationServerMetadata);
this.jsonMessageConverter.write(
authorizationServerMetadataResponseParameters,
STRING_OBJECT_MAP.getType(),
MediaType.APPLICATION_JSON,
outputMessage
);
} catch (Exception ex) {
throw new HttpMessageNotWritableException(
"An error occurred writing the OAuth 2.0 Authorization Server Metadata: " + ex.getMessage(), ex);
}
}
/**
* Sets the {@link Converter} used for converting the OAuth 2.0 Authorization Server Metadata
* parameters to an {@link OAuth2AuthorizationServerMetadata}.
*
* @param authorizationServerMetadataConverter the {@link Converter} used for converting to
* an {@link OAuth2AuthorizationServerMetadata}.
*/
public final void setAuthorizationServerMetadataConverter(Converter<Map<String, Object>, OAuth2AuthorizationServerMetadata> authorizationServerMetadataConverter) {
Assert.notNull(authorizationServerMetadataConverter, "authorizationServerMetadataConverter cannot be null");
this.authorizationServerMetadataConverter = authorizationServerMetadataConverter;
}
/**
* Sets the {@link Converter} used for converting the {@link OAuth2AuthorizationServerMetadata} to a
* {@code Map} representation of the OAuth 2.0 Authorization Server Metadata.
*
* @param authorizationServerMetadataParametersConverter the {@link Converter} used for converting to a
* {@code Map} representation of the OAuth 2.0 Authorization Server Metadata.
*/
public final void setAuthorizationServerMetadataParametersConverter(Converter<OAuth2AuthorizationServerMetadata, Map<String, Object>> authorizationServerMetadataParametersConverter) {
Assert.notNull(authorizationServerMetadataParametersConverter, "authorizationServerMetadataParametersConverter cannot be null");
this.authorizationServerMetadataParametersConverter = authorizationServerMetadataParametersConverter;
}
private static final class OAuth2AuthorizationServerMetadataConverter implements Converter<Map<String, Object>, OAuth2AuthorizationServerMetadata> {
private static final ClaimConversionService CLAIM_CONVERSION_SERVICE = ClaimConversionService.getSharedInstance();
private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class);
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
private static final TypeDescriptor URL_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(URL.class);
private final ClaimTypeConverter claimTypeConverter;
private OAuth2AuthorizationServerMetadataConverter() {
Converter<Object, ?> collectionStringConverter = getConverter(
TypeDescriptor.collection(Collection.class, STRING_TYPE_DESCRIPTOR));
Converter<Object, ?> urlConverter = getConverter(URL_TYPE_DESCRIPTOR);
Map<String, Converter<Object, ?>> claimConverters = new HashMap<>();
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, urlConverter);
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, urlConverter);
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, urlConverter);
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, collectionStringConverter);
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI, urlConverter);
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, collectionStringConverter);
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, collectionStringConverter);
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, collectionStringConverter);
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT, urlConverter);
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED, collectionStringConverter);
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT, urlConverter);
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED, collectionStringConverter);
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED, collectionStringConverter);
this.claimTypeConverter = new ClaimTypeConverter(claimConverters);
}
@Override
public OAuth2AuthorizationServerMetadata convert(Map<String, Object> source) {
Map<String, Object> parsedClaims = this.claimTypeConverter.convert(source);
return OAuth2AuthorizationServerMetadata.withClaims(parsedClaims).build();
}
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
return (source) -> CLAIM_CONVERSION_SERVICE.convert(source, OBJECT_TYPE_DESCRIPTOR, targetDescriptor);
}
}
}

View File

@@ -0,0 +1,203 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.http.converter;
import java.net.URL;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.security.oauth2.core.OAuth2TokenIntrospection;
import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames;
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* A {@link HttpMessageConverter} for an {@link OAuth2TokenIntrospection OAuth 2.0 Token Introspection Response}.
*
* @author Gerardo Roza
* @author Joe Grandja
* @since 0.1.1
* @see AbstractHttpMessageConverter
* @see OAuth2TokenIntrospection
*/
public class OAuth2TokenIntrospectionHttpMessageConverter extends AbstractHttpMessageConverter<OAuth2TokenIntrospection> {
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP = new ParameterizedTypeReference<Map<String, Object>>() {
};
private final GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter();
private Converter<Map<String, Object>, OAuth2TokenIntrospection> tokenIntrospectionConverter = new MapOAuth2TokenIntrospectionConverter();
private Converter<OAuth2TokenIntrospection, Map<String, Object>> tokenIntrospectionParametersConverter = new OAuth2TokenIntrospectionMapConverter();
public OAuth2TokenIntrospectionHttpMessageConverter() {
super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
}
@Override
protected boolean supports(Class<?> clazz) {
return OAuth2TokenIntrospection.class.isAssignableFrom(clazz);
}
@Override
@SuppressWarnings("unchecked")
protected OAuth2TokenIntrospection readInternal(Class<? extends OAuth2TokenIntrospection> clazz, HttpInputMessage inputMessage)
throws HttpMessageNotReadableException {
try {
Map<String, Object> tokenIntrospectionParameters = (Map<String, Object>) this.jsonMessageConverter
.read(STRING_OBJECT_MAP.getType(), null, inputMessage);
return this.tokenIntrospectionConverter.convert(tokenIntrospectionParameters);
} catch (Exception ex) {
throw new HttpMessageNotReadableException(
"An error occurred reading the Token Introspection Response: " + ex.getMessage(), ex, inputMessage);
}
}
@Override
protected void writeInternal(OAuth2TokenIntrospection tokenIntrospection, HttpOutputMessage outputMessage)
throws HttpMessageNotWritableException {
try {
Map<String, Object> tokenIntrospectionResponseParameters = this.tokenIntrospectionParametersConverter
.convert(tokenIntrospection);
this.jsonMessageConverter.write(tokenIntrospectionResponseParameters, STRING_OBJECT_MAP.getType(),
MediaType.APPLICATION_JSON, outputMessage);
} catch (Exception ex) {
throw new HttpMessageNotWritableException(
"An error occurred writing the Token Introspection Response: " + ex.getMessage(), ex);
}
}
/**
* Sets the {@link Converter} used for converting the Token Introspection Response parameters to an {@link OAuth2TokenIntrospection}.
*
* @param tokenIntrospectionConverter the {@link Converter} used for converting to an {@link OAuth2TokenIntrospection}
*/
public final void setTokenIntrospectionConverter(
Converter<Map<String, Object>, OAuth2TokenIntrospection> tokenIntrospectionConverter) {
Assert.notNull(tokenIntrospectionConverter, "tokenIntrospectionConverter cannot be null");
this.tokenIntrospectionConverter = tokenIntrospectionConverter;
}
/**
* Sets the {@link Converter} used for converting an {@link OAuth2TokenIntrospection}
* to a {@code Map} representation of the Token Introspection Response parameters.
*
* @param tokenIntrospectionParametersConverter the {@link Converter} used for converting to a
* {@code Map} representation of the Token Introspection Response parameters
*/
public final void setTokenIntrospectionParametersConverter(
Converter<OAuth2TokenIntrospection, Map<String, Object>> tokenIntrospectionParametersConverter) {
Assert.notNull(tokenIntrospectionParametersConverter, "tokenIntrospectionParametersConverter cannot be null");
this.tokenIntrospectionParametersConverter = tokenIntrospectionParametersConverter;
}
private static final class MapOAuth2TokenIntrospectionConverter
implements Converter<Map<String, Object>, OAuth2TokenIntrospection> {
private static final ClaimConversionService CLAIM_CONVERSION_SERVICE = ClaimConversionService.getSharedInstance();
private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class);
private static final TypeDescriptor BOOLEAN_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Boolean.class);
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
private static final TypeDescriptor INSTANT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Instant.class);
private static final TypeDescriptor URL_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(URL.class);
private final ClaimTypeConverter claimTypeConverter;
private MapOAuth2TokenIntrospectionConverter() {
Converter<Object, ?> booleanConverter = getConverter(BOOLEAN_TYPE_DESCRIPTOR);
Converter<Object, ?> stringConverter = getConverter(STRING_TYPE_DESCRIPTOR);
Converter<Object, ?> instantConverter = getConverter(INSTANT_TYPE_DESCRIPTOR);
Converter<Object, ?> collectionStringConverter = getConverter(
TypeDescriptor.collection(Collection.class, STRING_TYPE_DESCRIPTOR));
Converter<Object, ?> urlConverter = getConverter(URL_TYPE_DESCRIPTOR);
Map<String, Converter<Object, ?>> claimConverters = new HashMap<>();
claimConverters.put(OAuth2TokenIntrospectionClaimNames.ACTIVE, booleanConverter);
claimConverters.put(OAuth2TokenIntrospectionClaimNames.SCOPE, MapOAuth2TokenIntrospectionConverter::convertScope);
claimConverters.put(OAuth2TokenIntrospectionClaimNames.CLIENT_ID, stringConverter);
claimConverters.put(OAuth2TokenIntrospectionClaimNames.USERNAME, stringConverter);
claimConverters.put(OAuth2TokenIntrospectionClaimNames.TOKEN_TYPE, stringConverter);
claimConverters.put(OAuth2TokenIntrospectionClaimNames.EXP, instantConverter);
claimConverters.put(OAuth2TokenIntrospectionClaimNames.IAT, instantConverter);
claimConverters.put(OAuth2TokenIntrospectionClaimNames.NBF, instantConverter);
claimConverters.put(OAuth2TokenIntrospectionClaimNames.SUB, stringConverter);
claimConverters.put(OAuth2TokenIntrospectionClaimNames.AUD, collectionStringConverter);
claimConverters.put(OAuth2TokenIntrospectionClaimNames.ISS, urlConverter);
claimConverters.put(OAuth2TokenIntrospectionClaimNames.JTI, stringConverter);
this.claimTypeConverter = new ClaimTypeConverter(claimConverters);
}
@Override
public OAuth2TokenIntrospection convert(Map<String, Object> source) {
Map<String, Object> parsedClaims = this.claimTypeConverter.convert(source);
return OAuth2TokenIntrospection.withClaims(parsedClaims).build();
}
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
return (source) -> CLAIM_CONVERSION_SERVICE.convert(source, OBJECT_TYPE_DESCRIPTOR, targetDescriptor);
}
private static List<String> convertScope(Object scope) {
if (scope == null) {
return Collections.emptyList();
}
return Arrays.asList(StringUtils.delimitedListToStringArray(scope.toString(), " "));
}
}
private static final class OAuth2TokenIntrospectionMapConverter
implements Converter<OAuth2TokenIntrospection, Map<String, Object>> {
@Override
public Map<String, Object> convert(OAuth2TokenIntrospection source) {
Map<String, Object> responseClaims = new LinkedHashMap<>(source.getClaims());
if (!CollectionUtils.isEmpty(source.getScopes())) {
responseClaims.put(OAuth2TokenIntrospectionClaimNames.SCOPE, StringUtils.collectionToDelimitedString(source.getScopes(), " "));
}
if (source.getExpiresAt() != null) {
responseClaims.put(OAuth2TokenIntrospectionClaimNames.EXP, source.getExpiresAt().getEpochSecond());
}
if (source.getIssuedAt() != null) {
responseClaims.put(OAuth2TokenIntrospectionClaimNames.IAT, source.getIssuedAt().getEpochSecond());
}
if (source.getNotBefore() != null) {
responseClaims.put(OAuth2TokenIntrospectionClaimNames.NBF, source.getNotBefore().getEpochSecond());
}
return responseClaims;
}
}
}

View File

@@ -0,0 +1,183 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.oidc;
import java.net.URL;
import java.time.Instant;
import java.util.List;
import org.springframework.security.oauth2.core.ClaimAccessor;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
/**
* A {@link ClaimAccessor} for the "claims" that are contained
* in the OpenID Client Registration Request and Response.
*
* @author Ovidiu Popa
* @author Joe Grandja
* @since 0.1.1
* @see ClaimAccessor
* @see OidcClientMetadataClaimNames
* @see OidcClientRegistration
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata">2. Client Metadata</a>
*/
public interface OidcClientMetadataClaimAccessor extends ClaimAccessor {
/**
* Returns the Client Identifier {@code (client_id)}.
*
* @return the Client Identifier
*/
default String getClientId() {
return getClaimAsString(OidcClientMetadataClaimNames.CLIENT_ID);
}
/**
* Returns the time at which the Client Identifier was issued {@code (client_id_issued_at)}.
*
* @return the time at which the Client Identifier was issued
*/
default Instant getClientIdIssuedAt() {
return getClaimAsInstant(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT);
}
/**
* Returns the Client Secret {@code (client_secret)}.
*
* @return the Client Secret
*/
default String getClientSecret() {
return getClaimAsString(OidcClientMetadataClaimNames.CLIENT_SECRET);
}
/**
* Returns the time at which the {@code client_secret} will expire {@code (client_secret_expires_at)}.
*
* @return the time at which the {@code client_secret} will expire
*/
default Instant getClientSecretExpiresAt() {
return getClaimAsInstant(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT);
}
/**
* Returns the name of the Client to be presented to the End-User {@code (client_name)}.
*
* @return the name of the Client to be presented to the End-User
*/
default String getClientName() {
return getClaimAsString(OidcClientMetadataClaimNames.CLIENT_NAME);
}
/**
* Returns the redirection {@code URI} values used by the Client {@code (redirect_uris)}.
*
* @return the redirection {@code URI} values used by the Client
*/
default List<String> getRedirectUris() {
return getClaimAsStringList(OidcClientMetadataClaimNames.REDIRECT_URIS);
}
/**
* Returns the authentication method used by the Client for the Token Endpoint {@code (token_endpoint_auth_method)}.
*
* @return the authentication method used by the Client for the Token Endpoint
*/
default String getTokenEndpointAuthenticationMethod() {
return getClaimAsString(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD);
}
/**
* Returns the {@link JwsAlgorithm JWS} algorithm that must be used for signing the {@link Jwt JWT} used to authenticate
* the Client at the Token Endpoint for the {@link ClientAuthenticationMethod#PRIVATE_KEY_JWT private_key_jwt} and
* {@link ClientAuthenticationMethod#CLIENT_SECRET_JWT client_secret_jwt} authentication methods {@code (token_endpoint_auth_signing_alg)}.
*
* @return the {@link JwsAlgorithm JWS} algorithm that must be used for signing the {@link Jwt JWT} used to authenticate the Client at the Token Endpoint
* @since 0.2.2
*/
default String getTokenEndpointAuthenticationSigningAlgorithm() {
return getClaimAsString(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_SIGNING_ALG);
}
/**
* Returns the OAuth 2.0 {@code grant_type} values that the Client will restrict itself to using {@code (grant_types)}.
*
* @return the OAuth 2.0 {@code grant_type} values that the Client will restrict itself to using
*/
default List<String> getGrantTypes() {
return getClaimAsStringList(OidcClientMetadataClaimNames.GRANT_TYPES);
}
/**
* Returns the OAuth 2.0 {@code response_type} values that the Client will restrict itself to using {@code (response_types)}.
*
* @return the OAuth 2.0 {@code response_type} values that the Client will restrict itself to using
*/
default List<String> getResponseTypes() {
return getClaimAsStringList(OidcClientMetadataClaimNames.RESPONSE_TYPES);
}
/**
* Returns the OAuth 2.0 {@code scope} values that the Client will restrict itself to using {@code (scope)}.
*
* @return the OAuth 2.0 {@code scope} values that the Client will restrict itself to using
*/
default List<String> getScopes() {
return getClaimAsStringList(OidcClientMetadataClaimNames.SCOPE);
}
/**
* Returns the {@code URL} for the Client's JSON Web Key Set {@code (jwks_uri)}.
*
* @return the {@code URL} for the Client's JSON Web Key Set {@code (jwks_uri)}
* @since 0.2.2
*/
default URL getJwkSetUrl() {
return getClaimAsURL(OidcClientMetadataClaimNames.JWKS_URI);
}
/**
* Returns the {@link SignatureAlgorithm JWS} algorithm required for signing the {@link OidcIdToken ID Token} issued to the Client {@code (id_token_signed_response_alg)}.
*
* @return the {@link SignatureAlgorithm JWS} algorithm required for signing the {@link OidcIdToken ID Token} issued to the Client
*/
default String getIdTokenSignedResponseAlgorithm() {
return getClaimAsString(OidcClientMetadataClaimNames.ID_TOKEN_SIGNED_RESPONSE_ALG);
}
/**
* Returns the Registration Access Token that can be used at the Client Configuration Endpoint.
*
* @return the Registration Access Token that can be used at the Client Configuration Endpoint
* @since 0.2.1
*/
default String getRegistrationAccessToken() {
return getClaimAsString(OidcClientMetadataClaimNames.REGISTRATION_ACCESS_TOKEN);
}
/**
* Returns the {@code URL} of the Client Configuration Endpoint where the Registration Access Token can be used.
*
* @return the {@code URL} of the Client Configuration Endpoint where the Registration Access Token can be used
* @since 0.2.1
*/
default URL getRegistrationClientUrl() {
return getClaimAsURL(OidcClientMetadataClaimNames.REGISTRATION_CLIENT_URI);
}
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.oidc;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
/**
* The names of the "claims" defined by OpenID Connect Dynamic Client Registration 1.0
* that are contained in the OpenID Client Registration Request and Response.
*
* @author Ovidiu Popa
* @author Joe Grandja
* @since 0.1.1
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata">2. Client Metadata</a>
*/
public interface OidcClientMetadataClaimNames {
/**
* {@code client_id} - the Client Identifier
*/
String CLIENT_ID = "client_id";
/**
* {@code client_id_issued_at} - the time at which the Client Identifier was issued
*/
String CLIENT_ID_ISSUED_AT = "client_id_issued_at";
/**
* {@code client_secret} - the Client Secret
*/
String CLIENT_SECRET = "client_secret";
/**
* {@code client_secret_expires_at} - the time at which the {@code client_secret} will expire or 0 if it will not expire
*/
String CLIENT_SECRET_EXPIRES_AT = "client_secret_expires_at";
/**
* {@code client_name} - the name of the Client to be presented to the End-User
*/
String CLIENT_NAME = "client_name";
/**
* {@code redirect_uris} - the redirection {@code URI} values used by the Client
*/
String REDIRECT_URIS = "redirect_uris";
/**
* {@code token_endpoint_auth_method} - the authentication method used by the Client for the Token Endpoint
*/
String TOKEN_ENDPOINT_AUTH_METHOD = "token_endpoint_auth_method";
/**
* {@code token_endpoint_auth_signing_alg} - the {@link JwsAlgorithm JWS} algorithm that must be used for signing the {@link Jwt JWT}
* used to authenticate the Client at the Token Endpoint for the {@link ClientAuthenticationMethod#PRIVATE_KEY_JWT private_key_jwt} and
* {@link ClientAuthenticationMethod#CLIENT_SECRET_JWT client_secret_jwt} authentication methods
* @since 0.2.2
*/
String TOKEN_ENDPOINT_AUTH_SIGNING_ALG = "token_endpoint_auth_signing_alg";
/**
* {@code grant_types} - the OAuth 2.0 {@code grant_type} values that the Client will restrict itself to using
*/
String GRANT_TYPES = "grant_types";
/**
* {@code response_types} - the OAuth 2.0 {@code response_type} values that the Client will restrict itself to using
*/
String RESPONSE_TYPES = "response_types";
/**
* {@code scope} - a space-separated list of OAuth 2.0 {@code scope} values that the Client will restrict itself to using
*/
String SCOPE = "scope";
/**
* {@code jwks_uri} - the {@code URL} for the Client's JSON Web Key Set
* @since 0.2.2
*/
String JWKS_URI = "jwks_uri";
/**
* {@code id_token_signed_response_alg} - the {@link JwsAlgorithm JWS} algorithm required for signing the {@link OidcIdToken ID Token} issued to the Client
*/
String ID_TOKEN_SIGNED_RESPONSE_ALG = "id_token_signed_response_alg";
/**
* {@code registration_access_token} - the Registration Access Token that can be used at the Client Configuration Endpoint
* @since 0.2.1
*/
String REGISTRATION_ACCESS_TOKEN = "registration_access_token";
/**
* {@code registration_client_uri} - the {@code URL} of the Client Configuration Endpoint where the Registration Access Token can be used
* @since 0.2.1
*/
String REGISTRATION_CLIENT_URI = "registration_client_uri";
}

View File

@@ -0,0 +1,408 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.oidc;
import java.io.Serializable;
import java.net.URI;
import java.net.URL;
import java.time.Instant;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.Version;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.util.Assert;
/**
* A representation of an OpenID Client Registration Request and Response,
* which is sent to and returned from the Client Registration Endpoint,
* and contains a set of claims about the Client's Registration information.
* The claims are defined by the OpenID Connect Dynamic Client Registration 1.0 specification.
*
* @author Ovidiu Popa
* @author Joe Grandja
* @since 0.1.1
* @see OidcClientMetadataClaimAccessor
* @see <a href="https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationRequest">3.1. Client Registration Request</a>
* @see <a href="https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse">3.2. Client Registration Response</a>
*/
public final class OidcClientRegistration implements OidcClientMetadataClaimAccessor, Serializable {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
private final Map<String, Object> claims;
private OidcClientRegistration(Map<String, Object> claims) {
Assert.notEmpty(claims, "claims cannot be empty");
this.claims = Collections.unmodifiableMap(new LinkedHashMap<>(claims));
}
/**
* Returns the metadata as claims.
*
* @return a {@code Map} of the metadata as claims
*/
@Override
public Map<String, Object> getClaims() {
return this.claims;
}
/**
* Constructs a new {@link Builder} with empty claims.
*
* @return the {@link Builder}
*/
public static Builder builder() {
return new Builder();
}
/**
* Constructs a new {@link Builder} with the provided claims.
*
* @param claims the claims to initialize the builder
*/
public static Builder withClaims(Map<String, Object> claims) {
Assert.notEmpty(claims, "claims cannot be empty");
return new Builder()
.claims(c -> c.putAll(claims));
}
/**
* Helps configure an {@link OidcClientRegistration}.
*/
public static class Builder {
private final Map<String, Object> claims = new LinkedHashMap<>();
private Builder() {
}
/**
* Sets the Client Identifier, REQUIRED.
*
* @param clientId the Client Identifier
* @return the {@link Builder} for further configuration
*/
public Builder clientId(String clientId) {
return claim(OidcClientMetadataClaimNames.CLIENT_ID, clientId);
}
/**
* Sets the time at which the Client Identifier was issued, OPTIONAL.
*
* @param clientIdIssuedAt the time at which the Client Identifier was issued
* @return the {@link Builder} for further configuration
*/
public Builder clientIdIssuedAt(Instant clientIdIssuedAt) {
return claim(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT, clientIdIssuedAt);
}
/**
* Sets the Client Secret, OPTIONAL.
*
* @param clientSecret the Client Secret
* @return the {@link Builder} for further configuration
*/
public Builder clientSecret(String clientSecret) {
return claim(OidcClientMetadataClaimNames.CLIENT_SECRET, clientSecret);
}
/**
* Sets the time at which the {@code client_secret} will expire or {@code null} if it will not expire, REQUIRED if {@code client_secret} was issued.
*
* @param clientSecretExpiresAt the time at which the {@code client_secret} will expire or {@code null} if it will not expire
* @return the {@link Builder} for further configuration
*/
public Builder clientSecretExpiresAt(Instant clientSecretExpiresAt) {
return claim(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT, clientSecretExpiresAt);
}
/**
* Sets the name of the Client to be presented to the End-User, OPTIONAL.
*
* @param clientName the name of the Client to be presented to the End-User
* @return the {@link Builder} for further configuration
*/
public Builder clientName(String clientName) {
return claim(OidcClientMetadataClaimNames.CLIENT_NAME, clientName);
}
/**
* Add the redirection {@code URI} used by the Client, REQUIRED.
*
* @param redirectUri the redirection {@code URI} used by the Client
* @return the {@link Builder} for further configuration
*/
public Builder redirectUri(String redirectUri) {
addClaimToClaimList(OidcClientMetadataClaimNames.REDIRECT_URIS, redirectUri);
return this;
}
/**
* A {@code Consumer} of the redirection {@code URI} values used by the Client,
* allowing the ability to add, replace, or remove, REQUIRED.
*
* @param redirectUrisConsumer a {@code Consumer} of the redirection {@code URI} values used by the Client
* @return the {@link Builder} for further configuration
*/
public Builder redirectUris(Consumer<List<String>> redirectUrisConsumer) {
acceptClaimValues(OidcClientMetadataClaimNames.REDIRECT_URIS, redirectUrisConsumer);
return this;
}
/**
* Sets the authentication method used by the Client for the Token Endpoint, OPTIONAL.
*
* @param tokenEndpointAuthenticationMethod the authentication method used by the Client for the Token Endpoint
* @return the {@link Builder} for further configuration
*/
public Builder tokenEndpointAuthenticationMethod(String tokenEndpointAuthenticationMethod) {
return claim(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD, tokenEndpointAuthenticationMethod);
}
/**
* Sets the {@link JwsAlgorithm JWS} algorithm that must be used for signing the {@link Jwt JWT} used to authenticate
* the Client at the Token Endpoint for the {@link ClientAuthenticationMethod#PRIVATE_KEY_JWT private_key_jwt} and
* {@link ClientAuthenticationMethod#CLIENT_SECRET_JWT client_secret_jwt} authentication methods, OPTIONAL.
* @param authenticationSigningAlgorithm the {@link JwsAlgorithm JWS} algorithm that must be used for signing the {@link Jwt JWT}
* used to authenticate the Client at the Token Endpoint
* @return the {@link Builder} for further configuration
* @since 0.2.2
*/
public Builder tokenEndpointAuthenticationSigningAlgorithm(String authenticationSigningAlgorithm) {
return claim(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, authenticationSigningAlgorithm);
}
/**
* Add the OAuth 2.0 {@code grant_type} that the Client will restrict itself to using, OPTIONAL.
*
* @param grantType the OAuth 2.0 {@code grant_type} that the Client will restrict itself to using
* @return the {@link Builder} for further configuration
*/
public Builder grantType(String grantType) {
addClaimToClaimList(OidcClientMetadataClaimNames.GRANT_TYPES, grantType);
return this;
}
/**
* A {@code Consumer} of the OAuth 2.0 {@code grant_type} values that the Client will restrict itself to using,
* allowing the ability to add, replace, or remove, OPTIONAL.
*
* @param grantTypesConsumer a {@code Consumer} of the OAuth 2.0 {@code grant_type} values that the Client will restrict itself to using
* @return the {@link Builder} for further configuration
*/
public Builder grantTypes(Consumer<List<String>> grantTypesConsumer) {
acceptClaimValues(OidcClientMetadataClaimNames.GRANT_TYPES, grantTypesConsumer);
return this;
}
/**
* Add the OAuth 2.0 {@code response_type} that the Client will restrict itself to using, OPTIONAL.
*
* @param responseType the OAuth 2.0 {@code response_type} that the Client will restrict itself to using
* @return the {@link Builder} for further configuration
*/
public Builder responseType(String responseType) {
addClaimToClaimList(OidcClientMetadataClaimNames.RESPONSE_TYPES, responseType);
return this;
}
/**
* A {@code Consumer} of the OAuth 2.0 {@code response_type} values that the Client will restrict itself to using,
* allowing the ability to add, replace, or remove, OPTIONAL.
*
* @param responseTypesConsumer a {@code Consumer} of the OAuth 2.0 {@code response_type} values that the Client will restrict itself to using
* @return the {@link Builder} for further configuration
*/
public Builder responseTypes(Consumer<List<String>> responseTypesConsumer) {
acceptClaimValues(OidcClientMetadataClaimNames.RESPONSE_TYPES, responseTypesConsumer);
return this;
}
/**
* Add the OAuth 2.0 {@code scope} that the Client will restrict itself to using, OPTIONAL.
*
* @param scope the OAuth 2.0 {@code scope} that the Client will restrict itself to using
* @return the {@link Builder} for further configuration
*/
public Builder scope(String scope) {
addClaimToClaimList(OidcClientMetadataClaimNames.SCOPE, scope);
return this;
}
/**
* A {@code Consumer} of the OAuth 2.0 {@code scope} values that the Client will restrict itself to using,
* allowing the ability to add, replace, or remove, OPTIONAL.
*
* @param scopesConsumer a {@code Consumer} of the OAuth 2.0 {@code scope} values that the Client will restrict itself to using
* @return the {@link Builder} for further configuration
*/
public Builder scopes(Consumer<List<String>> scopesConsumer) {
acceptClaimValues(OidcClientMetadataClaimNames.SCOPE, scopesConsumer);
return this;
}
/**
* Sets the {@code URL} for the Client's JSON Web Key Set, OPTIONAL.
*
* @param jwkSetUrl the {@code URL} for the Client's JSON Web Key Set
* @return the {@link Builder} for further configuration
* @since 0.2.2
*/
public Builder jwkSetUrl(String jwkSetUrl) {
return claim(OidcClientMetadataClaimNames.JWKS_URI, jwkSetUrl);
}
/**
* Sets the {@link SignatureAlgorithm JWS} algorithm required for signing the {@link OidcIdToken ID Token} issued to the Client, OPTIONAL.
*
* @param idTokenSignedResponseAlgorithm the {@link SignatureAlgorithm JWS} algorithm required for signing the {@link OidcIdToken ID Token} issued to the Client
* @return the {@link Builder} for further configuration
*/
public Builder idTokenSignedResponseAlgorithm(String idTokenSignedResponseAlgorithm) {
return claim(OidcClientMetadataClaimNames.ID_TOKEN_SIGNED_RESPONSE_ALG, idTokenSignedResponseAlgorithm);
}
/**
* Sets the Registration Access Token that can be used at the Client Configuration Endpoint, OPTIONAL.
*
* @param registrationAccessToken the Registration Access Token that can be used at the Client Configuration Endpoint
* @return the {@link Builder} for further configuration
* @since 0.2.1
*/
public Builder registrationAccessToken(String registrationAccessToken) {
return claim(OidcClientMetadataClaimNames.REGISTRATION_ACCESS_TOKEN, registrationAccessToken);
}
/**
* Sets the {@code URL} of the Client Configuration Endpoint where the Registration Access Token can be used, OPTIONAL.
*
* @param registrationClientUrl the {@code URL} of the Client Configuration Endpoint where the Registration Access Token can be used
* @return the {@link Builder} for further configuration
* @since 0.2.1
*/
public Builder registrationClientUrl(String registrationClientUrl) {
return claim(OidcClientMetadataClaimNames.REGISTRATION_CLIENT_URI, registrationClientUrl);
}
/**
* Sets the claim.
*
* @param name the claim name
* @param value the claim value
* @return the {@link Builder} for further configuration
*/
public Builder claim(String name, Object value) {
Assert.hasText(name, "name cannot be empty");
Assert.notNull(value, "value cannot be null");
this.claims.put(name, value);
return this;
}
/**
* Provides access to every {@link #claim(String, Object)} declared so far
* allowing the ability to add, replace, or remove.
*
* @param claimsConsumer a {@code Consumer} of the claims
* @return the {@link Builder} for further configurations
*/
public Builder claims(Consumer<Map<String, Object>> claimsConsumer) {
claimsConsumer.accept(this.claims);
return this;
}
/**
* Validate the claims and build the {@link OidcClientRegistration}.
* <p>
* The following claims are REQUIRED:
* {@code client_id}, {@code redirect_uris}.
*
* @return the {@link OidcClientRegistration}
*/
public OidcClientRegistration build() {
validate();
return new OidcClientRegistration(this.claims);
}
private void validate() {
if (this.claims.get(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT) != null ||
this.claims.get(OidcClientMetadataClaimNames.CLIENT_SECRET) != null) {
Assert.notNull(this.claims.get(OidcClientMetadataClaimNames.CLIENT_ID), "client_id cannot be null");
}
if (this.claims.get(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT) != null) {
Assert.isInstanceOf(Instant.class, this.claims.get(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT), "client_id_issued_at must be of type Instant");
}
if (this.claims.get(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT) != null) {
Assert.notNull(this.claims.get(OidcClientMetadataClaimNames.CLIENT_SECRET), "client_secret cannot be null");
Assert.isInstanceOf(Instant.class, this.claims.get(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT), "client_secret_expires_at must be of type Instant");
}
Assert.notNull(this.claims.get(OidcClientMetadataClaimNames.REDIRECT_URIS), "redirect_uris cannot be null");
Assert.isInstanceOf(List.class, this.claims.get(OidcClientMetadataClaimNames.REDIRECT_URIS), "redirect_uris must be of type List");
Assert.notEmpty((List<?>) this.claims.get(OidcClientMetadataClaimNames.REDIRECT_URIS), "redirect_uris cannot be empty");
if (this.claims.get(OidcClientMetadataClaimNames.GRANT_TYPES) != null) {
Assert.isInstanceOf(List.class, this.claims.get(OidcClientMetadataClaimNames.GRANT_TYPES), "grant_types must be of type List");
Assert.notEmpty((List<?>) this.claims.get(OidcClientMetadataClaimNames.GRANT_TYPES), "grant_types cannot be empty");
}
if (this.claims.get(OidcClientMetadataClaimNames.RESPONSE_TYPES) != null) {
Assert.isInstanceOf(List.class, this.claims.get(OidcClientMetadataClaimNames.RESPONSE_TYPES), "response_types must be of type List");
Assert.notEmpty((List<?>) this.claims.get(OidcClientMetadataClaimNames.RESPONSE_TYPES), "response_types cannot be empty");
}
if (this.claims.get(OidcClientMetadataClaimNames.SCOPE) != null) {
Assert.isInstanceOf(List.class, this.claims.get(OidcClientMetadataClaimNames.SCOPE), "scope must be of type List");
Assert.notEmpty((List<?>) this.claims.get(OidcClientMetadataClaimNames.SCOPE), "scope cannot be empty");
}
if (this.claims.get(OidcClientMetadataClaimNames.JWKS_URI) != null) {
validateURL(this.claims.get(OidcClientMetadataClaimNames.JWKS_URI), "jwksUri must be a valid URL");
}
}
@SuppressWarnings("unchecked")
private void addClaimToClaimList(String name, String value) {
Assert.hasText(name, "name cannot be empty");
Assert.notNull(value, "value cannot be null");
this.claims.computeIfAbsent(name, k -> new LinkedList<String>());
((List<String>) this.claims.get(name)).add(value);
}
@SuppressWarnings("unchecked")
private void acceptClaimValues(String name, Consumer<List<String>> valuesConsumer) {
Assert.hasText(name, "name cannot be empty");
Assert.notNull(valuesConsumer, "valuesConsumer cannot be null");
this.claims.computeIfAbsent(name, k -> new LinkedList<String>());
List<String> values = (List<String>) this.claims.get(name);
valuesConsumer.accept(values);
}
private static void validateURL(Object url, String errorMessage) {
if (URL.class.isAssignableFrom(url.getClass())) {
return;
}
try {
new URI(url.toString()).toURL();
} catch (Exception ex) {
throw new IllegalArgumentException(errorMessage, ex);
}
}
}
}

View File

@@ -0,0 +1,181 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.oidc;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.springframework.security.oauth2.core.AbstractOAuth2AuthorizationServerMetadata;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
import org.springframework.util.Assert;
/**
* A representation of an OpenID Provider Configuration Response,
* which is returned from an Issuer's Discovery Endpoint,
* and contains a set of claims about the OpenID Provider's configuration.
* The claims are defined by the OpenID Connect Discovery 1.0 specification.
*
* @author Daniel Garnier-Moiroux
* @since 0.1.0
* @see AbstractOAuth2AuthorizationServerMetadata
* @see OidcProviderMetadataClaimAccessor
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse">4.2. OpenID Provider Configuration Response</a>
*/
public final class OidcProviderConfiguration extends AbstractOAuth2AuthorizationServerMetadata
implements OidcProviderMetadataClaimAccessor {
private OidcProviderConfiguration(Map<String, Object> claims) {
super(claims);
}
/**
* Constructs a new {@link Builder} with empty claims.
*
* @return the {@link Builder}
*/
public static Builder builder() {
return new Builder();
}
/**
* Constructs a new {@link Builder} with the provided claims.
*
* @param claims the claims to initialize the builder
*/
public static Builder withClaims(Map<String, Object> claims) {
Assert.notEmpty(claims, "claims cannot be empty");
return new Builder()
.claims(c -> c.putAll(claims));
}
/**
* Helps configure an {@link OidcProviderConfiguration}.
*/
public static class Builder extends AbstractBuilder<OidcProviderConfiguration, Builder> {
private Builder() {
}
/**
* Add this Subject Type to the collection of {@code subject_types_supported} in the resulting
* {@link OidcProviderConfiguration}, REQUIRED.
*
* @param subjectType the Subject Type that the OpenID Provider supports
* @return the {@link Builder} for further configuration
*/
public Builder subjectType(String subjectType) {
addClaimToClaimList(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, subjectType);
return this;
}
/**
* A {@code Consumer} of the Subject Types(s) allowing the ability to add, replace, or remove.
*
* @param subjectTypesConsumer a {@code Consumer} of the Subject Types(s)
* @return the {@link Builder} for further configuration
*/
public Builder subjectTypes(Consumer<List<String>> subjectTypesConsumer) {
acceptClaimValues(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, subjectTypesConsumer);
return this;
}
/**
* Add this {@link JwsAlgorithm JWS} signing algorithm to the collection of {@code id_token_signing_alg_values_supported}
* in the resulting {@link OidcProviderConfiguration}, REQUIRED.
*
* @param signingAlgorithm the {@link JwsAlgorithm JWS} signing algorithm supported for the {@link OidcIdToken ID Token}
* @return the {@link Builder} for further configuration
*/
public Builder idTokenSigningAlgorithm(String signingAlgorithm) {
addClaimToClaimList(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, signingAlgorithm);
return this;
}
/**
* A {@code Consumer} of the {@link JwsAlgorithm JWS} signing algorithms for the {@link OidcIdToken ID Token}
* allowing the ability to add, replace, or remove.
*
* @param signingAlgorithmsConsumer a {@code Consumer} of the {@link JwsAlgorithm JWS} signing algorithms for the {@link OidcIdToken ID Token}
* @return the {@link Builder} for further configuration
*/
public Builder idTokenSigningAlgorithms(Consumer<List<String>> signingAlgorithmsConsumer) {
acceptClaimValues(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, signingAlgorithmsConsumer);
return this;
}
/**
* Use this {@code userinfo_endpoint} in the resulting {@link OidcProviderConfiguration}, OPTIONAL.
*
* @param userInfoEndpoint the {@code URL} of the OpenID Connect 1.0 UserInfo Endpoint
* @return the {@link Builder} for further configuration
* @since 0.2.2
*/
public Builder userInfoEndpoint(String userInfoEndpoint) {
return claim(OidcProviderMetadataClaimNames.USER_INFO_ENDPOINT, userInfoEndpoint);
}
/**
* Validate the claims and build the {@link OidcProviderConfiguration}.
* <p>
* The following claims are REQUIRED:
* {@code issuer}, {@code authorization_endpoint}, {@code token_endpoint}, {@code jwks_uri},
* {@code response_types_supported}, {@code subject_types_supported} and
* {@code id_token_signing_alg_values_supported}.
*
* @return the {@link OidcProviderConfiguration}
*/
@Override
public OidcProviderConfiguration build() {
validate();
return new OidcProviderConfiguration(getClaims());
}
@Override
protected void validate() {
super.validate();
Assert.notNull(getClaims().get(OidcProviderMetadataClaimNames.JWKS_URI), "jwksUri cannot be null");
Assert.notNull(getClaims().get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), "subjectTypes cannot be null");
Assert.isInstanceOf(List.class, getClaims().get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), "subjectTypes must be of type List");
Assert.notEmpty((List<?>) getClaims().get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), "subjectTypes cannot be empty");
Assert.notNull(getClaims().get(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED), "idTokenSigningAlgorithms cannot be null");
Assert.isInstanceOf(List.class, getClaims().get(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED), "idTokenSigningAlgorithms must be of type List");
Assert.notEmpty((List<?>) getClaims().get(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED), "idTokenSigningAlgorithms cannot be empty");
if (getClaims().get(OidcProviderMetadataClaimNames.USER_INFO_ENDPOINT) != null) {
validateURL(getClaims().get(OidcProviderMetadataClaimNames.USER_INFO_ENDPOINT), "userInfoEndpoint must be a valid URL");
}
}
@SuppressWarnings("unchecked")
private void addClaimToClaimList(String name, String value) {
Assert.hasText(name, "name cannot be empty");
Assert.notNull(value, "value cannot be null");
getClaims().computeIfAbsent(name, k -> new LinkedList<String>());
((List<String>) getClaims().get(name)).add(value);
}
@SuppressWarnings("unchecked")
private void acceptClaimValues(String name, Consumer<List<String>> valuesConsumer) {
Assert.hasText(name, "name cannot be empty");
Assert.notNull(valuesConsumer, "valuesConsumer cannot be null");
getClaims().computeIfAbsent(name, k -> new LinkedList<String>());
List<String> values = (List<String>) getClaims().get(name);
valuesConsumer.accept(values);
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.oidc;
import java.net.URL;
import java.util.List;
import org.springframework.security.oauth2.core.ClaimAccessor;
import org.springframework.security.oauth2.core.OAuth2AuthorizationServerMetadataClaimAccessor;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
/**
* A {@link ClaimAccessor} for the "claims" that can be returned
* in the OpenID Provider Configuration Response.
*
* @author Daniel Garnier-Moiroux
* @since 0.1.0
* @see ClaimAccessor
* @see OAuth2AuthorizationServerMetadataClaimAccessor
* @see OidcProviderMetadataClaimNames
* @see OidcProviderConfiguration
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata">3. OpenID Provider Metadata</a>
*/
public interface OidcProviderMetadataClaimAccessor extends OAuth2AuthorizationServerMetadataClaimAccessor {
/**
* Returns the Subject Identifier types supported {@code (subject_types_supported)}.
*
* @return the Subject Identifier types supported
*/
default List<String> getSubjectTypes() {
return getClaimAsStringList(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED);
}
/**
* Returns the {@link JwsAlgorithm JWS} signing algorithms supported for the {@link OidcIdToken ID Token}
* to encode the claims in a {@link Jwt} {@code (id_token_signing_alg_values_supported)}.
*
* @return the {@link JwsAlgorithm JWS} signing algorithms supported for the {@link OidcIdToken ID Token}
*/
default List<String> getIdTokenSigningAlgorithms() {
return getClaimAsStringList(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED);
}
/**
* Returns the {@code URL} of the OpenID Connect 1.0 UserInfo Endpoint {@code (userinfo_endpoint)}.
*
* @return the {@code URL} of the OpenID Connect 1.0 UserInfo Endpoint
* @since 0.2.2
*/
default URL getUserInfoEndpoint() {
return getClaimAsURL(OidcProviderMetadataClaimNames.USER_INFO_ENDPOINT);
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.oidc;
import org.springframework.security.oauth2.core.OAuth2AuthorizationServerMetadataClaimNames;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
/**
* The names of the "claims" defined by OpenID Connect Discovery 1.0 that can be returned
* in the OpenID Provider Configuration Response.
*
* @author Daniel Garnier-Moiroux
* @since 0.1.0
* @see OAuth2AuthorizationServerMetadataClaimNames
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata">3. OpenID Provider Metadata</a>
*/
public interface OidcProviderMetadataClaimNames extends OAuth2AuthorizationServerMetadataClaimNames {
/**
* {@code subject_types_supported} - the Subject Identifier types supported
*/
String SUBJECT_TYPES_SUPPORTED = "subject_types_supported";
/**
* {@code id_token_signing_alg_values_supported} - the {@link JwsAlgorithm JWS} signing algorithms supported for the {@link OidcIdToken ID Token}
*/
String ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED = "id_token_signing_alg_values_supported";
/**
* {@code userinfo_endpoint} - the {@code URL} of the OpenID Connect 1.0 UserInfo Endpoint
* @since 0.2.2
*/
String USER_INFO_ENDPOINT = "userinfo_endpoint";
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.oidc.http.converter;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.ClassUtils;
/**
* TODO
* This class is a straight copy from Spring Security.
* It should be consolidated when merging this codebase into Spring Security.
*
* Utility methods for {@link HttpMessageConverter}'s.
*
* @author Joe Grandja
* @since 5.1
*/
final class HttpMessageConverters {
private static final boolean jackson2Present;
private static final boolean gsonPresent;
private static final boolean jsonbPresent;
static {
ClassLoader classLoader = HttpMessageConverters.class.getClassLoader();
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
}
private HttpMessageConverters() {
}
static GenericHttpMessageConverter<Object> getJsonMessageConverter() {
if (jackson2Present) {
return new MappingJackson2HttpMessageConverter();
}
if (gsonPresent) {
return new GsonHttpMessageConverter();
}
if (jsonbPresent) {
return new JsonbHttpMessageConverter();
}
return null;
}
}

View File

@@ -0,0 +1,214 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.oidc.http.converter;
import java.net.URL;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
import org.springframework.security.oauth2.core.oidc.OidcClientMetadataClaimNames;
import org.springframework.security.oauth2.core.oidc.OidcClientRegistration;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* A {@link HttpMessageConverter} for an {@link OidcClientRegistration OpenID Client Registration Request and Response}.
*
* @author Ovidiu Popa
* @author Joe Grandja
* @since 0.1.1
* @see AbstractHttpMessageConverter
* @see OidcClientRegistration
*/
public class OidcClientRegistrationHttpMessageConverter extends AbstractHttpMessageConverter<OidcClientRegistration> {
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP = new ParameterizedTypeReference<Map<String, Object>>() {
};
private final GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter();
private Converter<Map<String, Object>, OidcClientRegistration> clientRegistrationConverter = new MapOidcClientRegistrationConverter();
private Converter<OidcClientRegistration, Map<String, Object>> clientRegistrationParametersConverter = new OidcClientRegistrationMapConverter();
public OidcClientRegistrationHttpMessageConverter() {
super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
}
@Override
protected boolean supports(Class<?> clazz) {
return OidcClientRegistration.class.isAssignableFrom(clazz);
}
@Override
@SuppressWarnings("unchecked")
protected OidcClientRegistration readInternal(Class<? extends OidcClientRegistration> clazz, HttpInputMessage inputMessage)
throws HttpMessageNotReadableException {
try {
Map<String, Object> clientRegistrationParameters = (Map<String, Object>) this.jsonMessageConverter
.read(STRING_OBJECT_MAP.getType(), null, inputMessage);
return this.clientRegistrationConverter.convert(clientRegistrationParameters);
} catch (Exception ex) {
throw new HttpMessageNotReadableException(
"An error occurred reading the OpenID Client Registration: " + ex.getMessage(), ex, inputMessage);
}
}
@Override
protected void writeInternal(OidcClientRegistration clientRegistration, HttpOutputMessage outputMessage)
throws HttpMessageNotWritableException {
try {
Map<String, Object> clientRegistrationParameters = this.clientRegistrationParametersConverter
.convert(clientRegistration);
this.jsonMessageConverter.write(clientRegistrationParameters, STRING_OBJECT_MAP.getType(),
MediaType.APPLICATION_JSON, outputMessage);
} catch (Exception ex) {
throw new HttpMessageNotWritableException(
"An error occurred writing the OpenID Client Registration: " + ex.getMessage(), ex);
}
}
/**
* Sets the {@link Converter} used for converting the OpenID Client Registration parameters to an {@link OidcClientRegistration}.
*
* @param clientRegistrationConverter the {@link Converter} used for converting to an {@link OidcClientRegistration}
*/
public final void setClientRegistrationConverter(
Converter<Map<String, Object>, OidcClientRegistration> clientRegistrationConverter) {
Assert.notNull(clientRegistrationConverter, "clientRegistrationConverter cannot be null");
this.clientRegistrationConverter = clientRegistrationConverter;
}
/**
* Sets the {@link Converter} used for converting the {@link OidcClientRegistration}
* to a {@code Map} representation of the OpenID Client Registration parameters.
*
* @param clientRegistrationParametersConverter the {@link Converter} used for converting to a
* {@code Map} representation of the OpenID Client Registration parameters
*/
public final void setClientRegistrationParametersConverter(
Converter<OidcClientRegistration, Map<String, Object>> clientRegistrationParametersConverter) {
Assert.notNull(clientRegistrationParametersConverter, "clientRegistrationParametersConverter cannot be null");
this.clientRegistrationParametersConverter = clientRegistrationParametersConverter;
}
private static final class MapOidcClientRegistrationConverter
implements Converter<Map<String, Object>, OidcClientRegistration> {
private static final ClaimConversionService CLAIM_CONVERSION_SERVICE = ClaimConversionService.getSharedInstance();
private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class);
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
private static final TypeDescriptor INSTANT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Instant.class);
private static final TypeDescriptor URL_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(URL.class);
private static final Converter<Object, ?> INSTANT_CONVERTER = getConverter(INSTANT_TYPE_DESCRIPTOR);
private final ClaimTypeConverter claimTypeConverter;
private MapOidcClientRegistrationConverter() {
Converter<Object, ?> stringConverter = getConverter(STRING_TYPE_DESCRIPTOR);
Converter<Object, ?> collectionStringConverter = getConverter(
TypeDescriptor.collection(Collection.class, STRING_TYPE_DESCRIPTOR));
Converter<Object, ?> urlConverter = getConverter(URL_TYPE_DESCRIPTOR);
Map<String, Converter<Object, ?>> claimConverters = new HashMap<>();
claimConverters.put(OidcClientMetadataClaimNames.CLIENT_ID, stringConverter);
claimConverters.put(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT, INSTANT_CONVERTER);
claimConverters.put(OidcClientMetadataClaimNames.CLIENT_SECRET, stringConverter);
claimConverters.put(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT, MapOidcClientRegistrationConverter::convertClientSecretExpiresAt);
claimConverters.put(OidcClientMetadataClaimNames.CLIENT_NAME, stringConverter);
claimConverters.put(OidcClientMetadataClaimNames.REDIRECT_URIS, collectionStringConverter);
claimConverters.put(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD, stringConverter);
claimConverters.put(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_SIGNING_ALG, stringConverter);
claimConverters.put(OidcClientMetadataClaimNames.GRANT_TYPES, collectionStringConverter);
claimConverters.put(OidcClientMetadataClaimNames.RESPONSE_TYPES, collectionStringConverter);
claimConverters.put(OidcClientMetadataClaimNames.SCOPE, MapOidcClientRegistrationConverter::convertScope);
claimConverters.put(OidcClientMetadataClaimNames.JWKS_URI, urlConverter);
claimConverters.put(OidcClientMetadataClaimNames.ID_TOKEN_SIGNED_RESPONSE_ALG, stringConverter);
this.claimTypeConverter = new ClaimTypeConverter(claimConverters);
}
@Override
public OidcClientRegistration convert(Map<String, Object> source) {
Map<String, Object> parsedClaims = this.claimTypeConverter.convert(source);
Object clientSecretExpiresAt = parsedClaims.get(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT);
if (clientSecretExpiresAt instanceof Number && clientSecretExpiresAt.equals(0)) {
parsedClaims.remove(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT);
}
return OidcClientRegistration.withClaims(parsedClaims).build();
}
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
return source -> CLAIM_CONVERSION_SERVICE.convert(source, OBJECT_TYPE_DESCRIPTOR, targetDescriptor);
}
private static Instant convertClientSecretExpiresAt(Object clientSecretExpiresAt) {
if (clientSecretExpiresAt != null && String.valueOf(clientSecretExpiresAt).equals("0")) {
// 0 indicates that client_secret_expires_at does not expire
return null;
}
return (Instant) INSTANT_CONVERTER.convert(clientSecretExpiresAt);
}
private static List<String> convertScope(Object scope) {
if (scope == null) {
return Collections.emptyList();
}
return Arrays.asList(StringUtils.delimitedListToStringArray(scope.toString(), " "));
}
}
private static final class OidcClientRegistrationMapConverter
implements Converter<OidcClientRegistration, Map<String, Object>> {
@Override
public Map<String, Object> convert(OidcClientRegistration source) {
Map<String, Object> responseClaims = new LinkedHashMap<>(source.getClaims());
if (source.getClientIdIssuedAt() != null) {
responseClaims.put(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT, source.getClientIdIssuedAt().getEpochSecond());
}
if (source.getClientSecret() != null) {
long clientSecretExpiresAt = 0;
if (source.getClientSecretExpiresAt() != null) {
clientSecretExpiresAt = source.getClientSecretExpiresAt().getEpochSecond();
}
responseClaims.put(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT, clientSecretExpiresAt);
}
if (!CollectionUtils.isEmpty(source.getScopes())) {
responseClaims.put(OidcClientMetadataClaimNames.SCOPE, StringUtils.collectionToDelimitedString(source.getScopes(), " "));
}
return responseClaims;
}
}
}

View File

@@ -0,0 +1,162 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.oidc.http.converter;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
import org.springframework.security.oauth2.core.oidc.OidcProviderConfiguration;
import org.springframework.security.oauth2.core.oidc.OidcProviderMetadataClaimNames;
import org.springframework.util.Assert;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* A {@link HttpMessageConverter} for an {@link OidcProviderConfiguration OpenID Provider Configuration Response}.
*
* @author Daniel Garnier-Moiroux
* @since 0.1.0
* @see AbstractHttpMessageConverter
* @see OidcProviderConfiguration
*/
public class OidcProviderConfigurationHttpMessageConverter
extends AbstractHttpMessageConverter<OidcProviderConfiguration> {
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP =
new ParameterizedTypeReference<Map<String, Object>>() {};
private final GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter();
private Converter<Map<String, Object>, OidcProviderConfiguration> providerConfigurationConverter = new OidcProviderConfigurationConverter();
private Converter<OidcProviderConfiguration, Map<String, Object>> providerConfigurationParametersConverter = OidcProviderConfiguration::getClaims;
public OidcProviderConfigurationHttpMessageConverter() {
super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
}
@Override
protected boolean supports(Class<?> clazz) {
return OidcProviderConfiguration.class.isAssignableFrom(clazz);
}
@Override
@SuppressWarnings("unchecked")
protected OidcProviderConfiguration readInternal(Class<? extends OidcProviderConfiguration> clazz, HttpInputMessage inputMessage)
throws HttpMessageNotReadableException {
try {
Map<String, Object> providerConfigurationParameters =
(Map<String, Object>) this.jsonMessageConverter.read(STRING_OBJECT_MAP.getType(), null, inputMessage);
return this.providerConfigurationConverter.convert(providerConfigurationParameters);
} catch (Exception ex) {
throw new HttpMessageNotReadableException(
"An error occurred reading the OpenID Provider Configuration: " + ex.getMessage(), ex, inputMessage);
}
}
@Override
protected void writeInternal(OidcProviderConfiguration providerConfiguration, HttpOutputMessage outputMessage)
throws HttpMessageNotWritableException {
try {
Map<String, Object> providerConfigurationResponseParameters =
this.providerConfigurationParametersConverter.convert(providerConfiguration);
this.jsonMessageConverter.write(
providerConfigurationResponseParameters,
STRING_OBJECT_MAP.getType(),
MediaType.APPLICATION_JSON,
outputMessage
);
} catch (Exception ex) {
throw new HttpMessageNotWritableException(
"An error occurred writing the OpenID Provider Configuration: " + ex.getMessage(), ex);
}
}
/**
* Sets the {@link Converter} used for converting the OpenID Provider Configuration parameters
* to an {@link OidcProviderConfiguration}.
*
* @param providerConfigurationConverter the {@link Converter} used for converting to an
* {@link OidcProviderConfiguration}
*/
public final void setProviderConfigurationConverter(Converter<Map<String, Object>, OidcProviderConfiguration> providerConfigurationConverter) {
Assert.notNull(providerConfigurationConverter, "providerConfigurationConverter cannot be null");
this.providerConfigurationConverter = providerConfigurationConverter;
}
/**
* Sets the {@link Converter} used for converting the {@link OidcProviderConfiguration} to a
* {@code Map} representation of the OpenID Provider Configuration.
*
* @param providerConfigurationParametersConverter the {@link Converter} used for converting to a
* {@code Map} representation of the OpenID Provider Configuration
*/
public final void setProviderConfigurationParametersConverter(
Converter<OidcProviderConfiguration, Map<String, Object>> providerConfigurationParametersConverter) {
Assert.notNull(providerConfigurationParametersConverter, "providerConfigurationParametersConverter cannot be null");
this.providerConfigurationParametersConverter = providerConfigurationParametersConverter;
}
private static final class OidcProviderConfigurationConverter implements Converter<Map<String, Object>, OidcProviderConfiguration> {
private static final ClaimConversionService CLAIM_CONVERSION_SERVICE = ClaimConversionService.getSharedInstance();
private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class);
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
private static final TypeDescriptor URL_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(URL.class);
private final ClaimTypeConverter claimTypeConverter;
private OidcProviderConfigurationConverter() {
Converter<Object, ?> collectionStringConverter = getConverter(
TypeDescriptor.collection(Collection.class, STRING_TYPE_DESCRIPTOR));
Converter<Object, ?> urlConverter = getConverter(URL_TYPE_DESCRIPTOR);
Map<String, Converter<Object, ?>> claimConverters = new HashMap<>();
claimConverters.put(OidcProviderMetadataClaimNames.ISSUER, urlConverter);
claimConverters.put(OidcProviderMetadataClaimNames.AUTHORIZATION_ENDPOINT, urlConverter);
claimConverters.put(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT, urlConverter);
claimConverters.put(OidcProviderMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, collectionStringConverter);
claimConverters.put(OidcProviderMetadataClaimNames.JWKS_URI, urlConverter);
claimConverters.put(OidcProviderMetadataClaimNames.USER_INFO_ENDPOINT, urlConverter);
claimConverters.put(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, collectionStringConverter);
claimConverters.put(OidcProviderMetadataClaimNames.GRANT_TYPES_SUPPORTED, collectionStringConverter);
claimConverters.put(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, collectionStringConverter);
claimConverters.put(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, collectionStringConverter);
claimConverters.put(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED, collectionStringConverter);
this.claimTypeConverter = new ClaimTypeConverter(claimConverters);
}
@Override
public OidcProviderConfiguration convert(Map<String, Object> source) {
Map<String, Object> parsedClaims = this.claimTypeConverter.convert(source);
return OidcProviderConfiguration.withClaims(parsedClaims).build();
}
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
return (source) -> CLAIM_CONVERSION_SERVICE.convert(source, OBJECT_TYPE_DESCRIPTOR, targetDescriptor);
}
}
}

View File

@@ -0,0 +1,174 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.oidc.http.converter;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
import org.springframework.util.Assert;
/**
* A {@link HttpMessageConverter} for an {@link OidcUserInfo OpenID Connect UserInfo Response}.
*
* @author Ido Salomon
* @author Steve Riesenberg
* @since 0.2.1
* @see AbstractHttpMessageConverter
* @see OidcUserInfo
*/
public class OidcUserInfoHttpMessageConverter extends AbstractHttpMessageConverter<OidcUserInfo> {
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP =
new ParameterizedTypeReference<Map<String, Object>>() {};
private final GenericHttpMessageConverter<Object> jsonMessageConverter =
HttpMessageConverters.getJsonMessageConverter();
private Converter<Map<String, Object>, OidcUserInfo> userInfoConverter = new MapOidcUserInfoConverter();
private Converter<OidcUserInfo, Map<String, Object>> userInfoParametersConverter = OidcUserInfo::getClaims;
public OidcUserInfoHttpMessageConverter() {
super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
}
@Override
protected boolean supports(Class<?> clazz) {
return OidcUserInfo.class.isAssignableFrom(clazz);
}
@Override
@SuppressWarnings("unchecked")
protected OidcUserInfo readInternal(Class<? extends OidcUserInfo> clazz, HttpInputMessage inputMessage)
throws HttpMessageNotReadableException {
try {
Map<String, Object> userInfoParameters =
(Map<String, Object>) this.jsonMessageConverter.read(STRING_OBJECT_MAP.getType(), null, inputMessage);
return this.userInfoConverter.convert(userInfoParameters);
} catch (Exception ex) {
throw new HttpMessageNotReadableException(
"An error occurred reading the UserInfo response: " + ex.getMessage(), ex, inputMessage);
}
}
@Override
protected void writeInternal(OidcUserInfo oidcUserInfo, HttpOutputMessage outputMessage)
throws HttpMessageNotWritableException {
try {
Map<String, Object> userInfoResponseParameters =
this.userInfoParametersConverter.convert(oidcUserInfo);
this.jsonMessageConverter.write(
userInfoResponseParameters,
STRING_OBJECT_MAP.getType(),
MediaType.APPLICATION_JSON,
outputMessage
);
} catch (Exception ex) {
throw new HttpMessageNotWritableException(
"An error occurred writing the UserInfo response: " + ex.getMessage(), ex);
}
}
/**
* Sets the {@link Converter} used for converting the UserInfo parameters
* to an {@link OidcUserInfo}.
*
* @param userInfoConverter the {@link Converter} used for converting to an {@link OidcUserInfo}
*/
public final void setUserInfoConverter(Converter<Map<String, Object>, OidcUserInfo> userInfoConverter) {
Assert.notNull(userInfoConverter, "userInfoConverter cannot be null");
this.userInfoConverter = userInfoConverter;
}
/**
* Sets the {@link Converter} used for converting the {@link OidcUserInfo} to a
* {@code Map} representation of the UserInfo.
*
* @param userInfoParametersConverter the {@link Converter} used for converting to a
* {@code Map} representation of the UserInfo
*/
public final void setUserInfoParametersConverter(
Converter<OidcUserInfo, Map<String, Object>> userInfoParametersConverter) {
Assert.notNull(userInfoParametersConverter, "userInfoParametersConverter cannot be null");
this.userInfoParametersConverter = userInfoParametersConverter;
}
private static final class MapOidcUserInfoConverter implements Converter<Map<String, Object>, OidcUserInfo> {
private static final ClaimConversionService CLAIM_CONVERSION_SERVICE = ClaimConversionService.getSharedInstance();
private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class);
private static final TypeDescriptor BOOLEAN_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Boolean.class);
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
private static final TypeDescriptor INSTANT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Instant.class);
private static final TypeDescriptor STRING_OBJECT_MAP_DESCRIPTOR = TypeDescriptor.map(Map.class, STRING_TYPE_DESCRIPTOR, OBJECT_TYPE_DESCRIPTOR);
private final ClaimTypeConverter claimTypeConverter;
private MapOidcUserInfoConverter() {
Converter<Object, ?> booleanConverter = getConverter(BOOLEAN_TYPE_DESCRIPTOR);
Converter<Object, ?> stringConverter = getConverter(STRING_TYPE_DESCRIPTOR);
Converter<Object, ?> instantConverter = getConverter(INSTANT_TYPE_DESCRIPTOR);
Converter<Object, ?> mapConverter = getConverter(STRING_OBJECT_MAP_DESCRIPTOR);
Map<String, Converter<Object, ?>> claimConverters = new HashMap<>();
claimConverters.put(StandardClaimNames.SUB, stringConverter);
claimConverters.put(StandardClaimNames.NAME, stringConverter);
claimConverters.put(StandardClaimNames.GIVEN_NAME, stringConverter);
claimConverters.put(StandardClaimNames.FAMILY_NAME, stringConverter);
claimConverters.put(StandardClaimNames.MIDDLE_NAME, stringConverter);
claimConverters.put(StandardClaimNames.NICKNAME, stringConverter);
claimConverters.put(StandardClaimNames.PREFERRED_USERNAME, stringConverter);
claimConverters.put(StandardClaimNames.PROFILE, stringConverter);
claimConverters.put(StandardClaimNames.PICTURE, stringConverter);
claimConverters.put(StandardClaimNames.WEBSITE, stringConverter);
claimConverters.put(StandardClaimNames.EMAIL, stringConverter);
claimConverters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter);
claimConverters.put(StandardClaimNames.GENDER, stringConverter);
claimConverters.put(StandardClaimNames.BIRTHDATE, stringConverter);
claimConverters.put(StandardClaimNames.ZONEINFO, stringConverter);
claimConverters.put(StandardClaimNames.LOCALE, stringConverter);
claimConverters.put(StandardClaimNames.PHONE_NUMBER, stringConverter);
claimConverters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, booleanConverter);
claimConverters.put(StandardClaimNames.ADDRESS, mapConverter);
claimConverters.put(StandardClaimNames.UPDATED_AT, instantConverter);
this.claimTypeConverter = new ClaimTypeConverter(claimConverters);
}
@Override
public OidcUserInfo convert(Map<String, Object> source) {
Map<String, Object> parsedClaims = this.claimTypeConverter.convert(source);
return new OidcUserInfo(parsedClaims);
}
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
return (source) -> CLAIM_CONVERSION_SERVICE.convert(source, OBJECT_TYPE_DESCRIPTOR, targetDescriptor);
}
}
}

View File

@@ -1,324 +0,0 @@
/*
* Copyright 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.security.oauth2.jose.jws;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.KeyLengthException;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.util.Base64;
import com.nimbusds.jose.util.Base64URL;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.crypto.keys.KeyManager;
import org.springframework.security.crypto.keys.ManagedKey;
import org.springframework.security.oauth2.jose.JoseHeader;
import org.springframework.security.oauth2.jose.JoseHeaderNames;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncodingException;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import javax.crypto.SecretKey;
import java.net.URI;
import java.net.URL;
import java.security.PrivateKey;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* An implementation of a {@link JwtEncoder} that encodes a JSON Web Token (JWT)
* using the JSON Web Signature (JWS) Compact Serialization format.
* The private/secret key used for signing the JWS is obtained
* from the {@link KeyManager} supplied via the constructor.
*
* <p>
* <b>NOTE:</b> This implementation uses the Nimbus JOSE + JWT SDK.
*
* @author Joe Grandja
* @since 0.0.1
* @see JwtEncoder
* @see KeyManager
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7519">JSON Web Token (JWT)</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515">JSON Web Signature (JWS)</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515#section-3.1">JWS Compact Serialization</a>
* @see <a target="_blank" href="https://connect2id.com/products/nimbus-jose-jwt">Nimbus JOSE + JWT SDK</a>
*/
public final class NimbusJwsEncoder implements JwtEncoder {
private static final String ENCODING_ERROR_MESSAGE_TEMPLATE =
"An error occurred while attempting to encode the Jwt: %s";
private static final String RSA_KEY_TYPE = "RSA";
private static final String EC_KEY_TYPE = "EC";
private static final Map<JwsAlgorithm, String> jcaKeyAlgorithmMappings = new HashMap<JwsAlgorithm, String>() {
{
put(MacAlgorithm.HS256, "HmacSHA256");
put(MacAlgorithm.HS384, "HmacSHA384");
put(MacAlgorithm.HS512, "HmacSHA512");
put(SignatureAlgorithm.RS256, RSA_KEY_TYPE);
put(SignatureAlgorithm.RS384, RSA_KEY_TYPE);
put(SignatureAlgorithm.RS512, RSA_KEY_TYPE);
put(SignatureAlgorithm.ES256, EC_KEY_TYPE);
put(SignatureAlgorithm.ES384, EC_KEY_TYPE);
put(SignatureAlgorithm.ES512, EC_KEY_TYPE);
}
};
private static final Converter<JoseHeader, JWSHeader> jwsHeaderConverter = new JwsHeaderConverter();
private static final Converter<JwtClaimsSet, JWTClaimsSet> jwtClaimsSetConverter = new JwtClaimsSetConverter();
private final KeyManager keyManager;
/**
* Constructs a {@code NimbusJwsEncoder} using the provided parameters.
*
* @param keyManager the key manager
*/
public NimbusJwsEncoder(KeyManager keyManager) {
Assert.notNull(keyManager, "keyManager cannot be null");
this.keyManager = keyManager;
}
@Override
public Jwt encode(JoseHeader headers, JwtClaimsSet claims) throws JwtEncodingException {
Assert.notNull(headers, "headers cannot be null");
Assert.notNull(claims, "claims cannot be null");
ManagedKey managedKey = selectKey(headers);
if (managedKey == null) {
throw new JwtEncodingException(String.format(
ENCODING_ERROR_MESSAGE_TEMPLATE,
"Unsupported key for algorithm '" + headers.getJwsAlgorithm().getName() + "'"));
}
JWSSigner jwsSigner;
if (managedKey.isAsymmetric()) {
if (!managedKey.getAlgorithm().equals(RSA_KEY_TYPE)) {
throw new JwtEncodingException(String.format(
ENCODING_ERROR_MESSAGE_TEMPLATE,
"Unsupported key type '" + managedKey.getAlgorithm() + "'"));
}
PrivateKey privateKey = managedKey.getKey();
jwsSigner = new RSASSASigner(privateKey);
} else {
SecretKey secretKey = managedKey.getKey();
try {
jwsSigner = new MACSigner(secretKey);
} catch (KeyLengthException ex) {
throw new JwtEncodingException(String.format(
ENCODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
}
}
headers = JoseHeader.from(headers)
.type(JOSEObjectType.JWT.getType())
.keyId(managedKey.getKeyId())
.build();
JWSHeader jwsHeader = jwsHeaderConverter.convert(headers);
claims = JwtClaimsSet.from(claims)
.id(UUID.randomUUID().toString())
.build();
JWTClaimsSet jwtClaimsSet = jwtClaimsSetConverter.convert(claims);
SignedJWT signedJWT = new SignedJWT(jwsHeader, jwtClaimsSet);
try {
signedJWT.sign(jwsSigner);
} catch (JOSEException ex) {
throw new JwtEncodingException(String.format(
ENCODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
}
String jws = signedJWT.serialize();
return new Jwt(jws, claims.getIssuedAt(), claims.getExpiresAt(),
headers.getHeaders(), claims.getClaims());
}
private ManagedKey selectKey(JoseHeader headers) {
JwsAlgorithm jwsAlgorithm = headers.getJwsAlgorithm();
String keyAlgorithm = jcaKeyAlgorithmMappings.get(jwsAlgorithm);
if (!StringUtils.hasText(keyAlgorithm)) {
return null;
}
Set<ManagedKey> matchingKeys = this.keyManager.findByAlgorithm(keyAlgorithm);
if (CollectionUtils.isEmpty(matchingKeys)) {
return null;
}
return matchingKeys.stream()
.filter(ManagedKey::isActive)
.max(this::mostRecentActivated)
.orElse(null);
}
private int mostRecentActivated(ManagedKey managedKey1, ManagedKey managedKey2) {
return managedKey1.getActivatedOn().isAfter(managedKey2.getActivatedOn()) ? 1 : -1;
}
private static class JwsHeaderConverter implements Converter<JoseHeader, JWSHeader> {
@Override
public JWSHeader convert(JoseHeader headers) {
JWSHeader.Builder builder = new JWSHeader.Builder(
JWSAlgorithm.parse(headers.getJwsAlgorithm().getName()));
Set<String> critical = headers.getCritical();
if (!CollectionUtils.isEmpty(critical)) {
builder.criticalParams(critical);
}
String contentType = headers.getContentType();
if (StringUtils.hasText(contentType)) {
builder.contentType(contentType);
}
String jwkSetUri = headers.getJwkSetUri();
if (StringUtils.hasText(jwkSetUri)) {
try {
builder.jwkURL(new URI(jwkSetUri));
} catch (Exception ex) {
throw new JwtEncodingException(String.format(
ENCODING_ERROR_MESSAGE_TEMPLATE,
"Failed to convert '" + JoseHeaderNames.JKU + "' JOSE header"), ex);
}
}
Map<String, Object> jwk = headers.getJwk();
if (!CollectionUtils.isEmpty(jwk)) {
try {
builder.jwk(JWK.parse(jwk));
} catch (Exception ex) {
throw new JwtEncodingException(String.format(
ENCODING_ERROR_MESSAGE_TEMPLATE,
"Failed to convert '" + JoseHeaderNames.JWK + "' JOSE header"), ex);
}
}
String keyId = headers.getKeyId();
if (StringUtils.hasText(keyId)) {
builder.keyID(keyId);
}
String type = headers.getType();
if (StringUtils.hasText(type)) {
builder.type(new JOSEObjectType(type));
}
List<String> x509CertificateChain = headers.getX509CertificateChain();
if (!CollectionUtils.isEmpty(x509CertificateChain)) {
builder.x509CertChain(
x509CertificateChain.stream()
.map(Base64::new)
.collect(Collectors.toList()));
}
String x509SHA1Thumbprint = headers.getX509SHA1Thumbprint();
if (StringUtils.hasText(x509SHA1Thumbprint)) {
builder.x509CertThumbprint(new Base64URL(x509SHA1Thumbprint));
}
String x509SHA256Thumbprint = headers.getX509SHA256Thumbprint();
if (StringUtils.hasText(x509SHA256Thumbprint)) {
builder.x509CertSHA256Thumbprint(new Base64URL(x509SHA256Thumbprint));
}
String x509Uri = headers.getX509Uri();
if (StringUtils.hasText(x509Uri)) {
try {
builder.x509CertURL(new URI(x509Uri));
} catch (Exception ex) {
throw new JwtEncodingException(String.format(
ENCODING_ERROR_MESSAGE_TEMPLATE,
"Failed to convert '" + JoseHeaderNames.X5U + "' JOSE header"), ex);
}
}
Map<String, Object> customHeaders = headers.getHeaders().entrySet().stream()
.filter(header -> !JWSHeader.getRegisteredParameterNames().contains(header.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
if (!CollectionUtils.isEmpty(customHeaders)) {
builder.customParams(customHeaders);
}
return builder.build();
}
}
private static class JwtClaimsSetConverter implements Converter<JwtClaimsSet, JWTClaimsSet> {
@Override
public JWTClaimsSet convert(JwtClaimsSet claims) {
JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();
URL issuer = claims.getIssuer();
if (issuer != null) {
builder.issuer(issuer.toExternalForm());
}
String subject = claims.getSubject();
if (StringUtils.hasText(subject)) {
builder.subject(subject);
}
List<String> audience = claims.getAudience();
if (!CollectionUtils.isEmpty(audience)) {
builder.audience(audience);
}
Instant issuedAt = claims.getIssuedAt();
if (issuedAt != null) {
builder.issueTime(Date.from(issuedAt));
}
Instant expiresAt = claims.getExpiresAt();
if (expiresAt != null) {
builder.expirationTime(Date.from(expiresAt));
}
Instant notBefore = claims.getNotBefore();
if (notBefore != null) {
builder.notBeforeTime(Date.from(notBefore));
}
String jwtId = claims.getId();
if (StringUtils.hasText(jwtId)) {
builder.jwtID(jwtId);
}
Map<String, Object> customClaims = claims.getClaims().entrySet().stream()
.filter(claim -> !JWTClaimsSet.getRegisteredNames().contains(claim.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
if (!CollectionUtils.isEmpty(customClaims)) {
customClaims.forEach(builder::claim);
}
return builder.build();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,30 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.jose;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.util.Assert;
package org.springframework.security.oauth2.jwt;
import java.net.URL;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import static org.springframework.security.oauth2.jose.JoseHeaderNames.ALG;
import static org.springframework.security.oauth2.jose.JoseHeaderNames.CRIT;
import static org.springframework.security.oauth2.jose.JoseHeaderNames.CTY;
import static org.springframework.security.oauth2.jose.JoseHeaderNames.JKU;
import static org.springframework.security.oauth2.jose.JoseHeaderNames.JWK;
import static org.springframework.security.oauth2.jose.JoseHeaderNames.KID;
import static org.springframework.security.oauth2.jose.JoseHeaderNames.TYP;
import static org.springframework.security.oauth2.jose.JoseHeaderNames.X5C;
import static org.springframework.security.oauth2.jose.JoseHeaderNames.X5T;
import static org.springframework.security.oauth2.jose.JoseHeaderNames.X5T_S256;
import static org.springframework.security.oauth2.jose.JoseHeaderNames.X5U;
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
import org.springframework.security.oauth2.jose.JwaAlgorithm;
import org.springframework.util.Assert;
/**
* The JOSE header is a JSON object representing the header parameters of a JSON Web Token,
@@ -47,24 +36,27 @@ import static org.springframework.security.oauth2.jose.JoseHeaderNames.X5U;
* @author Joe Grandja
* @since 0.0.1
* @see Jwt
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7519#section-5">JWT JOSE Header</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515#section-4">JWS JOSE Header</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7516#section-4">JWE JOSE Header</a>
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7519#section-5">JWT JOSE Header</a>
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7515#section-4">JWS JOSE Header</a>
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7516#section-4">JWE JOSE Header</a>
* @deprecated See <a target="_blank" href="https://github.com/spring-projects/spring-authorization-server/issues/596">gh-596</a>
*/
@Deprecated
public final class JoseHeader {
private final Map<String, Object> headers;
private JoseHeader(Map<String, Object> headers) {
this.headers = Collections.unmodifiableMap(new LinkedHashMap<>(headers));
this.headers = Collections.unmodifiableMap(new HashMap<>(headers));
}
/**
* Returns the JWS algorithm used to digitally sign the JWS.
* Returns the {@link JwaAlgorithm JWA algorithm} used to digitally sign the JWS or encrypt the JWE.
*
* @return the JWS algorithm
* @return the {@link JwaAlgorithm}
*/
public JwsAlgorithm getJwsAlgorithm() {
return getHeader(ALG);
@SuppressWarnings("unchecked")
public <T extends JwaAlgorithm> T getAlgorithm() {
return (T) getHeader(JoseHeaderNames.ALG);
}
/**
@@ -73,8 +65,8 @@ public final class JoseHeader {
*
* @return the JWK Set URL
*/
public String getJwkSetUri() {
return getHeader(JKU);
public URL getJwkSetUrl() {
return getHeader(JoseHeaderNames.JKU);
}
/**
@@ -84,7 +76,7 @@ public final class JoseHeader {
* @return the JSON Web Key
*/
public Map<String, Object> getJwk() {
return getHeader(JWK);
return getHeader(JoseHeaderNames.JWK);
}
/**
@@ -93,7 +85,7 @@ public final class JoseHeader {
* @return the key ID
*/
public String getKeyId() {
return getHeader(KID);
return getHeader(JoseHeaderNames.KID);
}
/**
@@ -102,18 +94,21 @@ public final class JoseHeader {
*
* @return the X.509 URL
*/
public String getX509Uri() {
return getHeader(X5U);
public URL getX509Url() {
return getHeader(JoseHeaderNames.X5U);
}
/**
* Returns the X.509 certificate chain that contains the X.509 public key certificate
* or certificate chain corresponding to the key used to digitally sign the JWS or encrypt the JWE.
* or certificate chain corresponding to the key used to digitally sign the JWS or
* encrypt the JWE. The certificate or certificate chain is represented as a
* {@code List} of certificate value {@code String}s. Each {@code String} in the
* {@code List} is a Base64-encoded DER PKIX certificate value.
*
* @return the X.509 certificate chain
*/
public List<String> getX509CertificateChain() {
return getHeader(X5C);
return getHeader(JoseHeaderNames.X5C);
}
/**
@@ -123,7 +118,7 @@ public final class JoseHeader {
* @return the X.509 certificate SHA-1 thumbprint
*/
public String getX509SHA1Thumbprint() {
return getHeader(X5T);
return getHeader(JoseHeaderNames.X5T);
}
/**
@@ -133,7 +128,25 @@ public final class JoseHeader {
* @return the X.509 certificate SHA-256 thumbprint
*/
public String getX509SHA256Thumbprint() {
return getHeader(X5T_S256);
return getHeader(JoseHeaderNames.X5T_S256);
}
/**
* Returns the type header that declares the media type of the JWS/JWE.
*
* @return the type header
*/
public String getType() {
return getHeader(JoseHeaderNames.TYP);
}
/**
* Returns the content type header that declares the media type of the secured content (the payload).
*
* @return the content type header
*/
public String getContentType() {
return getHeader(JoseHeaderNames.CTY);
}
/**
@@ -143,25 +156,7 @@ public final class JoseHeader {
* @return the critical headers
*/
public Set<String> getCritical() {
return getHeader(CRIT);
}
/**
* Returns the type header that declares the media type of the JWS/JWE.
*
* @return the type header
*/
public String getType() {
return getHeader(TYP);
}
/**
* Returns the content type header that declares the media type of the secured content (the payload).
*
* @return the content type header
*/
public String getContentType() {
return getHeader(CTY);
return getHeader(JoseHeaderNames.CRIT);
}
/**
@@ -187,13 +182,22 @@ public final class JoseHeader {
}
/**
* Returns a new {@link Builder}, initialized with the provided {@link JwsAlgorithm}.
* Returns a new {@link Builder}.
*
* @param jwsAlgorithm the {@link JwsAlgorithm}
* @return the {@link Builder}
*/
public static Builder withAlgorithm(JwsAlgorithm jwsAlgorithm) {
return new Builder(jwsAlgorithm);
public static Builder builder() {
return new Builder();
}
/**
* Returns a new {@link Builder}, initialized with the provided {@link JwaAlgorithm}.
*
* @param jwaAlgorithm the {@link JwaAlgorithm}
* @return the {@link Builder}
*/
public static Builder withAlgorithm(JwaAlgorithm jwaAlgorithm) {
return new Builder(jwaAlgorithm);
}
/**
@@ -209,12 +213,14 @@ public final class JoseHeader {
/**
* A builder for {@link JoseHeader}.
*/
public static class Builder {
private final Map<String, Object> headers = new LinkedHashMap<>();
public static final class Builder {
private final Map<String, Object> headers = new HashMap<>();
private Builder(JwsAlgorithm jwsAlgorithm) {
Assert.notNull(jwsAlgorithm, "jwsAlgorithm cannot be null");
header(ALG, jwsAlgorithm);
private Builder() {
}
private Builder(JwaAlgorithm jwaAlgorithm) {
algorithm(jwaAlgorithm);
}
private Builder(JoseHeader headers) {
@@ -222,15 +228,26 @@ public final class JoseHeader {
this.headers.putAll(headers.getHeaders());
}
/**
* Sets the {@link JwaAlgorithm JWA algorithm} used to digitally sign the JWS or encrypt the JWE.
*
* @param jwaAlgorithm the {@link JwaAlgorithm}
* @return the {@link Builder}
*/
public Builder algorithm(JwaAlgorithm jwaAlgorithm) {
Assert.notNull(jwaAlgorithm, "jwaAlgorithm cannot be null");
return header(JoseHeaderNames.ALG, jwaAlgorithm);
}
/**
* Sets the JWK Set URL that refers to the resource of a set of JSON-encoded public keys,
* one of which corresponds to the key used to digitally sign the JWS or encrypt the JWE.
*
* @param jwkSetUri the JWK Set URL
* @param jwkSetUrl the JWK Set URL
* @return the {@link Builder}
*/
public Builder jwkSetUri(String jwkSetUri) {
return header(JKU, jwkSetUri);
public Builder jwkSetUrl(String jwkSetUrl) {
return header(JoseHeaderNames.JKU, convertAsURL(JoseHeaderNames.JKU, jwkSetUrl));
}
/**
@@ -241,7 +258,7 @@ public final class JoseHeader {
* @return the {@link Builder}
*/
public Builder jwk(Map<String, Object> jwk) {
return header(JWK, jwk);
return header(JoseHeaderNames.JWK, jwk);
}
/**
@@ -251,29 +268,32 @@ public final class JoseHeader {
* @return the {@link Builder}
*/
public Builder keyId(String keyId) {
return header(KID, keyId);
return header(JoseHeaderNames.KID, keyId);
}
/**
* Sets the X.509 URL that refers to the resource for the X.509 public key certificate
* or certificate chain corresponding to the key used to digitally sign the JWS or encrypt the JWE.
*
* @param x509Uri the X.509 URL
* @param x509Url the X.509 URL
* @return the {@link Builder}
*/
public Builder x509Uri(String x509Uri) {
return header(X5U, x509Uri);
public Builder x509Url(String x509Url) {
return header(JoseHeaderNames.X5U, convertAsURL(JoseHeaderNames.X5U, x509Url));
}
/**
* Sets the X.509 certificate chain that contains the X.509 public key certificate
* or certificate chain corresponding to the key used to digitally sign the JWS or encrypt the JWE.
* or certificate chain corresponding to the key used to digitally sign the JWS or
* encrypt the JWE. The certificate or certificate chain is represented as a
* {@code List} of certificate value {@code String}s. Each {@code String} in the
* {@code List} is a Base64-encoded DER PKIX certificate value.
*
* @param x509CertificateChain the X.509 certificate chain
* @return the {@link Builder}
*/
public Builder x509CertificateChain(List<String> x509CertificateChain) {
return header(X5C, x509CertificateChain);
return header(JoseHeaderNames.X5C, x509CertificateChain);
}
/**
@@ -284,7 +304,7 @@ public final class JoseHeader {
* @return the {@link Builder}
*/
public Builder x509SHA1Thumbprint(String x509SHA1Thumbprint) {
return header(X5T, x509SHA1Thumbprint);
return header(JoseHeaderNames.X5T, x509SHA1Thumbprint);
}
/**
@@ -295,7 +315,27 @@ public final class JoseHeader {
* @return the {@link Builder}
*/
public Builder x509SHA256Thumbprint(String x509SHA256Thumbprint) {
return header(X5T_S256, x509SHA256Thumbprint);
return header(JoseHeaderNames.X5T_S256, x509SHA256Thumbprint);
}
/**
* Sets the type header that declares the media type of the JWS/JWE.
*
* @param type the type header
* @return the {@link Builder}
*/
public Builder type(String type) {
return header(JoseHeaderNames.TYP, type);
}
/**
* Sets the content type header that declares the media type of the secured content (the payload).
*
* @param contentType the content type header
* @return the {@link Builder}
*/
public Builder contentType(String contentType) {
return header(JoseHeaderNames.CTY, contentType);
}
/**
@@ -306,27 +346,7 @@ public final class JoseHeader {
* @return the {@link Builder}
*/
public Builder critical(Set<String> headerNames) {
return header(CRIT, headerNames);
}
/**
* Sets the type header that declares the media type of the JWS/JWE.
*
* @param type the type header
* @return the {@link Builder}
*/
public Builder type(String type) {
return header(TYP, type);
}
/**
* Sets the content type header that declares the media type of the secured content (the payload).
*
* @param contentType the content type header
* @return the {@link Builder}
*/
public Builder contentType(String contentType) {
return header(CTY, contentType);
return header(JoseHeaderNames.CRIT, headerNames);
}
/**
@@ -364,5 +384,13 @@ public final class JoseHeader {
Assert.notEmpty(this.headers, "headers cannot be empty");
return new JoseHeader(this.headers);
}
private static URL convertAsURL(String header, String value) {
URL convertedValue = ClaimConversionService.getSharedInstance().convert(value, URL.class);
Assert.isTrue(convertedValue != null,
() -> "Unable to convert header '" + header + "' of type '" + value.getClass() + "' to URL.");
return convertedValue;
}
}
}

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