147 Commits
0.0.2 ... 0.1.1

Author SHA1 Message Date
Joe Grandja
dcf8741d16 Release 0.1.1 2021-05-07 15:02:13 -04:00
Joe Grandja
830556a19b Update Spring Boot to 2.4.5 2021-05-07 14:56:53 -04:00
Joe Grandja
e72899d827 Update Reactor to 2020.0.6 2021-05-07 14:50:19 -04:00
Joe Grandja
ec09d120ef Update Spring Security to 5.4.6 2021-05-07 14:49:38 -04:00
Joe Grandja
55dda0f767 Update Spring to 5.3.6 2021-05-07 14:48:54 -04:00
Joe Grandja
7933cb8c4b Fix sample
Issue gh-272
2021-05-07 14:45:28 -04:00
Joe Grandja
93d16d4419 Polish gh-272 2021-05-07 14:26:29 -04:00
Rafal Lewczuk
8cd954ffa2 Use PasswordEncoder in OAuth2ClientAuthenticationProvider
Closes gh-271
2021-05-07 14:25:24 -04:00
Joe Grandja
0c70e8ad3a Rename OAuth2TokenIntrospection.Builder.validateClaims() to validate()
Issue gh-161
2021-05-07 11:44:02 -04:00
Joe Grandja
e7feb6c0ed Polish gh-189 2021-05-07 11:21:41 -04:00
Ovidiu Popa
8224a0d971 Implement OpenID client registration endpoint
See: https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration

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

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

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

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

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

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

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

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

Closes gh-134
2020-11-10 16:56:29 +05:30
Joe Grandja
90fbbea126 Next Development Version 2020-11-09 15:12:23 -05:00
Joe Grandja
d6fc405bb1 Revert "Lock Dependency Versions for 0.0.3 release"
This reverts commit 6e2f2fe8a4.
2020-11-09 15:10:17 -05:00
Joe Grandja
db4dd3f08e Release 0.0.3 2020-11-09 14:51:23 -05:00
Joe Grandja
6e2f2fe8a4 Lock Dependency Versions for 0.0.3 release 2020-11-09 14:49:27 -05:00
Joe Grandja
061d9f8c18 Remove spring-security.version from sample 2020-11-09 14:18:36 -05:00
Joe Grandja
59f77d034e Downgrade springSecurityVersion to 5.4.+ 2020-11-09 14:11:00 -05:00
Joe Grandja
bf24cfb19e Add temporary OAuth2RefreshToken2
Issue https://github.com/spring-projects/spring-security/pull/9146
2020-11-09 14:09:34 -05:00
Joe Grandja
77a9b2ebf3 Add temporary OAuth2ErrorCodes2
Issue https://github.com/spring-projects/spring-security/issues/9184
2020-11-09 14:09:34 -05:00
Joe Grandja
d76d209124 Add temporary OAuth2ParameterNames2
Issue https://github.com/spring-projects/spring-security/issues/9183
2020-11-09 14:09:34 -05:00
Joe Grandja
58ad2d2c6c Revert "Set springSecurityVersion to 5.5.0-M1"
This reverts commit 7e2264204b.
2020-11-09 06:52:19 -05:00
Joe Grandja
1a6b3e3e59 Revert "Set springBootVersion to 2.4.0-RC1"
This reverts commit 43b44a1f77.
2020-11-09 05:57:02 -05:00
Joe Grandja
e61639bf7f Set spring-security.version to 5.5.0-M1 in sample 2020-11-09 05:42:27 -05:00
Joe Grandja
7e2264204b Set springSecurityVersion to 5.5.0-M1 2020-11-09 05:30:35 -05:00
Joe Grandja
43b44a1f77 Set springBootVersion to 2.4.0-RC1 2020-11-09 05:11:14 -05:00
Joe Grandja
19d6e97372 Revert "Use reactor-netty-http for snapshot build"
Issue https://github.com/spring-projects/spring-security/issues/8909
2020-11-09 05:10:37 -05:00
Joe Grandja
cff7b786de Set reactorVersion to 2020.0.+ 2020-11-09 05:07:48 -05:00
Joe Grandja
edf23562cb Set springVersion to 5.3.+ 2020-11-09 05:06:45 -05:00
Joe Grandja
e7909d0cdd Update javadoc OAuth2TokenEndpointFilter 2020-11-05 16:57:51 -05:00
Joe Grandja
e49d4a79b4 Polish PublicClientAuthenticationConverter
Commit 5c31fb1b7e
2020-11-05 15:54:24 -05:00
Joe Grandja
7720e275e4 Polish OAuth2ClientAuthenticationProvider
Commit 5c31fb1b7e
2020-11-05 15:23:50 -05:00
Joe Grandja
6a2c841d06 Update OAuth2TokenMetadata.TOKEN_METADATA_BASE
Issue gh-137
2020-11-04 15:39:55 -05:00
Joe Grandja
d7fe79d0ec Update TokenSettings.TOKEN_SETTING_BASE
Issue gh-117
2020-11-04 15:38:48 -05:00
Joe Grandja
40ca7a4654 Update ClientSettings.CLIENT_SETTING_BASE
Issue gh-117
2020-11-04 15:38:05 -05:00
Joe Grandja
06bf391bfa Update javadoc InMemoryOAuth2AuthorizationService
Issue gh-43
2020-11-04 14:49:55 -05:00
Joe Grandja
bfb5432b46 Update javadoc InMemoryRegisteredClientRepository
Issue gh-40
2020-11-04 14:49:55 -05:00
Joe Grandja
9818618ea3 Reuse client authentication assertion
Closes gh-144
2020-11-04 09:28:55 -05:00
Joe Grandja
cb09aef605 Use OAuth2ErrorCodes.UNSUPPORTED_TOKEN_TYPE
Issue gh-83
2020-11-04 07:36:37 -05:00
Joe Grandja
ebcdf7989d Use OAuth2ParameterNames.TOKEN
Issue gh-83
2020-11-03 20:51:46 -05:00
Joe Grandja
df8793c902 Polish tests gh-84 2020-11-02 18:55:43 -05:00
Joe Grandja
6c7486429c Polish gh-88 2020-11-02 18:49:35 -05:00
Joe Grandja
cf82c06502 Polish tests gh-128 2020-11-02 18:43:30 -05:00
Joe Grandja
a2167a5091 Polish gh-128 2020-10-30 11:27:27 -04:00
Alexey Nesterov
78d4bd0bad Add Refresh Token grant type support
Closes gh-50
2020-10-30 11:26:51 -04:00
Alexey Nesterov
1ce77d3caa Bump Spring Security to 5.5 for sample app 2020-10-29 11:16:28 +00:00
Joe Grandja
b7ddb837d6 Polish gh-84 2020-10-28 16:03:17 -04:00
Vivek Babu
dc94e5e161 Implement Token Revocation Endpoint
Closes gh-83
2020-10-24 18:45:46 -04:00
Joe Grandja
18f8b3afaa Enforce one-time use for authorization code
Closes gh-138
2020-10-22 19:43:09 -04:00
Joe Grandja
601640e4fa Set springSecurityVersion to 5.5.+ 2020-10-22 13:41:34 -04:00
Joe Grandja
af60f3d4d0 Introduce OAuth2Tokens
Closes gh-137
2020-10-20 14:43:59 -04:00
Joe Grandja
7b1b965c08 Next Development Version 2020-10-15 05:01:05 -04:00
Joe Grandja
0c6b1251ce Revert "Lock Dependency Versions for 0.0.2 release"
This reverts commit 5471c94615.
2020-10-15 04:59:49 -04:00
268 changed files with 17022 additions and 5045 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:

5
Jenkinsfile vendored
View File

@@ -32,13 +32,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 check -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --stacktrace"
}
}
} catch(Exception e) {

View File

@@ -1,6 +1,6 @@
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-experimental/spring-authorization-server/workflows/CI/badge.svg?branch=main["Build Status", link="https://github.com/spring-projects-experimental/spring-authorization-server/actions?query=workflow%3ACI"]
= Spring Authorization Server
@@ -17,16 +17,18 @@ This project uses https://www.zenhub.com/[ZenHub] to prioritize the feature road
The project board can be accessed https://app.zenhub.com/workspaces/authorization-server-5e8f3182b5e8f5841bfc4902/board?repos=248032165[here].
It is recommended to install the ZenHub https://www.zenhub.com/extension[browser extension] as it integrates natively within GitHub's user interface.
The completed and upcoming feature list can be viewed in the https://github.com/spring-projects-experimental/spring-authorization-server/wiki/Feature-List[wiki].
== 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 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.
@@ -43,7 +45,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.

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.37'
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
classpath 'io.spring.nohttp:nohttp-gradle:0.0.5.RELEASE'
}
repositories {
maven { url 'https://repo.spring.io/plugins-snapshot' }
maven {
url = 'https://repo.spring.io/plugins-snapshot'
if (project.hasProperty('artifactoryUsername')) {
credentials {
username "$artifactoryUsername"
password "$artifactoryPassword"
}
}
}
maven { url 'https://plugins.gradle.org/m2/' }
}
}

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.17.2
org.hamcrest:hamcrest-core:1.3
org.mockito:mockito-core:3.5.13
org.objenesis:objenesis:3.1
org.springframework:spring-core:5.2.9.RELEASE
org.springframework:spring-jcl:5.2.9.RELEASE
org.springframework:spring-test:5.2.9.RELEASE

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.17.2
org.hamcrest:hamcrest-core:1.3
org.mockito:mockito-core:3.5.13
org.objenesis:objenesis:3.1
org.springframework:spring-core:5.2.9.RELEASE
org.springframework:spring-jcl:5.2.9.RELEASE
org.springframework:spring-test:5.2.9.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,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.17.2
org.hamcrest:hamcrest-core:1.3
org.mockito:mockito-core:3.5.13
org.objenesis:objenesis:3.1
org.springframework:spring-core:5.2.9.RELEASE
org.springframework:spring-jcl:5.2.9.RELEASE
org.springframework:spring-test:5.2.9.RELEASE

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.17.2
org.hamcrest:hamcrest-core:1.3
org.mockito:mockito-core:3.5.13
org.objenesis:objenesis:3.1
org.springframework:spring-core:5.2.9.RELEASE
org.springframework:spring-jcl:5.2.9.RELEASE
org.springframework:spring-test:5.2.9.RELEASE

View File

@@ -14,7 +14,7 @@ asciidoctor {
}
asciidoctorj {
def ghTag = snapshotBuild ? 'master' : project.version
def ghTag = snapshotBuild ? 'main' : project.version
def ghUrl = "https://github.com/spring-projects-experimental/spring-authorization-server/tree/$ghTag"
attributes 'spring-authorization-server-version' : project.version,
'spring-boot-version' : springBootVersion,
@@ -31,3 +31,7 @@ def resolvedVersions(Configuration configuration) {
.collectEntries { [(it.name + "-version"): it.moduleVersion.id.version] }
}
}
repositories {
maven { url "https://repo.spring.io/release" }
}

View File

@@ -1,5 +1,5 @@
version=0.0.2
springBootVersion=2.4.0-M3
version=0.1.1
springBootVersion=2.4.5
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.2.+"
ext.springVersion = "5.3.6"
}
if (!project.hasProperty("springSecurityVersion")) {
ext.springSecurityVersion = "5.4.+"
ext.springSecurityVersion = "5.4.6"
}
if (!project.hasProperty("reactorVersion")) {
ext.reactorVersion = "Dysprosium-SR+"
ext.reactorVersion = "2020.0.6"
}
if (!project.hasProperty("locksDisabled")) {
@@ -21,47 +21,16 @@ 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.0"
}
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.+"
}
}
/*
NOTE:
The latest `reactor-netty` dependency was split into `reactor-netty-core` and `reactor-netty-http`,
which resulted in the snapshot build to fail. The below configuration fixes it.
Reference:
- https://github.com/spring-projects/spring-security/issues/8909
- https://github.com/reactor/reactor-netty/issues/739#issuecomment-667047117
*/
if (reactorVersion.startsWith('20')) {
if (reactorVersion.endsWith('SNAPSHOT') || reactorVersion.endsWith('+')) {
ext.reactorLatestVersion = "latest.integration"
} else {
ext.reactorLatestVersion = "latest.release"
}
configurations {
all {
resolutionStrategy {
eachDependency { DependencyResolveDetails details ->
if (details.requested.name == 'reactor-netty') {
details.useTarget("${details.requested.group}:reactor-netty-http:${reactorLatestVersion}")
details.because("reactor-netty is now split into reactor-netty-core and reactor-netty-http")
}
}
}
}
dependency "javax.servlet:javax.servlet-api:4.0.1"
dependency 'junit:junit:4.13.1'
dependency 'org.assertj:assertj-core:3.18.1'
dependency 'org.mockito:mockito-core:3.6.28'
dependency "com.squareup.okhttp3:mockwebserver:3.14.9"
dependency "com.squareup.okhttp3:okhttp:3.14.9"
dependency "com.jayway.jsonpath:json-path:2.4.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,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.0.1
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.2.9.RELEASE
org.springframework:spring-beans:5.2.9.RELEASE
org.springframework:spring-context:5.2.9.RELEASE
org.springframework:spring-core:5.2.9.RELEASE
org.springframework:spring-expression:5.2.9.RELEASE
org.springframework:spring-jcl:5.2.9.RELEASE
org.springframework:spring-web:5.2.9.RELEASE

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.0.1
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.2.9.RELEASE
org.springframework:spring-beans:5.2.9.RELEASE
org.springframework:spring-context:5.2.9.RELEASE
org.springframework:spring-core:5.2.9.RELEASE
org.springframework:spring-expression:5.2.9.RELEASE
org.springframework:spring-jcl:5.2.9.RELEASE
org.springframework:spring-web:5.2.9.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,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.0.1
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.2.9.RELEASE
org.springframework:spring-beans:5.2.9.RELEASE
org.springframework:spring-context:5.2.9.RELEASE
org.springframework:spring-core:5.2.9.RELEASE
org.springframework:spring-expression:5.2.9.RELEASE
org.springframework:spring-jcl:5.2.9.RELEASE
org.springframework:spring-web:5.2.9.RELEASE

View File

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

View File

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

View File

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

View File

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

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.0.1
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.2.9.RELEASE
org.springframework:spring-beans:5.2.9.RELEASE
org.springframework:spring-context:5.2.9.RELEASE
org.springframework:spring-core:5.2.9.RELEASE
org.springframework:spring-expression:5.2.9.RELEASE
org.springframework:spring-jcl:5.2.9.RELEASE
org.springframework:spring-web:5.2.9.RELEASE

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.0.1
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.2.9.RELEASE
org.springframework:spring-beans:5.2.9.RELEASE
org.springframework:spring-context:5.2.9.RELEASE
org.springframework:spring-core:5.2.9.RELEASE
org.springframework:spring-expression:5.2.9.RELEASE
org.springframework:spring-jcl:5.2.9.RELEASE
org.springframework:spring-web:5.2.9.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,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.0.1
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.17.2
org.hamcrest:hamcrest-core:1.3
org.mockito:mockito-core:3.5.13
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.2.9.RELEASE
org.springframework:spring-beans:5.2.9.RELEASE
org.springframework:spring-context:5.2.9.RELEASE
org.springframework:spring-core:5.2.9.RELEASE
org.springframework:spring-expression:5.2.9.RELEASE
org.springframework:spring-jcl:5.2.9.RELEASE
org.springframework:spring-test:5.2.9.RELEASE
org.springframework:spring-web:5.2.9.RELEASE
org.springframework:spring-webmvc:5.2.9.RELEASE

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.0.1
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.17.2
org.hamcrest:hamcrest-core:1.3
org.mockito:mockito-core:3.5.13
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.2.9.RELEASE
org.springframework:spring-beans:5.2.9.RELEASE
org.springframework:spring-context:5.2.9.RELEASE
org.springframework:spring-core:5.2.9.RELEASE
org.springframework:spring-expression:5.2.9.RELEASE
org.springframework:spring-jcl:5.2.9.RELEASE
org.springframework:spring-test:5.2.9.RELEASE
org.springframework:spring-web:5.2.9.RELEASE
org.springframework:spring-webmvc:5.2.9.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,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.0.1
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.17.2
org.hamcrest:hamcrest-core:1.3
org.mockito:mockito-core:3.5.13
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.2.9.RELEASE
org.springframework:spring-beans:5.2.9.RELEASE
org.springframework:spring-context:5.2.9.RELEASE
org.springframework:spring-core:5.2.9.RELEASE
org.springframework:spring-expression:5.2.9.RELEASE
org.springframework:spring-jcl:5.2.9.RELEASE
org.springframework:spring-test:5.2.9.RELEASE
org.springframework:spring-web:5.2.9.RELEASE
org.springframework:spring-webmvc:5.2.9.RELEASE

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.0.1
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.17.2
org.hamcrest:hamcrest-core:1.3
org.mockito:mockito-core:3.5.13
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.2.9.RELEASE
org.springframework:spring-beans:5.2.9.RELEASE
org.springframework:spring-context:5.2.9.RELEASE
org.springframework:spring-core:5.2.9.RELEASE
org.springframework:spring-expression:5.2.9.RELEASE
org.springframework:spring-jcl:5.2.9.RELEASE
org.springframework:spring-test:5.2.9.RELEASE
org.springframework:spring-web:5.2.9.RELEASE
org.springframework:spring-webmvc:5.2.9.RELEASE

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.0.1
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.17.2
org.hamcrest:hamcrest-core:1.3
org.mockito:mockito-core:3.5.13
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.2.9.RELEASE
org.springframework:spring-beans:5.2.9.RELEASE
org.springframework:spring-context:5.2.9.RELEASE
org.springframework:spring-core:5.2.9.RELEASE
org.springframework:spring-expression:5.2.9.RELEASE
org.springframework:spring-jcl:5.2.9.RELEASE
org.springframework:spring-test:5.2.9.RELEASE
org.springframework:spring-web:5.2.9.RELEASE
org.springframework:spring-webmvc:5.2.9.RELEASE

View File

@@ -5,6 +5,7 @@ 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'
@@ -20,5 +21,5 @@ dependencies {
}
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,78 @@
*/
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.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.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))
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.apply(authorizationServerConfigurer);
}
// @formatter:on
@Bean
public static JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
Set<JWSAlgorithm> jwsAlgs = new HashSet<>();
jwsAlgs.addAll(JWSAlgorithm.Family.RSA);
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);
}
}

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

@@ -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,27 +15,48 @@
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import java.net.URI;
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.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
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.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwsEncoder;
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
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.OAuth2TokenIntrospectionAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.web.JwkSetEndpointFilter;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
@@ -45,36 +66,47 @@ 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
* @since 0.0.1
* @see AbstractHttpConfigurer
* @see RegisteredClientRepository
* @see OAuth2AuthorizationService
* @see OAuth2AuthorizationEndpointFilter
* @see OAuth2TokenEndpointFilter
* @see OAuth2TokenIntrospectionEndpointFilter
* @see OAuth2TokenRevocationEndpointFilter
* @see NimbusJwkSetEndpointFilter
* @see OidcProviderConfigurationEndpointFilter
* @see OAuth2AuthorizationServerMetadataEndpointFilter
* @see OAuth2ClientAuthenticationFilter
* @see OidcClientRegistrationEndpointFilter
*/
public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBuilder<B>>
extends AbstractHttpConfigurer<OAuth2AuthorizationServerConfigurer<B>, B> {
private final 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 jwkSetEndpointMatcher = new AntPathRequestMatcher(
JwkSetEndpointFilter.DEFAULT_JWK_SET_ENDPOINT_URI, HttpMethod.GET.name());
private RequestMatcher authorizationEndpointMatcher;
private RequestMatcher tokenEndpointMatcher;
private RequestMatcher tokenIntrospectionEndpointMatcher;
private RequestMatcher tokenRevocationEndpointMatcher;
private RequestMatcher jwkSetEndpointMatcher;
private RequestMatcher oidcProviderConfigurationEndpointMatcher;
private RequestMatcher authorizationServerMetadataEndpointMatcher;
private RequestMatcher oidcClientRegistrationEndpointMatcher;
private final RequestMatcher endpointsMatcher = (request) ->
this.authorizationEndpointMatcher.matches(request) ||
this.tokenEndpointMatcher.matches(request) ||
this.tokenIntrospectionEndpointMatcher.matches(request) ||
this.tokenRevocationEndpointMatcher.matches(request) ||
this.jwkSetEndpointMatcher.matches(request) ||
this.oidcProviderConfigurationEndpointMatcher.matches(request) ||
this.authorizationServerMetadataEndpointMatcher.matches(request) ||
this.oidcClientRegistrationEndpointMatcher.matches(request);
/**
* Sets the repository of registered clients.
@@ -101,99 +133,212 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
}
/**
* Sets the key manager.
* Sets the provider settings.
*
* @param keyManager the key manager
* @param providerSettings the provider settings
* @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> providerSettings(ProviderSettings providerSettings) {
Assert.notNull(providerSettings, "providerSettings cannot be null");
this.getBuilder().setSharedObject(ProviderSettings.class, providerSettings);
return this;
}
/**
* Returns a {@code List} of {@link RequestMatcher}'s for the authorization server endpoints.
* Returns a {@link RequestMatcher} for the authorization server endpoints.
*
* @return a {@code List} of {@link RequestMatcher}'s for the authorization server endpoints
* @return a {@link RequestMatcher} for the authorization server endpoints
*/
public List<RequestMatcher> getEndpointMatchers() {
return Arrays.asList(this.authorizationEndpointMatcher,
this.tokenEndpointMatcher, this.jwkSetEndpointMatcher);
public RequestMatcher getEndpointsMatcher() {
return this.endpointsMatcher;
}
@Override
public void init(B builder) {
ProviderSettings providerSettings = getProviderSettings(builder);
validateProviderSettings(providerSettings);
initEndpointMatchers(providerSettings);
OAuth2ClientAuthenticationProvider clientAuthenticationProvider =
new OAuth2ClientAuthenticationProvider(
getRegisteredClientRepository(builder),
getAuthorizationService(builder));
PasswordEncoder passwordEncoder = getOptionalBean(builder, PasswordEncoder.class);
if (passwordEncoder != null) {
clientAuthenticationProvider.setPasswordEncoder(passwordEncoder);
}
builder.authenticationProvider(postProcess(clientAuthenticationProvider));
NimbusJwsEncoder jwtEncoder = new NimbusJwsEncoder(getKeyManager(builder));
JwtEncoder jwtEncoder = getJwtEncoder(builder);
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = getJwtCustomizer(builder);
OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider =
new OAuth2AuthorizationCodeAuthenticationProvider(
getRegisteredClientRepository(builder),
getAuthorizationService(builder),
jwtEncoder);
if (jwtCustomizer != null) {
authorizationCodeAuthenticationProvider.setJwtCustomizer(jwtCustomizer);
}
builder.authenticationProvider(postProcess(authorizationCodeAuthenticationProvider));
OAuth2RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider =
new OAuth2RefreshTokenAuthenticationProvider(
getAuthorizationService(builder),
jwtEncoder);
if (jwtCustomizer != null) {
refreshTokenAuthenticationProvider.setJwtCustomizer(jwtCustomizer);
}
builder.authenticationProvider(postProcess(refreshTokenAuthenticationProvider));
OAuth2ClientCredentialsAuthenticationProvider clientCredentialsAuthenticationProvider =
new OAuth2ClientCredentialsAuthenticationProvider(
getAuthorizationService(builder),
jwtEncoder);
if (jwtCustomizer != null) {
clientCredentialsAuthenticationProvider.setJwtCustomizer(jwtCustomizer);
}
builder.authenticationProvider(postProcess(clientCredentialsAuthenticationProvider));
OAuth2TokenIntrospectionAuthenticationProvider tokenIntrospectionAuthenticationProvider =
new OAuth2TokenIntrospectionAuthenticationProvider(
getRegisteredClientRepository(builder),
getAuthorizationService(builder));
builder.authenticationProvider(postProcess(tokenIntrospectionAuthenticationProvider));
OAuth2TokenRevocationAuthenticationProvider tokenRevocationAuthenticationProvider =
new OAuth2TokenRevocationAuthenticationProvider(
getAuthorizationService(builder));
builder.authenticationProvider(postProcess(tokenRevocationAuthenticationProvider));
// TODO Make OpenID Client Registration an "opt-in" feature
OidcClientRegistrationAuthenticationProvider oidcClientRegistrationAuthenticationProvider =
new OidcClientRegistrationAuthenticationProvider(
getRegisteredClientRepository(builder),
getAuthorizationService(builder));
builder.authenticationProvider(postProcess(oidcClientRegistrationAuthenticationProvider));
ExceptionHandlingConfigurer<B> exceptionHandling = builder.getConfigurer(ExceptionHandlingConfigurer.class);
if (exceptionHandling != null) {
// Register the default AuthenticationEntryPoint for the token endpoint
exceptionHandling.defaultAuthenticationEntryPointFor(
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), this.tokenEndpointMatcher);
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
new OrRequestMatcher(
this.tokenEndpointMatcher,
this.tokenIntrospectionEndpointMatcher,
this.tokenRevocationEndpointMatcher)
);
}
}
@Override
public void configure(B builder) {
JwkSetEndpointFilter jwkSetEndpointFilter = new JwkSetEndpointFilter(getKeyManager(builder));
ProviderSettings providerSettings = getProviderSettings(builder);
if (providerSettings.issuer() != null) {
OidcProviderConfigurationEndpointFilter oidcProviderConfigurationEndpointFilter =
new OidcProviderConfigurationEndpointFilter(providerSettings);
builder.addFilterBefore(postProcess(oidcProviderConfigurationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
OAuth2AuthorizationServerMetadataEndpointFilter authorizationServerMetadataEndpointFilter =
new OAuth2AuthorizationServerMetadataEndpointFilter(providerSettings);
builder.addFilterBefore(postProcess(authorizationServerMetadataEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
}
JWKSource<SecurityContext> jwkSource = getJwkSource(builder);
NimbusJwkSetEndpointFilter jwkSetEndpointFilter = new NimbusJwkSetEndpointFilter(
jwkSource,
providerSettings.jwkSetEndpoint());
builder.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
OAuth2ClientAuthenticationFilter clientAuthenticationFilter = new OAuth2ClientAuthenticationFilter(
authenticationManager, this.tokenEndpointMatcher);
OAuth2ClientAuthenticationFilter clientAuthenticationFilter =
new OAuth2ClientAuthenticationFilter(
authenticationManager,
new OrRequestMatcher(
this.tokenEndpointMatcher,
this.tokenIntrospectionEndpointMatcher,
this.tokenRevocationEndpointMatcher));
builder.addFilterAfter(postProcess(clientAuthenticationFilter), AbstractPreAuthenticatedProcessingFilter.class);
OAuth2AuthorizationEndpointFilter authorizationEndpointFilter =
new OAuth2AuthorizationEndpointFilter(
getRegisteredClientRepository(builder),
getAuthorizationService(builder));
getAuthorizationService(builder),
providerSettings.authorizationEndpoint());
builder.addFilterBefore(postProcess(authorizationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
OAuth2TokenEndpointFilter tokenEndpointFilter =
new OAuth2TokenEndpointFilter(
authenticationManager,
getAuthorizationService(builder));
providerSettings.tokenEndpoint());
builder.addFilterAfter(postProcess(tokenEndpointFilter), FilterSecurityInterceptor.class);
OAuth2TokenIntrospectionEndpointFilter tokenIntrospectionEndpointFilter =
new OAuth2TokenIntrospectionEndpointFilter(
authenticationManager,
providerSettings.tokenIntrospectionEndpoint());
builder.addFilterAfter(postProcess(tokenIntrospectionEndpointFilter), OAuth2TokenEndpointFilter.class);
OAuth2TokenRevocationEndpointFilter tokenRevocationEndpointFilter =
new OAuth2TokenRevocationEndpointFilter(
authenticationManager,
providerSettings.tokenRevocationEndpoint());
builder.addFilterAfter(postProcess(tokenRevocationEndpointFilter), OAuth2TokenIntrospectionEndpointFilter.class);
// TODO Make OpenID Client Registration an "opt-in" feature
OidcClientRegistrationEndpointFilter oidcClientRegistrationEndpointFilter =
new OidcClientRegistrationEndpointFilter(
authenticationManager,
providerSettings.oidcClientRegistrationEndpoint());
builder.addFilterAfter(postProcess(oidcClientRegistrationEndpointFilter), OAuth2TokenRevocationEndpointFilter.class);
}
private void initEndpointMatchers(ProviderSettings providerSettings) {
this.authorizationEndpointMatcher = new OrRequestMatcher(
new AntPathRequestMatcher(
providerSettings.authorizationEndpoint(),
HttpMethod.GET.name()),
new AntPathRequestMatcher(
providerSettings.authorizationEndpoint(),
HttpMethod.POST.name()));
this.tokenEndpointMatcher = new AntPathRequestMatcher(
providerSettings.tokenEndpoint(), HttpMethod.POST.name());
this.tokenIntrospectionEndpointMatcher = new AntPathRequestMatcher(
providerSettings.tokenIntrospectionEndpoint(), HttpMethod.POST.name());
this.tokenRevocationEndpointMatcher = new AntPathRequestMatcher(
providerSettings.tokenRevocationEndpoint(), HttpMethod.POST.name());
this.jwkSetEndpointMatcher = new AntPathRequestMatcher(
providerSettings.jwkSetEndpoint(), HttpMethod.GET.name());
this.oidcProviderConfigurationEndpointMatcher = new AntPathRequestMatcher(
OidcProviderConfigurationEndpointFilter.DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI, HttpMethod.GET.name());
this.authorizationServerMetadataEndpointMatcher = new AntPathRequestMatcher(
OAuth2AuthorizationServerMetadataEndpointFilter.DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI, HttpMethod.GET.name());
this.oidcClientRegistrationEndpointMatcher = new AntPathRequestMatcher(
providerSettings.oidcClientRegistrationEndpoint(), HttpMethod.POST.name());
}
private static void validateProviderSettings(ProviderSettings providerSettings) {
if (providerSettings.issuer() != null) {
try {
new URI(providerSettings.issuer()).toURL();
} catch (Exception ex) {
throw new IllegalArgumentException("issuer must be a valid URL", ex);
}
}
}
private static <B extends HttpSecurityBuilder<B>> RegisteredClientRepository getRegisteredClientRepository(B builder) {
RegisteredClientRepository registeredClientRepository = builder.getSharedObject(RegisteredClientRepository.class);
if (registeredClientRepository == null) {
registeredClientRepository = getRegisteredClientRepositoryBean(builder);
registeredClientRepository = getBean(builder, RegisteredClientRepository.class);
builder.setSharedObject(RegisteredClientRepository.class, registeredClientRepository);
}
return registeredClientRepository;
}
private static <B extends HttpSecurityBuilder<B>> RegisteredClientRepository getRegisteredClientRepositoryBean(B builder) {
return builder.getSharedObject(ApplicationContext.class).getBean(RegisteredClientRepository.class);
}
private static <B extends HttpSecurityBuilder<B>> OAuth2AuthorizationService getAuthorizationService(B builder) {
OAuth2AuthorizationService authorizationService = builder.getSharedObject(OAuth2AuthorizationService.class);
if (authorizationService == null) {
authorizationService = getAuthorizationServiceBean(builder);
authorizationService = getOptionalBean(builder, OAuth2AuthorizationService.class);
if (authorizationService == null) {
authorizationService = new InMemoryOAuth2AuthorizationService();
}
@@ -202,27 +347,90 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
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()));
private static <B extends HttpSecurityBuilder<B>> JwtEncoder getJwtEncoder(B builder) {
JwtEncoder jwtEncoder = builder.getSharedObject(JwtEncoder.class);
if (jwtEncoder == null) {
jwtEncoder = getOptionalBean(builder, JwtEncoder.class);
if (jwtEncoder == null) {
JWKSource<SecurityContext> jwkSource = getJwkSource(builder);
jwtEncoder = new NimbusJwsEncoder(jwkSource);
}
builder.setSharedObject(JwtEncoder.class, jwtEncoder);
}
return (!authorizationServiceMap.isEmpty() ? authorizationServiceMap.values().iterator().next() : null);
return jwtEncoder;
}
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);
@SuppressWarnings("unchecked")
private 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 = getBean(builder, type);
builder.setSharedObject(JWKSource.class, jwkSource);
}
return keyManager;
return jwkSource;
}
private static <B extends HttpSecurityBuilder<B>> KeyManager getKeyManagerBean(B builder) {
return builder.getSharedObject(ApplicationContext.class).getBean(KeyManager.class);
@SuppressWarnings("unchecked")
private static <B extends HttpSecurityBuilder<B>> OAuth2TokenCustomizer<JwtEncodingContext> getJwtCustomizer(B builder) {
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = builder.getSharedObject(OAuth2TokenCustomizer.class);
if (jwtCustomizer == null) {
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, JwtEncodingContext.class);
jwtCustomizer = getOptionalBean(builder, type);
if (jwtCustomizer != null) {
builder.setSharedObject(OAuth2TokenCustomizer.class, jwtCustomizer);
}
}
return jwtCustomizer;
}
private static <B extends HttpSecurityBuilder<B>> ProviderSettings getProviderSettings(B builder) {
ProviderSettings providerSettings = builder.getSharedObject(ProviderSettings.class);
if (providerSettings == null) {
providerSettings = getOptionalBean(builder, ProviderSettings.class);
if (providerSettings == null) {
providerSettings = new ProviderSettings();
}
builder.setSharedObject(ProviderSettings.class, providerSettings);
}
return providerSettings;
}
private static <B extends HttpSecurityBuilder<B>, T> T getBean(B builder, Class<T> type) {
return builder.getSharedObject(ApplicationContext.class).getBean(type);
}
@SuppressWarnings("unchecked")
private 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);
}
private 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")
private 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

@@ -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-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.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);
}
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,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

@@ -0,0 +1,30 @@
/*
* 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;
/**
* 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/issues/9184">Issue gh-9184</a>
*/
public interface OAuth2ErrorCodes2 extends OAuth2ErrorCodes {
String UNSUPPORTED_TOKEN_TYPE = "unsupported_token_type";
}

View File

@@ -0,0 +1,40 @@
/*
* 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,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,148 @@
/*
* 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>
*/
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> getScope() {
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,102 @@
/*
* 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
*/
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.security.oauth2.server.authorization.Version;
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 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-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,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,8 +23,8 @@ 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 PATCH = 2;
private static final int MINOR = 1;
private static final int PATCH = 1;
/**
* Global Serialization value for Spring Security Authorization Server classes.

View File

@@ -0,0 +1,47 @@
/*
* 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 java.util.Map;
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 {
@Nullable
<V> V get(Object key);
@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;
}
boolean hasKey(Object key);
static Context of(Map<Object, Object> context) {
return new DefaultContext(context);
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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 java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* @author Joe Grandja
* @since 0.1.0
*/
final class DefaultContext implements Context {
private final Map<Object, Object> context;
DefaultContext(Map<Object, Object> context) {
Assert.notNull(context, "context cannot be null");
this.context = Collections.unmodifiableMap(new HashMap<>(context));
}
@SuppressWarnings("unchecked")
@Override
@Nullable
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);
}
}

View File

@@ -0,0 +1,32 @@
/*
* 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.endpoint;
/**
* 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/issues/9183">Issue gh-9183</a>
*/
public interface OAuth2ParameterNames2 extends OAuth2ParameterNames {
String TOKEN = "token";
String TOKEN_TYPE_HINT = "token_type_hint";
}

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.getScope())) {
responseClaims.put(OAuth2TokenIntrospectionClaimNames.SCOPE, StringUtils.collectionToDelimitedString(source.getScope(), " "));
}
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,137 @@
/*
* 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.time.Instant;
import java.util.List;
import org.springframework.security.oauth2.core.ClaimAccessor;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
/**
* 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 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 {@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);
}
}

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.oidc;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
/**
* 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 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 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";
}

View File

@@ -0,0 +1,355 @@
/*
* 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.Version;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
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);
}
/**
* 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 {@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 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");
((List<?>) this.claims.get(OidcClientMetadataClaimNames.REDIRECT_URIS)).forEach(
url -> validateURL(url, "redirect_uri must be a valid URL")
);
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");
}
}
@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,167 @@
/*
* 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.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;
}
/**
* 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");
}
@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,59 @@
/*
* 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.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);
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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.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";
}

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,209 @@
/*
* 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.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 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));
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.GRANT_TYPES, collectionStringConverter);
claimConverters.put(OidcClientMetadataClaimNames.RESPONSE_TYPES, collectionStringConverter);
claimConverters.put(OidcClientMetadataClaimNames.SCOPE, MapOidcClientRegistrationConverter::convertScope);
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,161 @@
/*
* 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.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.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

@@ -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,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.jws.JwsAlgorithm;
import org.springframework.util.Assert;
/**
* The JOSE header is a JSON object representing the header parameters of a JSON Web Token,
@@ -55,16 +44,16 @@ 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 JwsAlgorithm JWS algorithm} used to digitally sign the JWS.
*
* @return the JWS algorithm
*/
public JwsAlgorithm getJwsAlgorithm() {
return getHeader(ALG);
return getHeader(JoseHeaderNames.ALG);
}
/**
@@ -73,8 +62,8 @@ public final class JoseHeader {
*
* @return the JWK Set URL
*/
public String getJwkSetUri() {
return getHeader(JKU);
public URL getJwkSetUri() {
return getHeader(JoseHeaderNames.JKU);
}
/**
@@ -84,7 +73,7 @@ public final class JoseHeader {
* @return the JSON Web Key
*/
public Map<String, Object> getJwk() {
return getHeader(JWK);
return getHeader(JoseHeaderNames.JWK);
}
/**
@@ -93,7 +82,7 @@ public final class JoseHeader {
* @return the key ID
*/
public String getKeyId() {
return getHeader(KID);
return getHeader(JoseHeaderNames.KID);
}
/**
@@ -102,8 +91,8 @@ public final class JoseHeader {
*
* @return the X.509 URL
*/
public String getX509Uri() {
return getHeader(X5U);
public URL getX509Uri() {
return getHeader(JoseHeaderNames.X5U);
}
/**
@@ -113,7 +102,7 @@ public final class JoseHeader {
* @return the X.509 certificate chain
*/
public List<String> getX509CertificateChain() {
return getHeader(X5C);
return getHeader(JoseHeaderNames.X5C);
}
/**
@@ -123,7 +112,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 +122,7 @@ public final class JoseHeader {
* @return the X.509 certificate SHA-256 thumbprint
*/
public String getX509SHA256Thumbprint() {
return getHeader(X5T_S256);
return getHeader(JoseHeaderNames.X5T_S256);
}
/**
@@ -143,7 +132,7 @@ public final class JoseHeader {
* @return the critical headers
*/
public Set<String> getCritical() {
return getHeader(CRIT);
return getHeader(JoseHeaderNames.CRIT);
}
/**
@@ -152,7 +141,7 @@ public final class JoseHeader {
* @return the type header
*/
public String getType() {
return getHeader(TYP);
return getHeader(JoseHeaderNames.TYP);
}
/**
@@ -161,7 +150,7 @@ public final class JoseHeader {
* @return the content type header
*/
public String getContentType() {
return getHeader(CTY);
return getHeader(JoseHeaderNames.CTY);
}
/**
@@ -186,6 +175,15 @@ public final class JoseHeader {
return (T) getHeaders().get(name);
}
/**
* Returns a new {@link Builder}.
*
* @return the {@link Builder}
*/
public static Builder builder() {
return new Builder();
}
/**
* Returns a new {@link Builder}, initialized with the provided {@link JwsAlgorithm}.
*
@@ -209,12 +207,15 @@ 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() {
}
private Builder(JwsAlgorithm jwsAlgorithm) {
Assert.notNull(jwsAlgorithm, "jwsAlgorithm cannot be null");
header(ALG, jwsAlgorithm);
header(JoseHeaderNames.ALG, jwsAlgorithm);
}
private Builder(JoseHeader headers) {
@@ -222,6 +223,16 @@ public final class JoseHeader {
this.headers.putAll(headers.getHeaders());
}
/**
* Sets the {@link JwsAlgorithm JWS algorithm} used to digitally sign the JWS.
*
* @param jwsAlgorithm the JWS algorithm
* @return the {@link Builder}
*/
public Builder jwsAlgorithm(JwsAlgorithm jwsAlgorithm) {
return header(JoseHeaderNames.ALG, jwsAlgorithm);
}
/**
* 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.
@@ -230,7 +241,7 @@ public final class JoseHeader {
* @return the {@link Builder}
*/
public Builder jwkSetUri(String jwkSetUri) {
return header(JKU, jwkSetUri);
return header(JoseHeaderNames.JKU, jwkSetUri);
}
/**
@@ -241,7 +252,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,7 +262,7 @@ public final class JoseHeader {
* @return the {@link Builder}
*/
public Builder keyId(String keyId) {
return header(KID, keyId);
return header(JoseHeaderNames.KID, keyId);
}
/**
@@ -262,7 +273,7 @@ public final class JoseHeader {
* @return the {@link Builder}
*/
public Builder x509Uri(String x509Uri) {
return header(X5U, x509Uri);
return header(JoseHeaderNames.X5U, x509Uri);
}
/**
@@ -273,7 +284,7 @@ public final class JoseHeader {
* @return the {@link Builder}
*/
public Builder x509CertificateChain(List<String> x509CertificateChain) {
return header(X5C, x509CertificateChain);
return header(JoseHeaderNames.X5C, x509CertificateChain);
}
/**
@@ -284,7 +295,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 +306,7 @@ public final class JoseHeader {
* @return the {@link Builder}
*/
public Builder x509SHA256Thumbprint(String x509SHA256Thumbprint) {
return header(X5T_S256, x509SHA256Thumbprint);
return header(JoseHeaderNames.X5T_S256, x509SHA256Thumbprint);
}
/**
@@ -306,7 +317,7 @@ public final class JoseHeader {
* @return the {@link Builder}
*/
public Builder critical(Set<String> headerNames) {
return header(CRIT, headerNames);
return header(JoseHeaderNames.CRIT, headerNames);
}
/**
@@ -316,7 +327,7 @@ public final class JoseHeader {
* @return the {@link Builder}
*/
public Builder type(String type) {
return header(TYP, type);
return header(JoseHeaderNames.TYP, type);
}
/**
@@ -326,7 +337,7 @@ public final class JoseHeader {
* @return the {@link Builder}
*/
public Builder contentType(String contentType) {
return header(CTY, contentType);
return header(JoseHeaderNames.CTY, contentType);
}
/**
@@ -362,7 +373,19 @@ public final class JoseHeader {
*/
public JoseHeader build() {
Assert.notEmpty(this.headers, "headers cannot be empty");
convertAsURL(JoseHeaderNames.JKU);
convertAsURL(JoseHeaderNames.X5U);
return new JoseHeader(this.headers);
}
private void convertAsURL(String header) {
Object value = this.headers.get(header);
if (value != null) {
URL convertedValue = ClaimConversionService.getSharedInstance().convert(value, URL.class);
Assert.isTrue(convertedValue != null,
() -> "Unable to convert header '" + header + "' of type '" + value.getClass() + "' to URL.");
this.headers.put(header, convertedValue);
}
}
}
}

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,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.jose;
package org.springframework.security.oauth2.jwt;
/**
* The Registered Header Parameter Names defined by the JSON Web Token (JWT),
@@ -28,69 +28,72 @@ package org.springframework.security.oauth2.jose;
* @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>
*/
public interface JoseHeaderNames {
public final class JoseHeaderNames {
/**
* {@code alg} - the algorithm header identifies the cryptographic algorithm used to secure a JWS or JWE
*/
String ALG = "alg";
public static final String ALG = "alg";
/**
* {@code jku} - the JWK Set URL header is a URI that refers to a resource for a set of JSON-encoded public keys,
* one of which corresponds to the key used to digitally sign a JWS or encrypt a JWE
*/
String JKU = "jku";
public static final String JKU = "jku";
/**
* {@code jwk} - the JSON Web Key header is the public key that corresponds to the key
* used to digitally sign a JWS or encrypt a JWE
*/
String JWK = "jwk";
public static final String JWK = "jwk";
/**
* {@code kid} - the key ID header is a hint indicating which key was used to secure a JWS or JWE
*/
String KID = "kid";
public static final String KID = "kid";
/**
* {@code x5u} - the X.509 URL header is a URI that refers to a resource for the X.509 public key certificate
* or certificate chain corresponding to the key used to digitally sign a JWS or encrypt a JWE
*/
String X5U = "x5u";
public static final String X5U = "x5u";
/**
* {@code x5c} - the X.509 certificate chain header contains the X.509 public key certificate
* or certificate chain corresponding to the key used to digitally sign a JWS or encrypt a JWE
*/
String X5C = "x5c";
public static final String X5C = "x5c";
/**
* {@code x5t} - the X.509 certificate SHA-1 thumbprint header is a base64url-encoded SHA-1 thumbprint (a.k.a. digest)
* of the DER encoding of the X.509 certificate corresponding to the key used to digitally sign a JWS or encrypt a JWE
*/
String X5T = "x5t";
public static final String X5T = "x5t";
/**
* {@code x5t#S256} - the X.509 certificate SHA-256 thumbprint header is a base64url-encoded SHA-256 thumbprint (a.k.a. digest)
* of the DER encoding of the X.509 certificate corresponding to the key used to digitally sign a JWS or encrypt a JWE
*/
String X5T_S256 = "x5t#S256";
public static final String X5T_S256 = "x5t#S256";
/**
* {@code typ} - the type header is used by JWS/JWE applications to declare the media type of a JWS/JWE
*/
String TYP = "typ";
public static final String TYP = "typ";
/**
* {@code cty} - the content type header is used by JWS/JWE applications to declare the media type
* of the secured content (the payload)
*/
String CTY = "cty";
public static final String CTY = "cty";
/**
* {@code crit} - the critical header indicates that extensions to the JWS/JWE/JWA specifications
* are being used that MUST be understood and processed
*/
String CRIT = "crit";
public static final String CRIT = "crit";
private JoseHeaderNames() {
}
}

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,14 @@
*/
package org.springframework.security.oauth2.jwt;
import org.springframework.util.Assert;
import java.net.URL;
import java.time.Instant;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import static org.springframework.security.oauth2.jwt.JwtClaimNames.AUD;
import static org.springframework.security.oauth2.jwt.JwtClaimNames.EXP;
import static org.springframework.security.oauth2.jwt.JwtClaimNames.IAT;
import static org.springframework.security.oauth2.jwt.JwtClaimNames.ISS;
import static org.springframework.security.oauth2.jwt.JwtClaimNames.JTI;
import static org.springframework.security.oauth2.jwt.JwtClaimNames.NBF;
import static org.springframework.security.oauth2.jwt.JwtClaimNames.SUB;
import org.springframework.util.Assert;
/**
* The {@link Jwt JWT} Claims Set is a JSON object representing the claims conveyed by a JSON Web Token.
@@ -47,7 +38,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
private final Map<String, Object> claims;
private JwtClaimsSet(Map<String, Object> claims) {
this.claims = Collections.unmodifiableMap(new LinkedHashMap<>(claims));
this.claims = Collections.unmodifiableMap(new HashMap<>(claims));
}
@Override
@@ -60,7 +51,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
*
* @return the {@link Builder}
*/
public static Builder withClaims() {
public static Builder builder() {
return new Builder();
}
@@ -77,8 +68,8 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
/**
* A builder for {@link JwtClaimsSet}.
*/
public static class Builder {
private final Map<String, Object> claims = new LinkedHashMap<>();
public static final class Builder {
private final Map<String, Object> claims = new HashMap<>();
private Builder() {
}
@@ -94,8 +85,8 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
* @param issuer the issuer identifier
* @return the {@link Builder}
*/
public Builder issuer(URL issuer) {
return claim(ISS, issuer);
public Builder issuer(String issuer) {
return claim(JwtClaimNames.ISS, issuer);
}
/**
@@ -105,7 +96,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
* @return the {@link Builder}
*/
public Builder subject(String subject) {
return claim(SUB, subject);
return claim(JwtClaimNames.SUB, subject);
}
/**
@@ -115,7 +106,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
* @return the {@link Builder}
*/
public Builder audience(List<String> audience) {
return claim(AUD, audience);
return claim(JwtClaimNames.AUD, audience);
}
/**
@@ -126,7 +117,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
* @return the {@link Builder}
*/
public Builder expiresAt(Instant expiresAt) {
return claim(EXP, expiresAt);
return claim(JwtClaimNames.EXP, expiresAt);
}
/**
@@ -137,7 +128,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
* @return the {@link Builder}
*/
public Builder notBefore(Instant notBefore) {
return claim(NBF, notBefore);
return claim(JwtClaimNames.NBF, notBefore);
}
/**
@@ -147,7 +138,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
* @return the {@link Builder}
*/
public Builder issuedAt(Instant issuedAt) {
return claim(IAT, issuedAt);
return claim(JwtClaimNames.IAT, issuedAt);
}
/**
@@ -157,7 +148,7 @@ public final class JwtClaimsSet implements JwtClaimAccessor {
* @return the {@link Builder}
*/
public Builder id(String jti) {
return claim(JTI, jti);
return claim(JwtClaimNames.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.
@@ -15,8 +15,6 @@
*/
package org.springframework.security.oauth2.jwt;
import org.springframework.security.oauth2.jose.JoseHeader;
/**
* Implementations of this interface are responsible for encoding
* a JSON Web Token (JWT) to it's compact claims representation format.

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,52 +13,47 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.jose.jws;
package org.springframework.security.oauth2.jwt;
import java.net.URL;
import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
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.KeySourceException;
import com.nimbusds.jose.crypto.factories.DefaultJWSSignerFactory;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKMatcher;
import com.nimbusds.jose.jwk.JWKSelector;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jose.produce.JWSSignerFactory;
import com.nimbusds.jose.util.Base64;
import com.nimbusds.jose.util.Base64URL;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import net.minidev.json.JSONObject;
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.
* 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 supplied by the {@code com.nimbusds.jose.jwk.source.JWKSource}
* provided via the constructor.
*
* <p>
* <b>NOTE:</b> This implementation uses the Nimbus JOSE + JWT SDK.
@@ -66,42 +61,38 @@ import java.util.stream.Collectors;
* @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>
* @see com.nimbusds.jose.jwk.source.JWKSource
* @see com.nimbusds.jose.jwk.JWK
* @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;
private static final String ENCODING_ERROR_MESSAGE_TEMPLATE = "An error occurred while attempting to encode the Jwt: %s";
private static final Converter<JoseHeader, JWSHeader> JWS_HEADER_CONVERTER = new JwsHeaderConverter();
private static final Converter<JwtClaimsSet, JWTClaimsSet> JWT_CLAIMS_SET_CONVERTER = new JwtClaimsSetConverter();
private static final JWSSignerFactory JWS_SIGNER_FACTORY = new DefaultJWSSignerFactory();
private final Map<JWK, JWSSigner> jwsSigners = new ConcurrentHashMap<>();
private final JWKSource<SecurityContext> jwkSource;
/**
* Constructs a {@code NimbusJwsEncoder} using the provided parameters.
*
* @param keyManager the key manager
* @param jwkSource the {@code com.nimbusds.jose.jwk.source.JWKSource}
*/
public NimbusJwsEncoder(KeyManager keyManager) {
Assert.notNull(keyManager, "keyManager cannot be null");
this.keyManager = keyManager;
public NimbusJwsEncoder(JWKSource<SecurityContext> jwkSource) {
Assert.notNull(jwkSource, "jwkSource cannot be null");
this.jwkSource = jwkSource;
}
@Override
@@ -109,84 +100,79 @@ public final class NimbusJwsEncoder implements JwtEncoder {
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);
}
JWK jwk = selectJwk(headers);
if (jwk == null) {
throw new JwtEncodingException(
String.format(ENCODING_ERROR_MESSAGE_TEMPLATE, "Failed to select a JWK signing key"));
}
else if (!StringUtils.hasText(jwk.getKeyID())) {
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
"The \"kid\" (key ID) from the selected JWK cannot be empty"));
}
// @formatter:off
headers = JoseHeader.from(headers)
.type(JOSEObjectType.JWT.getType())
.keyId(managedKey.getKeyId())
.keyId(jwk.getKeyID())
.build();
JWSHeader jwsHeader = jwsHeaderConverter.convert(headers);
claims = JwtClaimsSet.from(claims)
.id(UUID.randomUUID().toString())
.build();
JWTClaimsSet jwtClaimsSet = jwtClaimsSetConverter.convert(claims);
// @formatter:on
SignedJWT signedJWT = new SignedJWT(jwsHeader, jwtClaimsSet);
JWSHeader jwsHeader = JWS_HEADER_CONVERTER.convert(headers);
JWTClaimsSet jwtClaimsSet = JWT_CLAIMS_SET_CONVERTER.convert(claims);
JWSSigner jwsSigner = this.jwsSigners.computeIfAbsent(jwk, (key) -> {
try {
return JWS_SIGNER_FACTORY.createJWSSigner(key);
}
catch (JOSEException ex) {
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
"Failed to create a JWS Signer -> " + ex.getMessage()), ex);
}
});
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);
signedJwt.sign(jwsSigner);
}
String jws = signedJWT.serialize();
catch (JOSEException ex) {
throw new JwtEncodingException(
String.format(ENCODING_ERROR_MESSAGE_TEMPLATE, "Failed to sign the JWT -> " + ex.getMessage()), ex);
}
String jws = signedJwt.serialize();
return new Jwt(jws, claims.getIssuedAt(), claims.getExpiresAt(),
headers.getHeaders(), claims.getClaims());
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;
private JWK selectJwk(JoseHeader headers) {
JWSAlgorithm jwsAlgorithm = JWSAlgorithm.parse(headers.getJwsAlgorithm().getName());
JWSHeader jwsHeader = new JWSHeader(jwsAlgorithm);
JWKSelector jwkSelector = new JWKSelector(JWKMatcher.forJWSHeader(jwsHeader));
List<JWK> jwks;
try {
jwks = this.jwkSource.get(jwkSelector, null);
}
catch (KeySourceException ex) {
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
"Failed to select a JWK signing key -> " + ex.getMessage()), ex);
}
Set<ManagedKey> matchingKeys = this.keyManager.findByAlgorithm(keyAlgorithm);
if (CollectionUtils.isEmpty(matchingKeys)) {
return null;
if (jwks.size() > 1) {
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
"Found multiple JWK signing keys for algorithm '" + jwsAlgorithm.getName() + "'"));
}
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;
return !jwks.isEmpty() ? jwks.get(0) : null;
}
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()));
JWSHeader.Builder builder = new JWSHeader.Builder(JWSAlgorithm.parse(headers.getJwsAlgorithm().getName()));
Set<String> critical = headers.getCritical();
if (!CollectionUtils.isEmpty(critical)) {
@@ -198,24 +184,24 @@ public final class NimbusJwsEncoder implements JwtEncoder {
builder.contentType(contentType);
}
String jwkSetUri = headers.getJwkSetUri();
if (StringUtils.hasText(jwkSetUri)) {
URL jwkSetUri = headers.getJwkSetUri();
if (jwkSetUri != null) {
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);
builder.jwkURL(jwkSetUri.toURI());
}
catch (Exception ex) {
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
"Failed to convert '" + JoseHeaderNames.JKU + "' JOSE header to a URI"), 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,
builder.jwk(JWK.parse(new JSONObject(jwk)));
}
catch (Exception ex) {
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
"Failed to convert '" + JoseHeaderNames.JWK + "' JOSE header"), ex);
}
}
@@ -232,10 +218,7 @@ public final class NimbusJwsEncoder implements JwtEncoder {
List<String> x509CertificateChain = headers.getX509CertificateChain();
if (!CollectionUtils.isEmpty(x509CertificateChain)) {
builder.x509CertChain(
x509CertificateChain.stream()
.map(Base64::new)
.collect(Collectors.toList()));
builder.x509CertChain(x509CertificateChain.stream().map(Base64::new).collect(Collectors.toList()));
}
String x509SHA1Thumbprint = headers.getX509SHA1Thumbprint();
@@ -248,19 +231,19 @@ public final class NimbusJwsEncoder implements JwtEncoder {
builder.x509CertSHA256Thumbprint(new Base64URL(x509SHA256Thumbprint));
}
String x509Uri = headers.getX509Uri();
if (StringUtils.hasText(x509Uri)) {
URL x509Uri = headers.getX509Uri();
if (x509Uri != null) {
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);
builder.x509CertURL(x509Uri.toURI());
}
catch (Exception ex) {
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
"Failed to convert '" + JoseHeaderNames.X5U + "' JOSE header to a URI"), ex);
}
}
Map<String, Object> customHeaders = headers.getHeaders().entrySet().stream()
.filter(header -> !JWSHeader.getRegisteredParameterNames().contains(header.getKey()))
.filter((header) -> !JWSHeader.getRegisteredParameterNames().contains(header.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
if (!CollectionUtils.isEmpty(customHeaders)) {
builder.customParams(customHeaders);
@@ -268,6 +251,7 @@ public final class NimbusJwsEncoder implements JwtEncoder {
return builder.build();
}
}
private static class JwtClaimsSetConverter implements Converter<JwtClaimsSet, JWTClaimsSet> {
@@ -312,7 +296,7 @@ public final class NimbusJwsEncoder implements JwtEncoder {
}
Map<String, Object> customClaims = claims.getClaims().entrySet().stream()
.filter(claim -> !JWTClaimsSet.getRegisteredNames().contains(claim.getKey()))
.filter((claim) -> !JWTClaimsSet.getRegisteredNames().contains(claim.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
if (!CollectionUtils.isEmpty(customClaims)) {
customClaims.forEach(builder::claim);
@@ -320,5 +304,7 @@ public final class NimbusJwsEncoder implements JwtEncoder {
return builder.build();
}
}
}

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,44 +15,86 @@
*/
package org.springframework.security.oauth2.server.authorization;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.server.authorization.Version;
import org.springframework.util.Assert;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.util.Assert;
/**
* An {@link OAuth2AuthorizationService} that stores {@link OAuth2Authorization}'s in-memory.
*
* <p>
* <b>NOTE:</b> This implementation should ONLY be used during development/testing.
*
* @author Krisztian Toth
* @author Joe Grandja
* @since 0.0.1
* @see OAuth2AuthorizationService
*/
public final class InMemoryOAuth2AuthorizationService implements OAuth2AuthorizationService {
private final Map<OAuth2AuthorizationId, OAuth2Authorization> authorizations = new ConcurrentHashMap<>();
private final Map<String, OAuth2Authorization> authorizations = new ConcurrentHashMap<>();
/**
* Constructs an {@code InMemoryOAuth2AuthorizationService}.
*/
public InMemoryOAuth2AuthorizationService() {
this(Collections.emptyList());
}
/**
* Constructs an {@code InMemoryOAuth2AuthorizationService} using the provided parameters.
*
* @param authorizations the authorization(s)
*/
public InMemoryOAuth2AuthorizationService(OAuth2Authorization... authorizations) {
this(Arrays.asList(authorizations));
}
/**
* Constructs an {@code InMemoryOAuth2AuthorizationService} using the provided parameters.
*
* @param authorizations the authorization(s)
*/
public InMemoryOAuth2AuthorizationService(List<OAuth2Authorization> authorizations) {
Assert.notNull(authorizations, "authorizations cannot be null");
authorizations.forEach(authorization -> {
Assert.notNull(authorization, "authorization cannot be null");
Assert.isTrue(!this.authorizations.containsKey(authorization.getId()),
"The authorization must be unique. Found duplicate identifier: " + authorization.getId());
this.authorizations.put(authorization.getId(), authorization);
});
}
@Override
public void save(OAuth2Authorization authorization) {
Assert.notNull(authorization, "authorization cannot be null");
OAuth2AuthorizationId authorizationId = new OAuth2AuthorizationId(
authorization.getRegisteredClientId(), authorization.getPrincipalName());
this.authorizations.put(authorizationId, authorization);
this.authorizations.put(authorization.getId(), authorization);
}
@Override
public void remove(OAuth2Authorization authorization) {
Assert.notNull(authorization, "authorization cannot be null");
OAuth2AuthorizationId authorizationId = new OAuth2AuthorizationId(
authorization.getRegisteredClientId(), authorization.getPrincipalName());
this.authorizations.remove(authorizationId, authorization);
this.authorizations.remove(authorization.getId(), authorization);
}
@Nullable
@Override
public OAuth2Authorization findByToken(String token, @Nullable TokenType tokenType) {
public OAuth2Authorization findById(String id) {
Assert.hasText(id, "id cannot be empty");
return this.authorizations.get(id);
}
@Nullable
@Override
public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) {
Assert.hasText(token, "token cannot be empty");
return this.authorizations.values().stream()
.filter(authorization -> hasToken(authorization, token, tokenType))
@@ -60,44 +102,43 @@ public final class InMemoryOAuth2AuthorizationService implements OAuth2Authoriza
.orElse(null);
}
private boolean hasToken(OAuth2Authorization authorization, String token, TokenType tokenType) {
if (OAuth2AuthorizationAttributeNames.STATE.equals(tokenType.getValue())) {
return token.equals(authorization.getAttribute(OAuth2AuthorizationAttributeNames.STATE));
} else if (TokenType.AUTHORIZATION_CODE.equals(tokenType)) {
return token.equals(authorization.getAttribute(OAuth2AuthorizationAttributeNames.CODE));
} else if (TokenType.ACCESS_TOKEN.equals(tokenType)) {
return authorization.getAccessToken() != null &&
authorization.getAccessToken().getTokenValue().equals(token);
private static boolean hasToken(OAuth2Authorization authorization, String token, @Nullable OAuth2TokenType tokenType) {
if (tokenType == null) {
return matchesState(authorization, token) ||
matchesAuthorizationCode(authorization, token) ||
matchesAccessToken(authorization, token) ||
matchesRefreshToken(authorization, token);
} else if (OAuth2ParameterNames.STATE.equals(tokenType.getValue())) {
return matchesState(authorization, token);
} else if (OAuth2ParameterNames.CODE.equals(tokenType.getValue())) {
return matchesAuthorizationCode(authorization, token);
} else if (OAuth2TokenType.ACCESS_TOKEN.equals(tokenType)) {
return matchesAccessToken(authorization, token);
} else if (OAuth2TokenType.REFRESH_TOKEN.equals(tokenType)) {
return matchesRefreshToken(authorization, token);
}
return false;
}
private static class OAuth2AuthorizationId implements Serializable {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
private final String registeredClientId;
private final String principalName;
private static boolean matchesState(OAuth2Authorization authorization, String token) {
return token.equals(authorization.getAttribute(OAuth2ParameterNames.STATE));
}
private OAuth2AuthorizationId(String registeredClientId, String principalName) {
this.registeredClientId = registeredClientId;
this.principalName = principalName;
}
private static boolean matchesAuthorizationCode(OAuth2Authorization authorization, String token) {
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode =
authorization.getToken(OAuth2AuthorizationCode.class);
return authorizationCode != null && authorizationCode.getToken().getTokenValue().equals(token);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
OAuth2AuthorizationId that = (OAuth2AuthorizationId) obj;
return Objects.equals(this.registeredClientId, that.registeredClientId) &&
Objects.equals(this.principalName, that.principalName);
}
private static boolean matchesAccessToken(OAuth2Authorization authorization, String token) {
OAuth2Authorization.Token<OAuth2AccessToken> accessToken =
authorization.getToken(OAuth2AccessToken.class);
return accessToken != null && accessToken.getToken().getTokenValue().equals(token);
}
@Override
public int hashCode() {
return Objects.hash(this.registeredClientId, this.principalName);
}
private static boolean matchesRefreshToken(OAuth2Authorization authorization, String token) {
OAuth2Authorization.Token<OAuth2RefreshToken> refreshToken =
authorization.getToken(OAuth2RefreshToken.class);
return refreshToken != null && refreshToken.getToken().getTokenValue().equals(token);
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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.server.authorization;
import java.util.Map;
import java.util.function.Consumer;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.core.context.Context;
import org.springframework.security.oauth2.jwt.JoseHeader;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.util.Assert;
/**
* @author Joe Grandja
* @since 0.1.0
* @see OAuth2TokenContext
* @see JoseHeader.Builder
* @see JwtClaimsSet.Builder
*/
public final class JwtEncodingContext implements OAuth2TokenContext {
private final Context context;
private JwtEncodingContext(Map<Object, Object> context) {
this.context = Context.of(context);
}
@Nullable
@Override
public <V> V get(Object key) {
return this.context.get(key);
}
@Override
public boolean hasKey(Object key) {
return this.context.hasKey(key);
}
public JoseHeader.Builder getHeaders() {
return get(JoseHeader.Builder.class);
}
public JwtClaimsSet.Builder getClaims() {
return get(JwtClaimsSet.Builder.class);
}
public static Builder with(JoseHeader.Builder headersBuilder, JwtClaimsSet.Builder claimsBuilder) {
return new Builder(headersBuilder, claimsBuilder);
}
public static final class Builder extends AbstractBuilder<JwtEncodingContext, Builder> {
private Builder(JoseHeader.Builder headersBuilder, JwtClaimsSet.Builder claimsBuilder) {
Assert.notNull(headersBuilder, "headersBuilder cannot be null");
Assert.notNull(claimsBuilder, "claimsBuilder cannot be null");
put(JoseHeader.Builder.class, headersBuilder);
put(JwtClaimsSet.Builder.class, claimsBuilder);
}
public Builder headers(Consumer<JoseHeader.Builder> headersConsumer) {
headersConsumer.accept(get(JoseHeader.Builder.class));
return this;
}
public Builder claims(Consumer<JwtClaimsSet.Builder> claimsConsumer) {
claimsConsumer.accept(get(JwtClaimsSet.Builder.class));
return this;
}
public JwtEncodingContext build() {
return new JwtEncodingContext(this.context);
}
}
}

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,39 +15,70 @@
*/
package org.springframework.security.oauth2.server.authorization;
import org.springframework.security.oauth2.server.authorization.Version;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.util.Assert;
import java.io.Serializable;
import java.time.Instant;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Consumer;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.core.Version;
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken2;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* A representation of an OAuth 2.0 Authorization,
* which holds state related to the authorization granted to the {@link #getRegisteredClientId() client}
* by the {@link #getPrincipalName() resource owner}.
* A representation of an OAuth 2.0 Authorization, which holds state related to the authorization granted
* to a {@link #getRegisteredClientId() client}, by the {@link #getPrincipalName() resource owner}
* or itself in the case of the {@code client_credentials} grant type.
*
* @author Joe Grandja
* @author Krisztian Toth
* @since 0.0.1
* @see RegisteredClient
* @see AuthorizationGrantType
* @see AbstractOAuth2Token
* @see OAuth2AccessToken
* @see OAuth2RefreshToken
*/
public class OAuth2Authorization implements Serializable {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
/**
* The name of the {@link #getAttribute(String) attribute} used for the authorized scope(s).
* The value of the attribute is of type {@code Set<String>}.
*/
public static final String AUTHORIZED_SCOPE_ATTRIBUTE_NAME =
OAuth2Authorization.class.getName().concat(".AUTHORIZED_SCOPE");
private String id;
private String registeredClientId;
private String principalName;
private OAuth2AccessToken accessToken;
private AuthorizationGrantType authorizationGrantType;
private Map<Class<? extends AbstractOAuth2Token>, Token<?>> tokens;
private Map<String, Object> attributes;
protected OAuth2Authorization() {
}
/**
* Returns the identifier for the authorization.
*
* @return the identifier for the authorization
*/
public String getId() {
return this.id;
}
/**
* Returns the identifier for the {@link RegisteredClient#getId() registered client}.
*
@@ -58,21 +89,73 @@ public class OAuth2Authorization implements Serializable {
}
/**
* Returns the resource owner's {@code Principal} name.
* Returns the {@code Principal} name of the resource owner (or client).
*
* @return the resource owner's {@code Principal} name
* @return the {@code Principal} name of the resource owner (or client)
*/
public String getPrincipalName() {
return this.principalName;
}
/**
* Returns the {@link OAuth2AccessToken access token} credential.
* Returns the {@link AuthorizationGrantType authorization grant type} used for the authorization.
*
* @return the {@link OAuth2AccessToken}
* @return the {@link AuthorizationGrantType} used for the authorization
*/
public OAuth2AccessToken getAccessToken() {
return this.accessToken;
public AuthorizationGrantType getAuthorizationGrantType() {
return this.authorizationGrantType;
}
/**
* Returns the {@link Token} of type {@link OAuth2AccessToken}.
*
* @return the {@link Token} of type {@link OAuth2AccessToken}
*/
public Token<OAuth2AccessToken> getAccessToken() {
return getToken(OAuth2AccessToken.class);
}
/**
* Returns the {@link Token} of type {@link OAuth2RefreshToken}.
*
* @return the {@link Token} of type {@link OAuth2RefreshToken}, or {@code null} if not available
*/
@Nullable
public Token<OAuth2RefreshToken> getRefreshToken() {
return getToken(OAuth2RefreshToken.class);
}
/**
* Returns the {@link Token} of type {@code tokenType}.
*
* @param tokenType the token type
* @param <T> the type of the token
* @return the {@link Token}, or {@code null} if not available
*/
@Nullable
@SuppressWarnings("unchecked")
public <T extends AbstractOAuth2Token> Token<T> getToken(Class<T> tokenType) {
Assert.notNull(tokenType, "tokenType cannot be null");
Token<?> token = this.tokens.get(tokenType);
return token != null ? (Token<T>) token : null;
}
/**
* Returns the {@link Token} matching the {@code tokenValue}.
*
* @param tokenValue the token value
* @param <T> the type of the token
* @return the {@link Token}, or {@code null} if not available
*/
@Nullable
@SuppressWarnings("unchecked")
public <T extends AbstractOAuth2Token> Token<T> getToken(String tokenValue) {
Assert.hasText(tokenValue, "tokenValue cannot be empty");
Token<?> token = this.tokens.values().stream()
.filter(t -> t.getToken().getTokenValue().equals(tokenValue))
.findFirst()
.orElse(null);
return token != null ? (Token<T>) token : null;
}
/**
@@ -89,8 +172,9 @@ public class OAuth2Authorization implements Serializable {
*
* @param name the name of the attribute
* @param <T> the type of the attribute
* @return the value of the attribute associated to the authorization, or {@code null} if not available
* @return the value of an attribute associated to the authorization, or {@code null} if not available
*/
@Nullable
@SuppressWarnings("unchecked")
public <T> T getAttribute(String name) {
Assert.hasText(name, "name cannot be empty");
@@ -106,15 +190,18 @@ public class OAuth2Authorization implements Serializable {
return false;
}
OAuth2Authorization that = (OAuth2Authorization) obj;
return Objects.equals(this.registeredClientId, that.registeredClientId) &&
return Objects.equals(this.id, that.id) &&
Objects.equals(this.registeredClientId, that.registeredClientId) &&
Objects.equals(this.principalName, that.principalName) &&
Objects.equals(this.accessToken, that.accessToken) &&
Objects.equals(this.authorizationGrantType, that.authorizationGrantType) &&
Objects.equals(this.tokens, that.tokens) &&
Objects.equals(this.attributes, that.attributes);
}
@Override
public int hashCode() {
return Objects.hash(this.registeredClientId, this.principalName, this.accessToken, this.attributes);
return Objects.hash(this.id, this.registeredClientId, this.principalName,
this.authorizationGrantType, this.tokens, this.attributes);
}
/**
@@ -129,37 +216,192 @@ public class OAuth2Authorization implements Serializable {
}
/**
* Returns a new {@link Builder}, initialized with the values from the provided {@code authorization}.
* Returns a new {@link Builder}, initialized with the values from the provided {@code OAuth2Authorization}.
*
* @param authorization the authorization used for initializing the {@link Builder}
* @param authorization the {@code OAuth2Authorization} used for initializing the {@link Builder}
* @return the {@link Builder}
*/
public static Builder from(OAuth2Authorization authorization) {
Assert.notNull(authorization, "authorization cannot be null");
return new Builder(authorization.getRegisteredClientId())
.id(authorization.getId())
.principalName(authorization.getPrincipalName())
.accessToken(authorization.getAccessToken())
.authorizationGrantType(authorization.getAuthorizationGrantType())
.tokens(authorization.tokens)
.attributes(attrs -> attrs.putAll(authorization.getAttributes()));
}
/**
* A holder of an OAuth 2.0 Token and it's associated metadata.
*
* @author Joe Grandja
* @since 0.1.0
*/
public static class Token<T extends AbstractOAuth2Token> implements Serializable {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
protected static final String TOKEN_METADATA_BASE = "metadata.token.";
/**
* The name of the metadata that indicates if the token has been invalidated.
*/
public static final String INVALIDATED_METADATA_NAME = TOKEN_METADATA_BASE.concat("invalidated");
/**
* The name of the metadata used for the claims of the token.
*/
public static final String CLAIMS_METADATA_NAME = TOKEN_METADATA_BASE.concat("claims");
private final T token;
private final Map<String, Object> metadata;
protected Token(T token) {
this(token, defaultMetadata());
}
protected Token(T token, Map<String, Object> metadata) {
this.token = token;
this.metadata = Collections.unmodifiableMap(metadata);
}
/**
* Returns the token of type {@link AbstractOAuth2Token}.
*
* @return the token of type {@link AbstractOAuth2Token}
*/
public T getToken() {
return this.token;
}
/**
* Returns {@code true} if the token has been invalidated (e.g. revoked).
* The default is {@code false}.
*
* @return {@code true} if the token has been invalidated, {@code false} otherwise
*/
public boolean isInvalidated() {
return Boolean.TRUE.equals(getMetadata(INVALIDATED_METADATA_NAME));
}
/**
* Returns {@code true} if the token has expired.
*
* @return {@code true} if the token has expired, {@code false} otherwise
*/
public boolean isExpired() {
return getToken().getExpiresAt() != null && Instant.now().isAfter(getToken().getExpiresAt());
}
/**
* Returns {@code true} if the token is before the time it can be used.
*
* @return {@code true} if the token is before the time it can be used, {@code false} otherwise
*/
public boolean isBeforeUse() {
Instant notBefore = null;
if (!CollectionUtils.isEmpty(getClaims())) {
notBefore = (Instant) getClaims().get("nbf");
}
return notBefore != null && Instant.now().isBefore(notBefore);
}
/**
* Returns {@code true} if the token is currently active.
*
* @return {@code true} if the token is currently active, {@code false} otherwise
*/
public boolean isActive() {
return !isInvalidated() && !isExpired() && !isBeforeUse();
}
/**
* Returns the claims associated to the token.
*
* @return a {@code Map} of the claims, or {@code null} if not available
*/
@Nullable
public Map<String, Object> getClaims() {
return getMetadata(CLAIMS_METADATA_NAME);
}
/**
* Returns the value of the metadata associated to the token.
*
* @param name the name of the metadata
* @param <V> the value type of the metadata
* @return the value of the metadata, or {@code null} if not available
*/
@Nullable
@SuppressWarnings("unchecked")
public <V> V getMetadata(String name) {
Assert.hasText(name, "name cannot be empty");
return (V) this.metadata.get(name);
}
/**
* Returns the metadata associated to the token.
*
* @return a {@code Map} of the metadata
*/
public Map<String, Object> getMetadata() {
return this.metadata;
}
protected static Map<String, Object> defaultMetadata() {
Map<String, Object> metadata = new HashMap<>();
metadata.put(INVALIDATED_METADATA_NAME, false);
return metadata;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Token<?> that = (Token<?>) obj;
return Objects.equals(this.token, that.token) &&
Objects.equals(this.metadata, that.metadata);
}
@Override
public int hashCode() {
return Objects.hash(this.token, this.metadata);
}
}
/**
* A builder for {@link OAuth2Authorization}.
*/
public static class Builder implements Serializable {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
private String registeredClientId;
private String id;
private final String registeredClientId;
private String principalName;
private OAuth2AccessToken accessToken;
private Map<String, Object> attributes = new HashMap<>();
private AuthorizationGrantType authorizationGrantType;
private Map<Class<? extends AbstractOAuth2Token>, Token<?>> tokens = new HashMap<>();
private final Map<String, Object> attributes = new HashMap<>();
protected Builder(String registeredClientId) {
this.registeredClientId = registeredClientId;
}
/**
* Sets the resource owner's {@code Principal} name.
* Sets the identifier for the authorization.
*
* @param principalName the resource owner's {@code Principal} name
* @param id the identifier for the authorization
* @return the {@link Builder}
*/
public Builder id(String id) {
this.id = id;
return this;
}
/**
* Sets the {@code Principal} name of the resource owner (or client).
*
* @param principalName the {@code Principal} name of the resource owner (or client)
* @return the {@link Builder}
*/
public Builder principalName(String principalName) {
@@ -168,13 +410,75 @@ public class OAuth2Authorization implements Serializable {
}
/**
* Sets the {@link OAuth2AccessToken access token} credential.
* Sets the {@link AuthorizationGrantType authorization grant type} used for the authorization.
*
* @param authorizationGrantType the {@link AuthorizationGrantType}
* @return the {@link Builder}
*/
public Builder authorizationGrantType(AuthorizationGrantType authorizationGrantType) {
this.authorizationGrantType = authorizationGrantType;
return this;
}
/**
* Sets the {@link OAuth2AccessToken access token}.
*
* @param accessToken the {@link OAuth2AccessToken}
* @return the {@link Builder}
*/
public Builder accessToken(OAuth2AccessToken accessToken) {
this.accessToken = accessToken;
return token(accessToken);
}
/**
* Sets the {@link OAuth2RefreshToken refresh token}.
*
* @param refreshToken the {@link OAuth2RefreshToken}
* @return the {@link Builder}
*/
public Builder refreshToken(OAuth2RefreshToken refreshToken) {
return token(refreshToken);
}
/**
* Sets the {@link AbstractOAuth2Token token}.
*
* @param token the token
* @param <T> the type of the token
* @return the {@link Builder}
*/
public <T extends AbstractOAuth2Token> Builder token(T token) {
return token(token, (metadata) -> {});
}
/**
* Sets the {@link AbstractOAuth2Token token} and associated metadata.
*
* @param token the token
* @param metadataConsumer a {@code Consumer} of the metadata {@code Map}
* @param <T> the type of the token
* @return the {@link Builder}
*/
public <T extends AbstractOAuth2Token> Builder token(T token,
Consumer<Map<String, Object>> metadataConsumer) {
Assert.notNull(token, "token cannot be null");
Map<String, Object> metadata = Token.defaultMetadata();
Token<?> existingToken = this.tokens.get(token.getClass());
if (existingToken != null) {
metadata.putAll(existingToken.getMetadata());
}
metadataConsumer.accept(metadata);
Class<? extends AbstractOAuth2Token> tokenClass = token.getClass();
if (tokenClass.equals(OAuth2RefreshToken2.class)) {
tokenClass = OAuth2RefreshToken.class;
}
this.tokens.put(tokenClass, new Token<>(token, metadata));
return this;
}
protected final Builder tokens(Map<Class<? extends AbstractOAuth2Token>, Token<?>> tokens) {
this.tokens = new HashMap<>(tokens);
return this;
}
@@ -211,11 +515,17 @@ public class OAuth2Authorization implements Serializable {
*/
public OAuth2Authorization build() {
Assert.hasText(this.principalName, "principalName cannot be empty");
Assert.notNull(this.authorizationGrantType, "authorizationGrantType cannot be null");
OAuth2Authorization authorization = new OAuth2Authorization();
if (!StringUtils.hasText(this.id)) {
this.id = UUID.randomUUID().toString();
}
authorization.id = this.id;
authorization.registeredClientId = this.registeredClientId;
authorization.principalName = this.principalName;
authorization.accessToken = this.accessToken;
authorization.authorizationGrantType = this.authorizationGrantType;
authorization.tokens = Collections.unmodifiableMap(this.tokens);
authorization.attributes = Collections.unmodifiableMap(this.attributes);
return authorization;
}

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.oauth2.server.authorization;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
/**
* The name of the attributes that may be contained in the
* {@link OAuth2Authorization#getAttributes()} {@code Map}.
*
* @author Joe Grandja
* @since 0.0.1
* @see OAuth2Authorization#getAttributes()
*/
public interface OAuth2AuthorizationAttributeNames {
/**
* The name of the attribute used for correlating the user consent request/response.
*/
String STATE = OAuth2Authorization.class.getName().concat(".STATE");
/**
* The name of the attribute used for the {@link OAuth2ParameterNames#CODE} parameter.
*/
String CODE = OAuth2Authorization.class.getName().concat(".CODE");
/**
* The name of the attribute used for the {@link OAuth2AuthorizationRequest}.
*/
String AUTHORIZATION_REQUEST = OAuth2Authorization.class.getName().concat(".AUTHORIZATION_REQUEST");
/**
* The name of the attribute used for the authorized scope(s).
*/
String AUTHORIZED_SCOPES = OAuth2Authorization.class.getName().concat(".AUTHORIZED_SCOPES");
/**
* The name of the attribute used for the attributes/claims of the {@link OAuth2AccessToken}.
*/
String ACCESS_TOKEN_ATTRIBUTES = OAuth2Authorization.class.getName().concat(".ACCESS_TOKEN_ATTRIBUTES");
}

View File

@@ -0,0 +1,43 @@
/*
* 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.server.authorization;
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
import java.time.Instant;
/**
* An implementation of an {@link AbstractOAuth2Token}
* representing an OAuth 2.0 Authorization Code Grant.
*
* @author Joe Grandja
* @since 0.0.3
* @see AbstractOAuth2Token
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant</a>
*/
public class OAuth2AuthorizationCode extends AbstractOAuth2Token {
/**
* Constructs an {@code OAuth2AuthorizationCode} using the provided parameters.
* @param tokenValue the token value
* @param issuedAt the time at which the token was issued
* @param expiresAt the time at which the token expires
*/
public OAuth2AuthorizationCode(String tokenValue, Instant issuedAt, Instant expiresAt) {
super(tokenValue, issuedAt, expiresAt);
}
}

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.
@@ -16,6 +16,7 @@
package org.springframework.security.oauth2.server.authorization;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.core.OAuth2TokenType;
/**
* Implementations of this interface are responsible for the management
@@ -24,6 +25,7 @@ import org.springframework.lang.Nullable;
* @author Joe Grandja
* @since 0.0.1
* @see OAuth2Authorization
* @see OAuth2TokenType
*/
public interface OAuth2AuthorizationService {
@@ -41,14 +43,25 @@ public interface OAuth2AuthorizationService {
*/
void remove(OAuth2Authorization authorization);
/**
* Returns the {@link OAuth2Authorization} identified by the provided {@code id},
* or {@code null} if not found.
*
* @param id the authorization identifier
* @return the {@link OAuth2Authorization} if found, otherwise {@code null}
*/
@Nullable
OAuth2Authorization findById(String id);
/**
* Returns the {@link OAuth2Authorization} containing the provided {@code token},
* or {@code null} if not found.
*
* @param token the token credential
* @param tokenType the {@link TokenType token type}
* @param tokenType the {@link OAuth2TokenType token type}
* @return the {@link OAuth2Authorization} if found, otherwise {@code null}
*/
OAuth2Authorization findByToken(String token, @Nullable TokenType tokenType);
@Nullable
OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType);
}

View File

@@ -0,0 +1,130 @@
/*
* 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.server.authorization;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import org.springframework.lang.Nullable;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.core.context.Context;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.util.Assert;
/**
* @author Joe Grandja
* @since 0.1.0
* @see Context
*/
public interface OAuth2TokenContext extends Context {
default RegisteredClient getRegisteredClient() {
return get(RegisteredClient.class);
}
default <T extends Authentication> T getPrincipal() {
return get(AbstractBuilder.PRINCIPAL_AUTHENTICATION_KEY);
}
@Nullable
default OAuth2Authorization getAuthorization() {
return get(OAuth2Authorization.class);
}
default Set<String> getAuthorizedScopes() {
return hasKey(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME) ?
get(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME) :
Collections.emptySet();
}
default OAuth2TokenType getTokenType() {
return get(OAuth2TokenType.class);
}
default AuthorizationGrantType getAuthorizationGrantType() {
return get(AuthorizationGrantType.class);
}
default <T extends Authentication> T getAuthorizationGrant() {
return get(AbstractBuilder.AUTHORIZATION_GRANT_AUTHENTICATION_KEY);
}
abstract class AbstractBuilder<T extends OAuth2TokenContext, B extends AbstractBuilder<T, B>> {
private static final String PRINCIPAL_AUTHENTICATION_KEY =
Authentication.class.getName().concat(".PRINCIPAL");
private static final String AUTHORIZATION_GRANT_AUTHENTICATION_KEY =
Authentication.class.getName().concat(".AUTHORIZATION_GRANT");
protected final Map<Object, Object> context = new HashMap<>();
public B registeredClient(RegisteredClient registeredClient) {
return put(RegisteredClient.class, registeredClient);
}
public B principal(Authentication principal) {
return put(PRINCIPAL_AUTHENTICATION_KEY, principal);
}
public B authorization(OAuth2Authorization authorization) {
return put(OAuth2Authorization.class, authorization);
}
public B authorizedScopes(Set<String> authorizedScopes) {
return put(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME, authorizedScopes);
}
public B tokenType(OAuth2TokenType tokenType) {
return put(OAuth2TokenType.class, tokenType);
}
public B authorizationGrantType(AuthorizationGrantType authorizationGrantType) {
return put(AuthorizationGrantType.class, authorizationGrantType);
}
public B authorizationGrant(Authentication authorizationGrant) {
return put(AUTHORIZATION_GRANT_AUTHENTICATION_KEY, authorizationGrant);
}
public B put(Object key, Object value) {
Assert.notNull(key, "key cannot be null");
Assert.notNull(value, "value cannot be null");
this.context.put(key, value);
return getThis();
}
public B context(Consumer<Map<Object, Object>> contextConsumer) {
contextConsumer.accept(this.context);
return getThis();
}
@SuppressWarnings("unchecked")
protected <V> V get(Object key) {
return (V) this.context.get(key);
}
@SuppressWarnings("unchecked")
protected B getThis() {
return (B) this;
}
public abstract T build();
}
}

View File

@@ -0,0 +1,28 @@
/*
* 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.server.authorization;
/**
* @author Joe Grandja
* @since 0.1.0
* @see OAuth2TokenContext
*/
@FunctionalInterface
public interface OAuth2TokenCustomizer<C extends OAuth2TokenContext> {
void customize(C context);
}

View File

@@ -0,0 +1,101 @@
/*
* 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.server.authorization.authentication;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.Set;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JoseHeader;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* Utility methods used by the {@link AuthenticationProvider}'s when issuing {@link Jwt}'s.
*
* @author Joe Grandja
* @since 0.1.0
*/
final class JwtUtils {
private JwtUtils() {
}
static JoseHeader.Builder headers() {
return JoseHeader.withAlgorithm(SignatureAlgorithm.RS256);
}
static JwtClaimsSet.Builder accessTokenClaims(RegisteredClient registeredClient,
String issuer, String subject, Set<String> authorizedScopes) {
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plus(registeredClient.getTokenSettings().accessTokenTimeToLive());
// @formatter:off
JwtClaimsSet.Builder claimsBuilder = JwtClaimsSet.builder();
if (StringUtils.hasText(issuer)) {
claimsBuilder.issuer(issuer);
}
claimsBuilder
.subject(subject)
.audience(Collections.singletonList(registeredClient.getClientId()))
.issuedAt(issuedAt)
.expiresAt(expiresAt)
.notBefore(issuedAt);
if (!CollectionUtils.isEmpty(authorizedScopes)) {
claimsBuilder.claim(OAuth2ParameterNames.SCOPE, authorizedScopes);
}
// @formatter:on
return claimsBuilder;
}
static JwtClaimsSet.Builder idTokenClaims(RegisteredClient registeredClient,
String issuer, String subject, String nonce) {
Instant issuedAt = Instant.now();
// TODO Allow configuration for ID Token time-to-live
Instant expiresAt = issuedAt.plus(30, ChronoUnit.MINUTES);
// @formatter:off
JwtClaimsSet.Builder claimsBuilder = JwtClaimsSet.builder();
if (StringUtils.hasText(issuer)) {
claimsBuilder.issuer(issuer);
}
claimsBuilder
.subject(subject)
.audience(Collections.singletonList(registeredClient.getClientId()))
.issuedAt(issuedAt)
.expiresAt(expiresAt)
.claim(IdTokenClaimNames.AZP, registeredClient.getClientId());
if (StringUtils.hasText(nonce)) {
claimsBuilder.claim(IdTokenClaimNames.NONCE, nonce);
}
// TODO Add 'auth_time' claim
// @formatter:on
return claimsBuilder;
}
}

View File

@@ -15,25 +15,29 @@
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.server.authorization.Version;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.Version;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.util.Assert;
import java.util.Collections;
import java.util.Map;
/**
* An {@link Authentication} implementation used when issuing an OAuth 2.0 Access Token.
* An {@link Authentication} implementation used when issuing an
* OAuth 2.0 Access Token and (optional) Refresh Token.
*
* @author Joe Grandja
* @author Madhu Bhat
* @since 0.0.1
* @see AbstractAuthenticationToken
* @see OAuth2AuthorizationCodeAuthenticationProvider
* @see RegisteredClient
* @see OAuth2AccessToken
* @see OAuth2RefreshToken
* @see OAuth2ClientAuthenticationToken
*/
public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthenticationToken {
@@ -41,6 +45,8 @@ public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthentication
private final RegisteredClient registeredClient;
private final Authentication clientPrincipal;
private final OAuth2AccessToken accessToken;
private final OAuth2RefreshToken refreshToken;
private final Map<String, Object> additionalParameters;
/**
* Constructs an {@code OAuth2AccessTokenAuthenticationToken} using the provided parameters.
@@ -51,13 +57,43 @@ public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthentication
*/
public OAuth2AccessTokenAuthenticationToken(RegisteredClient registeredClient,
Authentication clientPrincipal, OAuth2AccessToken accessToken) {
this(registeredClient, clientPrincipal, accessToken, null);
}
/**
* Constructs an {@code OAuth2AccessTokenAuthenticationToken} using the provided parameters.
*
* @param registeredClient the registered client
* @param clientPrincipal the authenticated client principal
* @param accessToken the access token
* @param refreshToken the refresh token
*/
public OAuth2AccessTokenAuthenticationToken(RegisteredClient registeredClient, Authentication clientPrincipal,
OAuth2AccessToken accessToken, @Nullable OAuth2RefreshToken refreshToken) {
this(registeredClient, clientPrincipal, accessToken, refreshToken, Collections.emptyMap());
}
/**
* Constructs an {@code OAuth2AccessTokenAuthenticationToken} using the provided parameters.
*
* @param registeredClient the registered client
* @param clientPrincipal the authenticated client principal
* @param accessToken the access token
* @param refreshToken the refresh token
* @param additionalParameters the additional parameters
*/
public OAuth2AccessTokenAuthenticationToken(RegisteredClient registeredClient, Authentication clientPrincipal,
OAuth2AccessToken accessToken, @Nullable OAuth2RefreshToken refreshToken, Map<String, Object> additionalParameters) {
super(Collections.emptyList());
Assert.notNull(registeredClient, "registeredClient cannot be null");
Assert.notNull(clientPrincipal, "clientPrincipal cannot be null");
Assert.notNull(accessToken, "accessToken cannot be null");
Assert.notNull(additionalParameters, "additionalParameters cannot be null");
this.registeredClient = registeredClient;
this.clientPrincipal = clientPrincipal;
this.accessToken = accessToken;
this.refreshToken = refreshToken;
this.additionalParameters = additionalParameters;
}
@Override
@@ -87,4 +123,23 @@ public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthentication
public OAuth2AccessToken getAccessToken() {
return this.accessToken;
}
/**
* Returns the {@link OAuth2RefreshToken refresh token}.
*
* @return the {@link OAuth2RefreshToken} or {@code null} if not available
*/
@Nullable
public OAuth2RefreshToken getRefreshToken() {
return this.refreshToken;
}
/**
* Returns the additional parameters.
*
* @return a {@code Map} of the additional parameters, may be empty
*/
public Map<String, Object> getAdditionalParameters() {
return this.additionalParameters;
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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.server.authorization.authentication;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
/**
* Utility methods for the OAuth 2.0 {@link AuthenticationProvider}'s.
*
* @author Joe Grandja
* @since 0.0.3
*/
final class OAuth2AuthenticationProviderUtils {
private OAuth2AuthenticationProviderUtils() {
}
static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
OAuth2ClientAuthenticationToken clientPrincipal = null;
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
}
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
return clientPrincipal;
}
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
}
static <T extends AbstractOAuth2Token> OAuth2Authorization invalidate(
OAuth2Authorization authorization, T token) {
// @formatter:off
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.from(authorization)
.token(token,
(metadata) ->
metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true));
if (OAuth2RefreshToken.class.isAssignableFrom(token.getClass())) {
authorizationBuilder.token(
authorization.getAccessToken().getToken(),
(metadata) ->
metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true));
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode =
authorization.getToken(OAuth2AuthorizationCode.class);
if (authorizationCode != null && !authorizationCode.isInvalidated()) {
authorizationBuilder.token(
authorizationCode.getToken(),
(metadata) ->
metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true));
}
}
// @formatter:on
return authorizationBuilder.build();
}
}

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,36 +15,43 @@
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import java.security.Principal;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.jose.JoseHeader;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.jwt.JoseHeader;
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.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.Set;
import static org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthenticationProviderUtils.getAuthenticatedClientElseThrowInvalidClient;
/**
* An {@link AuthenticationProvider} implementation for the OAuth 2.0 Authorization Code Grant.
@@ -54,58 +61,72 @@ import java.util.Set;
* @since 0.0.1
* @see OAuth2AuthorizationCodeAuthenticationToken
* @see OAuth2AccessTokenAuthenticationToken
* @see RegisteredClientRepository
* @see OAuth2AuthorizationService
* @see JwtEncoder
* @see OAuth2TokenCustomizer
* @see JwtEncodingContext
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request</a>
*/
public class OAuth2AuthorizationCodeAuthenticationProvider implements AuthenticationProvider {
private final RegisteredClientRepository registeredClientRepository;
private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE =
new OAuth2TokenType(OAuth2ParameterNames.CODE);
private static final OAuth2TokenType ID_TOKEN_TOKEN_TYPE =
new OAuth2TokenType(OidcParameterNames.ID_TOKEN);
private final OAuth2AuthorizationService authorizationService;
private final JwtEncoder jwtEncoder;
private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = (context) -> {};
private ProviderSettings providerSettings;
/**
* Constructs an {@code OAuth2AuthorizationCodeAuthenticationProvider} using the provided parameters.
*
* @param registeredClientRepository the repository of registered clients
* @param authorizationService the authorization service
* @param jwtEncoder the jwt encoder
*/
public OAuth2AuthorizationCodeAuthenticationProvider(RegisteredClientRepository registeredClientRepository,
OAuth2AuthorizationService authorizationService, JwtEncoder jwtEncoder) {
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
public OAuth2AuthorizationCodeAuthenticationProvider(OAuth2AuthorizationService authorizationService, JwtEncoder jwtEncoder) {
Assert.notNull(authorizationService, "authorizationService cannot be null");
Assert.notNull(jwtEncoder, "jwtEncoder cannot be null");
this.registeredClientRepository = registeredClientRepository;
this.authorizationService = authorizationService;
this.jwtEncoder = jwtEncoder;
}
public final void setJwtCustomizer(OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer) {
Assert.notNull(jwtCustomizer, "jwtCustomizer cannot be null");
this.jwtCustomizer = jwtCustomizer;
}
@Autowired(required = false)
protected void setProviderSettings(ProviderSettings providerSettings) {
this.providerSettings = providerSettings;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication =
(OAuth2AuthorizationCodeAuthenticationToken) authentication;
OAuth2ClientAuthenticationToken clientPrincipal = null;
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authorizationCodeAuthentication.getPrincipal().getClass())) {
clientPrincipal = (OAuth2ClientAuthenticationToken) authorizationCodeAuthentication.getPrincipal();
}
if (clientPrincipal == null || !clientPrincipal.isAuthenticated()) {
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
}
OAuth2ClientAuthenticationToken clientPrincipal =
getAuthenticatedClientElseThrowInvalidClient(authorizationCodeAuthentication);
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
OAuth2Authorization authorization = this.authorizationService.findByToken(
authorizationCodeAuthentication.getCode(), TokenType.AUTHORIZATION_CODE);
authorizationCodeAuthentication.getCode(), AUTHORIZATION_CODE_TOKEN_TYPE);
if (authorization == null) {
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT));
}
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode =
authorization.getToken(OAuth2AuthorizationCode.class);
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST);
OAuth2AuthorizationRequest.class.getName());
if (!registeredClient.getClientId().equals(authorizationRequest.getClientId())) {
if (!authorizationCode.isInvalidated()) {
// Invalidate the authorization code given that a different client is attempting to use it
authorization = OAuth2AuthenticationProviderUtils.invalidate(authorization, authorizationCode.getToken());
this.authorizationService.save(authorization);
}
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT));
}
@@ -114,44 +135,118 @@ public class OAuth2AuthorizationCodeAuthenticationProvider implements Authentica
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT));
}
JoseHeader joseHeader = JoseHeader.withAlgorithm(SignatureAlgorithm.RS256).build();
if (authorizationCode.isInvalidated()) {
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT));
}
// TODO Allow configuration for issuer claim
URL issuer = null;
try {
issuer = URI.create("https://oauth2.provider.com").toURL();
} catch (MalformedURLException e) { }
String issuer = this.providerSettings != null ? this.providerSettings.issuer() : null;
Set<String> authorizedScopes = authorization.getAttribute(
OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME);
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS); // TODO Allow configuration for access token time-to-live
Set<String> authorizedScopes = authorization.getAttribute(OAuth2AuthorizationAttributeNames.AUTHORIZED_SCOPES);
JoseHeader.Builder headersBuilder = JwtUtils.headers();
JwtClaimsSet.Builder claimsBuilder = JwtUtils.accessTokenClaims(
registeredClient, issuer, authorization.getPrincipalName(),
authorizedScopes);
JwtClaimsSet jwtClaimsSet = JwtClaimsSet.withClaims()
.issuer(issuer)
.subject(authorization.getPrincipalName())
.audience(Collections.singletonList(registeredClient.getClientId()))
.issuedAt(issuedAt)
.expiresAt(expiresAt)
.notBefore(issuedAt)
.claim(OAuth2ParameterNames.SCOPE, authorizedScopes)
// @formatter:off
JwtEncodingContext context = JwtEncodingContext.with(headersBuilder, claimsBuilder)
.registeredClient(registeredClient)
.principal(authorization.getAttribute(Principal.class.getName()))
.authorization(authorization)
.authorizedScopes(authorizedScopes)
.tokenType(OAuth2TokenType.ACCESS_TOKEN)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrant(authorizationCodeAuthentication)
.build();
// @formatter:on
Jwt jwt = this.jwtEncoder.encode(joseHeader, jwtClaimsSet);
this.jwtCustomizer.customize(context);
JoseHeader headers = context.getHeaders().build();
JwtClaimsSet claims = context.getClaims().build();
Jwt jwtAccessToken = this.jwtEncoder.encode(headers, claims);
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
jwtAccessToken.getTokenValue(), jwtAccessToken.getIssuedAt(),
jwtAccessToken.getExpiresAt(), authorizedScopes);
OAuth2RefreshToken refreshToken = null;
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN)) {
refreshToken = OAuth2RefreshTokenAuthenticationProvider.generateRefreshToken(
registeredClient.getTokenSettings().refreshTokenTimeToLive());
}
Jwt jwtIdToken = null;
if (authorizationRequest.getScopes().contains(OidcScopes.OPENID)) {
String nonce = (String) authorizationRequest.getAdditionalParameters().get(OidcParameterNames.NONCE);
headersBuilder = JwtUtils.headers();
claimsBuilder = JwtUtils.idTokenClaims(
registeredClient, issuer, authorization.getPrincipalName(), nonce);
// @formatter:off
context = JwtEncodingContext.with(headersBuilder, claimsBuilder)
.registeredClient(registeredClient)
.principal(authorization.getAttribute(Principal.class.getName()))
.authorization(authorization)
.authorizedScopes(authorizedScopes)
.tokenType(ID_TOKEN_TOKEN_TYPE)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrant(authorizationCodeAuthentication)
.build();
// @formatter:on
this.jwtCustomizer.customize(context);
headers = context.getHeaders().build();
claims = context.getClaims().build();
jwtIdToken = this.jwtEncoder.encode(headers, claims);
}
OidcIdToken idToken;
if (jwtIdToken != null) {
idToken = new OidcIdToken(jwtIdToken.getTokenValue(), jwtIdToken.getIssuedAt(),
jwtIdToken.getExpiresAt(), jwtIdToken.getClaims());
} else {
idToken = null;
}
// @formatter:off
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.from(authorization)
.token(accessToken,
(metadata) ->
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, jwtAccessToken.getClaims())
);
if (refreshToken != null) {
authorizationBuilder.refreshToken(refreshToken);
}
if (idToken != null) {
authorizationBuilder
.token(idToken,
(metadata) ->
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, idToken.getClaims()));
}
authorization = authorizationBuilder.build();
// @formatter:on
// Invalidate the authorization code as it can only be used once
authorization = OAuth2AuthenticationProviderUtils.invalidate(authorization, authorizationCode.getToken());
authorization = OAuth2Authorization.from(authorization)
.attribute(OAuth2AuthorizationAttributeNames.ACCESS_TOKEN_ATTRIBUTES, jwt)
.accessToken(accessToken)
.build();
this.authorizationService.save(authorization);
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken);
Map<String, Object> additionalParameters = Collections.emptyMap();
if (idToken != null) {
additionalParameters = new HashMap<>();
additionalParameters.put(OidcParameterNames.ID_TOKEN, idToken.getTokenValue());
}
return new OAuth2AccessTokenAuthenticationToken(
registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters);
}
@Override
public boolean supports(Class<?> authentication) {
return OAuth2AuthorizationCodeAuthenticationToken.class.isAssignableFrom(authentication);
}
}

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,15 +15,13 @@
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.server.authorization.Version;
import org.springframework.util.Assert;
import java.util.Collections;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.util.Assert;
/**
* An {@link Authentication} implementation used for the OAuth 2.0 Authorization Code Grant.
*
@@ -31,16 +29,12 @@ import java.util.Map;
* @author Madhu Bhat
* @author Daniel Garnier-Moiroux
* @since 0.0.1
* @see AbstractAuthenticationToken
* @see OAuth2AuthorizationGrantAuthenticationToken
* @see OAuth2AuthorizationCodeAuthenticationProvider
* @see OAuth2ClientAuthenticationToken
*/
public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
public class OAuth2AuthorizationCodeAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
private final String code;
private final Authentication clientPrincipal;
private final String redirectUri;
private final Map<String, Object> additionalParameters;
/**
* Constructs an {@code OAuth2AuthorizationCodeAuthenticationToken} using the provided parameters.
@@ -52,26 +46,10 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenti
*/
public OAuth2AuthorizationCodeAuthenticationToken(String code, Authentication clientPrincipal,
@Nullable String redirectUri, @Nullable Map<String, Object> additionalParameters) {
super(Collections.emptyList());
super(AuthorizationGrantType.AUTHORIZATION_CODE, clientPrincipal, additionalParameters);
Assert.hasText(code, "code cannot be empty");
Assert.notNull(clientPrincipal, "clientPrincipal cannot be null");
this.code = code;
this.clientPrincipal = clientPrincipal;
this.redirectUri = redirectUri;
this.additionalParameters = Collections.unmodifiableMap(
additionalParameters != null ?
additionalParameters :
Collections.emptyMap());
}
@Override
public Object getPrincipal() {
return this.clientPrincipal;
}
@Override
public Object getCredentials() {
return "";
}
/**
@@ -88,16 +66,8 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenti
*
* @return the redirect uri
*/
public @Nullable String getRedirectUri() {
@Nullable
public String getRedirectUri() {
return this.redirectUri;
}
/**
* Returns the additional parameters
*
* @return the additional parameters
*/
public Map<String, Object> getAdditionalParameters() {
return this.additionalParameters;
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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.server.authorization.authentication;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.Version;
import org.springframework.util.Assert;
/**
* Base implementation of an {@link Authentication} representing an OAuth 2.0 Authorization Grant.
*
* @author Joe Grandja
* @since 0.1.0
* @see AbstractAuthenticationToken
* @see AuthorizationGrantType
* @see OAuth2ClientAuthenticationToken
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.3">Section 1.3 Authorization Grant</a>
*/
public class OAuth2AuthorizationGrantAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
private final AuthorizationGrantType authorizationGrantType;
private final Authentication clientPrincipal;
private final Map<String, Object> additionalParameters;
/**
* Sub-class constructor.
*
* @param authorizationGrantType the authorization grant type
* @param clientPrincipal the authenticated client principal
* @param additionalParameters the additional parameters
*/
protected OAuth2AuthorizationGrantAuthenticationToken(AuthorizationGrantType authorizationGrantType,
Authentication clientPrincipal, @Nullable Map<String, Object> additionalParameters) {
super(Collections.emptyList());
Assert.notNull(authorizationGrantType, "authorizationGrantType cannot be null");
Assert.notNull(clientPrincipal, "clientPrincipal cannot be null");
this.authorizationGrantType = authorizationGrantType;
this.clientPrincipal = clientPrincipal;
this.additionalParameters = Collections.unmodifiableMap(
additionalParameters != null ?
new HashMap<>(additionalParameters) :
Collections.emptyMap());
}
/**
* Returns the authorization grant type.
*
* @return the authorization grant type
*/
public AuthorizationGrantType getGrantType() {
return this.authorizationGrantType;
}
@Override
public Object getPrincipal() {
return this.clientPrincipal;
}
@Override
public Object getCredentials() {
return "";
}
/**
* Returns the additional parameters.
*
* @return the additional parameters
*/
public Map<String, Object> getAdditionalParameters() {
return this.additionalParameters;
}
}

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,32 +15,33 @@
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Map;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* An {@link AuthenticationProvider} implementation used for authenticating an OAuth 2.0 Client.
*
@@ -52,10 +53,13 @@ import java.util.Map;
* @see OAuth2ClientAuthenticationToken
* @see RegisteredClientRepository
* @see OAuth2AuthorizationService
* @see PasswordEncoder
*/
public class OAuth2ClientAuthenticationProvider implements AuthenticationProvider {
private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE);
private final RegisteredClientRepository registeredClientRepository;
private final OAuth2AuthorizationService authorizationService;
private PasswordEncoder passwordEncoder;
/**
* Constructs an {@code OAuth2ClientAuthenticationProvider} using the provided parameters.
@@ -69,6 +73,20 @@ public class OAuth2ClientAuthenticationProvider implements AuthenticationProvide
Assert.notNull(authorizationService, "authorizationService cannot be null");
this.registeredClientRepository = registeredClientRepository;
this.authorizationService = authorizationService;
this.passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
/**
* Sets the {@link PasswordEncoder} used to validate
* the {@link RegisteredClient#getClientSecret() client secret}.
* If not set, the client secret will be compared using
* {@link PasswordEncoderFactories#createDelegatingPasswordEncoder()}.
*
* @param passwordEncoder the {@link PasswordEncoder} used to validate the client secret
*/
public final void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
this.passwordEncoder = passwordEncoder;
}
@Override
@@ -82,15 +100,26 @@ public class OAuth2ClientAuthenticationProvider implements AuthenticationProvide
throwInvalidClient();
}
if (clientAuthentication.getCredentials() != null) {
String clientSecret = clientAuthentication.getCredentials().toString();
// TODO Use PasswordEncoder.matches()
if (!registeredClient.getClientSecret().equals(clientSecret)) {
throwInvalidClient();
}
if (!registeredClient.getClientAuthenticationMethods().contains(
clientAuthentication.getClientAuthenticationMethod())) {
throwInvalidClient();
}
authenticatePkceIfAvailable(clientAuthentication, registeredClient);
boolean authenticatedCredentials = false;
if (clientAuthentication.getCredentials() != null) {
String clientSecret = clientAuthentication.getCredentials().toString();
if (!this.passwordEncoder.matches(clientSecret, registeredClient.getClientSecret())) {
throwInvalidClient();
}
authenticatedCredentials = true;
}
authenticatedCredentials = authenticatedCredentials ||
authenticatePkceIfAvailable(clientAuthentication, registeredClient);
if (!authenticatedCredentials) {
throwInvalidClient();
}
return new OAuth2ClientAuthenticationToken(registeredClient);
}
@@ -100,36 +129,39 @@ public class OAuth2ClientAuthenticationProvider implements AuthenticationProvide
return OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication);
}
private void authenticatePkceIfAvailable(OAuth2ClientAuthenticationToken clientAuthentication,
private boolean authenticatePkceIfAvailable(OAuth2ClientAuthenticationToken clientAuthentication,
RegisteredClient registeredClient) {
Map<String, Object> parameters = clientAuthentication.getAdditionalParameters();
if (CollectionUtils.isEmpty(parameters) || !authorizationCodeGrant(parameters)) {
return;
return false;
}
OAuth2Authorization authorization = this.authorizationService.findByToken(
(String) parameters.get(OAuth2ParameterNames.CODE),
TokenType.AUTHORIZATION_CODE);
AUTHORIZATION_CODE_TOKEN_TYPE);
if (authorization == null) {
throwInvalidClient();
}
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST);
OAuth2AuthorizationRequest.class.getName());
String codeChallenge = (String) authorizationRequest.getAdditionalParameters()
.get(PkceParameterNames.CODE_CHALLENGE);
if (StringUtils.hasText(codeChallenge)) {
String codeChallengeMethod = (String) authorizationRequest.getAdditionalParameters()
.get(PkceParameterNames.CODE_CHALLENGE_METHOD);
String codeVerifier = (String) parameters.get(PkceParameterNames.CODE_VERIFIER);
if (!codeVerifierValid(codeVerifier, codeChallenge, codeChallengeMethod)) {
throwInvalidClient();
}
} else if (registeredClient.getClientSettings().requireProofKey()) {
if (!StringUtils.hasText(codeChallenge) &&
registeredClient.getClientSettings().requireProofKey()) {
throwInvalidClient();
}
String codeChallengeMethod = (String) authorizationRequest.getAdditionalParameters()
.get(PkceParameterNames.CODE_CHALLENGE_METHOD);
String codeVerifier = (String) parameters.get(PkceParameterNames.CODE_VERIFIER);
if (!codeVerifierValid(codeVerifier, codeChallenge, codeChallengeMethod)) {
throwInvalidClient();
}
return true;
}
private static boolean authorizationCodeGrant(Map<String, Object> parameters) {

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