104 Commits
0.3.1 ... 0.4.0

Author SHA1 Message Date
Joe Grandja
9e0b52ea91 Release 0.4.0 2022-11-21 13:33:53 -05:00
Joe Grandja
02d0292cb1 Update to jackson-bom 2.14.0
Closes gh-980
2022-11-21 13:29:08 -05:00
Joe Grandja
e9c21235a4 Update to Spring Security 5.8.0
Closes gh-979
2022-11-21 13:28:54 -05:00
Joe Grandja
1d6ca0dbb1 Update to Spring Framework 5.3.24
Closes gh-978
2022-11-21 13:28:19 -05:00
Seyed Mohammad Hossein Jamali
4e63c3b6b6 Update links to current version of OAuth 2.1
Closes gh-960
2022-11-21 11:15:09 -05:00
Joe Grandja
8ed0194744 client_id authentication parameter must have printable ASCII characters
Closes gh-889
2022-11-18 14:30:06 -05:00
Joe Grandja
fcbb5c1197 Polish OAuth2AuthorizationCodeGrantTests 2022-11-16 04:27:45 -05:00
Joe Grandja
a9371e918a Document Jwt Client Assertion Validation
Closes gh-945
2022-11-16 03:57:56 -05:00
Joe Grandja
8c78a5bdee Document Authorization Request Validation
Closes gh-858
2022-11-15 12:14:28 -05:00
Steve Riesenberg
74fe63a65b Add logging for authentication filters
Closes gh-159
2022-11-09 07:38:47 -05:00
Steve Riesenberg
2c4bd29f98 Add logging for authentication providers
Issue gh-159
2022-11-09 07:38:47 -05:00
Joe Grandja
7e03795c58 Remove issuer setting from samples 2022-11-08 16:31:16 -05:00
Joe Grandja
8b0e7578df Upgrade to JUnit 5
Closes gh-964
2022-11-08 11:35:27 -05:00
Joe Grandja
0e509333bc Assert unique identifiers in JdbcRegisteredClientRepository
Closes gh-959
2022-11-03 16:29:12 -04:00
Steve Riesenberg
061badf3ae Add User Info to Getting Started in ref doc
Closes gh-917
2022-11-02 13:45:20 -05:00
Joe Grandja
70466b74d8 Next Development Version 2022-11-01 09:57:42 -04:00
Joe Grandja
b47d5ebb72 Release 0.4.0-RC1 2022-11-01 09:45:37 -04:00
Joe Grandja
1a6a9dd1f2 Update to mockito-core:4.8.1
Closes gh-951
2022-11-01 05:44:09 -04:00
Joe Grandja
836acf7c0e Update to jackson-bom:2.13.4.20221013
Closes gh-950
2022-11-01 05:43:59 -04:00
Joe Grandja
32c53e53da Update to Spring Security 5.8.0-RC1
Closes gh-949
2022-11-01 05:43:44 -04:00
Joe Grandja
5bb43af3ff Update to Spring Boot 2.7.5
Closes gh-948
2022-11-01 05:42:45 -04:00
Joe Grandja
6dc3944eef Add OidcClientRegistrationAuthenticationProvider.setRegisteredClientConverter()
Closes gh-696
2022-10-31 14:45:39 -04:00
Joe Grandja
4eb25c163f Polish gh-920 2022-10-31 11:56:03 -04:00
Jonah Back
356d669a78 Fix URL encoding for authorization request state parameter
Closes gh-875
2022-10-31 11:38:25 -04:00
Joe Grandja
11ce8ef201 Polish gh-929 2022-10-28 18:04:17 -04:00
Joe Grandja
bfd7a09c3b Polish gh-946 2022-10-28 17:36:14 -04:00
Daniel Garnier-Moiroux
efbfdc234c Improve customizing OIDC Client Registration endpoint
Related gh-696

Closes gh-946
2022-10-28 17:35:11 -04:00
Joe Grandja
2ba711c83a Polish gh-929 2022-10-28 14:23:16 -04:00
Daniel Garnier-Moiroux
8d7f8b3420 Improve customizing OIDC UserInfo endpoint
Closes gh-785
2022-10-27 13:55:02 -04:00
Joe Grandja
8c2b095195 Extract JwtDecoderFactory from JwtClientAssertionAuthenticationProvider
Closes gh-944
2022-10-27 10:12:35 -04:00
octopusthu-at-mbptt1
b1b2bc438f Update OAuth 2.1 spec link in README.adoc
Closes gh-940
2022-10-25 17:09:01 -04:00
Joe Grandja
72804be45b Extract OIDC client configuration implementation
Closes gh-941
2022-10-25 15:13:14 -04:00
Joe Grandja
629e220c2f Use Boolean field for OidcUserInfo.phoneNumberVerified
Related https://github.com/spring-projects/spring-security/issues/11315

Closes gh-923
2022-10-24 14:47:48 -04:00
Joe Grandja
9c964e37b0 Update reference for customizing Authorization Server metadata response
Issue gh-616 gh-878
2022-10-20 10:23:58 -04:00
Joe Grandja
d7aa72af68 Disable OpenID Connect 1.0 by default
Closes gh-928
2022-10-20 09:31:49 -04:00
Joe Grandja
a9cf857d33 Next Development Version 2022-09-20 15:06:39 -04:00
Joe Grandja
3729dc0d43 Release 0.4.0-M2 2022-09-20 14:55:14 -04:00
Joe Grandja
dc142cb253 Update to okhttp:4.10.0
Closes gh-904
2022-09-20 14:50:19 -04:00
Joe Grandja
9c0ca08e68 Update to mockito-core:4.8.0
Closes gh-903
2022-09-20 14:50:08 -04:00
Joe Grandja
1f68ad1655 Update to assertj-core:3.23.1
Closes gh-902
2022-09-20 14:49:58 -04:00
Joe Grandja
b71801cd1e Update to jackson-bom:2.13.4
Closes gh-901
2022-09-20 14:49:46 -04:00
Joe Grandja
213bf49510 Update to nimbus-jose-jwt:9.24.4
Closes gh-900
2022-09-20 14:49:31 -04:00
Joe Grandja
f114e3a1ce Update to Spring Security 5.8.0-M3
Closes gh-899
2022-09-20 14:49:21 -04:00
Joe Grandja
17c882c06d Update to Spring Framework 5.3.23
Closes gh-898
2022-09-20 14:48:50 -04:00
Joe Grandja
92dbcf29a5 Move integration tests for OidcProviderConfiguration 2022-09-20 12:25:58 -04:00
Joe Grandja
26aed3c183 Polish gh-881 2022-09-20 11:23:42 -04:00
sahariardev
cd6f1d7dc3 Return registration_endpoint when client registration is enabled
Closes gh-370
2022-09-20 11:22:45 -04:00
Joe Grandja
4d94e7095d Decompose OAuth2AuthorizationCodeRequestAuthenticationProvider
Closes gh-896
2022-09-20 06:03:32 -04:00
Joe Grandja
80b01854f2 Update README with documentation links 2022-09-13 11:57:41 -04:00
Joe Grandja
c326b1a2ba Remove OAuth2AuthenticationValidator
Closes gh-891
2022-09-12 16:57:22 -04:00
Joe Grandja
1db05991af Make OAuth2AuthenticationContext an interface
Closes gh-890
2022-09-12 16:56:45 -04:00
Joe Grandja
2cc603c7e7 Improve configurability for AuthenticationConverter and AuthenticationProvider
Closes gh-417
2022-09-07 04:29:03 -04:00
doctormacky
07d69cbfb4 Validate client secret not expired
Closes gh-850
2022-08-30 09:41:29 -04:00
Joe Grandja
502fa24cfb Polish gh-787 2022-08-30 05:58:15 -04:00
721806280
4466cbe69d Use configured ID Token signature algorithm
Closes gh-787
2022-08-30 05:19:21 -04:00
Joe Grandja
8043b8c949 Allow customizing Authorization Server Metadata Response
Closes gh-878
2022-08-29 17:06:46 -04:00
Joe Grandja
0994a1e1e1 Allow customizing OIDC Provider Configuration Response
Closes gh-616
2022-08-29 10:11:05 -04:00
Joe Grandja
70d433a45a Update ref-doc with OAuth2Authorization.getAuthorizedScopes()
Issue gh-829
2022-08-26 11:14:20 -04:00
Joe Grandja
2dabfa02e0 Remove constructor in OidcProviderConfigurationEndpointFilter
Closes gh-869
2022-08-23 13:57:11 -04:00
Joe Grandja
6b66719a83 Remove constructor in OAuth2AuthorizationServerMetadataEndpointFilter
Closes gh-868
2022-08-23 13:55:51 -04:00
Joe Grandja
aebc613862 Make AuthorizationServerContext an interface
Closes gh-867
2022-08-23 11:26:11 -04:00
Joe Grandja
f583668a9c Make AuthorizationServerContextFilter private
Closes gh-866
2022-08-23 11:25:46 -04:00
Joe Grandja
3efee494ad Rename ProviderContext
Closes gh-865
2022-08-23 11:25:18 -04:00
Joe Grandja
c60ae4532f Rename ProviderSettings
Closes gh-864
2022-08-23 11:24:22 -04:00
Joe Grandja
4066c3ec4d Next Development Version 2022-08-16 14:19:44 -04:00
Joe Grandja
daeeb14141 Release 0.4.0-M1 2022-08-16 14:07:02 -04:00
Joe Grandja
6701913d0e Update to nimbus-jose-jwt:9.23
Closes gh-857
2022-08-16 14:01:07 -04:00
Joe Grandja
03b31d90a7 Update to Spring Security 5.8.0-M2
Closes gh-856
2022-08-16 13:59:39 -04:00
Joe Grandja
c9ac9afb60 Update to Spring Framework 5.3.22
Closes gh-855
2022-08-16 13:58:38 -04:00
Joe Grandja
ce0d1b7b1e Enhance samples to call UserInfo endpoint
Closes gh-847
2022-08-16 11:31:46 -04:00
Joe Grandja
2e07ce8b8f Polish gh-738 2022-08-16 03:58:50 -04:00
yonyes
61621c5671 Default authorized scope to empty for client_credentials grant
Closes gh-780
2022-08-16 03:58:37 -04:00
Joe Grandja
532cade256 Update github workflows 2022-08-02 09:02:43 -04:00
Joe Grandja
6284e1dc1e Remove deprecated API usage 2022-08-01 11:47:29 -04:00
Joe Grandja
4934088593 Remove generic type from OAuth2AuthorizationServerConfigurer
Closes gh-831
2022-08-01 11:17:52 -04:00
Joe Grandja
0656fde051 Remove OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME
Closes gh-829
2022-07-29 15:42:26 -04:00
Joe Grandja
83be809814 Move SpringTestRule 2022-07-29 05:35:19 -04:00
Joe Grandja
a38b66adf2 Remove unused code 2022-07-29 05:08:16 -04:00
Joe Grandja
33a78913fa Cleanup TODOs 2022-07-29 04:29:11 -04:00
Joe Grandja
f29a068b7a Rename JwtEncodingContext.getHeaders() to getJwsHeader()
Closes gh-826
2022-07-28 15:54:52 -04:00
Joe Grandja
94053f2068 Make builders final for AbstractSettings implementations
Closes gh-825
2022-07-28 15:54:38 -04:00
Joe Grandja
0b85fdc883 Make OAuth2TokenIntrospectionEndpointConfigurer.getRequestMatcher() package-private
Closes gh-824
2022-07-28 15:54:05 -04:00
Joe Grandja
852d795abc Polish gh-786 2022-07-28 14:51:01 -04:00
Joe Grandja
95e9a9e4bd Rename Version to SpringAuthorizationServerVersion
Closes gh-823
2022-07-27 07:19:45 -04:00
Joe Grandja
403d7070d2 Relocate Version
Closes gh-823
2022-07-27 07:19:23 -04:00
Joe Grandja
fdda88263b Relocate OAuth2TokenFormat
Closes gh-822
2022-07-27 07:18:53 -04:00
Joe Grandja
8aa2213bc4 Relocate OAuth2TokenType
Closes gh-821
2022-07-27 07:18:43 -04:00
Joe Grandja
af5f71f2ae Relocate OAuth2AuthorizationCode
Closes gh-820
2022-07-27 07:18:28 -04:00
Joe Grandja
6d69f3831e Relocate OAuth2TokenIntrospection
Closes gh-819
2022-07-27 07:18:16 -04:00
Joe Grandja
9c34eb874c Relocate OidcUserInfoHttpMessageConverter
Closes gh-818
2022-07-27 07:18:04 -04:00
Joe Grandja
05468b7b29 Relocate OidcClientRegistration
Closes gh-817
2022-07-27 07:17:52 -04:00
Joe Grandja
33f6f4f731 Relocate OidcProviderConfiguration
Closes gh-816
2022-07-27 07:17:34 -04:00
Joe Grandja
a48fbe46d5 Relocate OAuth2AuthorizationServerMetadata
Closes gh-815
2022-07-27 07:17:21 -04:00
Joe Grandja
0808fba26e Relocate classes out from oauth2.core.context package
Closes gh-814
2022-07-27 07:17:08 -04:00
Joe Grandja
2b2221a467 Relocate classes out from oauth2.core.authentication package
Closes gh-813
2022-07-27 07:16:22 -04:00
Joe Grandja
3877999a2a Move AbstractSettings implementations to settings package
Closes gh-811
2022-07-26 11:43:45 -04:00
Joe Grandja
ab601640ff Relocate classes out from config.annotation.web.configurers package
Closes gh-810
2022-07-26 08:55:07 -04:00
Joe Grandja
435a17e4b9 Relocate classes out from config.annotation.web.configuration package
Closes gh-810
2022-07-26 08:38:21 -04:00
Joe Grandja
65400f8c71 Update gradle-enterprise-conventions plugin to 0.0.11
Issue gh-788
2022-07-19 16:12:06 -04:00
Jerome Prinet
f008d5dded Update Gradle Enterprise plugin
Closes gh-788
2022-07-19 16:11:36 -04:00
wingzero
d5a57396a4 Update custom consent page sample
Closes gh-802
2022-07-19 15:32:42 -04:00
Joe Grandja
19c7606c52 Polish gh-786 2022-07-19 12:27:43 -04:00
Gyeongwon, Do
303043ea78 Add configuration for authorization code time-to-live
Closes gh-642
2022-07-19 11:40:02 -04:00
Joe Grandja
3f5e7c793b Next Development Version 2022-06-17 12:15:39 -04:00
256 changed files with 8698 additions and 4963 deletions

View File

@@ -3,7 +3,7 @@ name: Build and Deploy
on:
push:
branches:
- main
- '**'
schedule:
- cron: '0 10 * * *' # Once per day at 10am UTC
@@ -80,7 +80,7 @@ jobs:
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
run: ./gradlew test --refresh-dependencies -Duser.name=spring-builds+github -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PforceMavenRepositories=snapshot -PspringFrameworkVersion='5.3.+' -PspringSecurityVersion='5.7.+' -PlocksDisabled --stacktrace
run: ./gradlew test --refresh-dependencies -Duser.name=spring-builds+github -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PforceMavenRepositories=snapshot -PspringFrameworkVersion='5.3.+' -PspringSecurityVersion='5.8.+' -PlocksDisabled --stacktrace
deploy_artifacts:
name: Deploy Artifacts
needs: [build, snapshot_tests]

View File

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

View File

@@ -4,7 +4,7 @@ image:https://github.com/spring-projects/spring-authorization-server/workflows/C
= Spring Authorization Server
The Spring Authorization Server project, led by the https://spring.io/projects/spring-security/[Spring Security] team, is focused on delivering https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-1.1[OAuth 2.1 Authorization Server] support to the Spring community.
The Spring Authorization Server project, led by the https://spring.io/projects/spring-security/[Spring Security] team, is focused on delivering https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07#section-1.1[OAuth 2.1 Authorization Server] support to the Spring community.
This project replaces the Authorization Server support provided by https://spring.io/projects/spring-security-oauth/[Spring Security OAuth].
@@ -19,7 +19,7 @@ The feature list can be viewed in the https://docs.spring.io/spring-authorizatio
The Spring Authorization Server project provides software support and is documented in its link:SUPPORT_POLICY.adoc[support policy].
== Getting Started
The first place to start is to read the https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01[OAuth 2.1 Authorization Framework] to gain an in-depth understanding on how to build an Authorization Server.
The first place to start is to read the https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07[OAuth 2.1 Authorization Framework] to gain an in-depth understanding on how to build an Authorization Server.
It is a critically important first step as the implementation must conform to the specification defined in the OAuth 2.1 Authorization Framework and the https://github.com/spring-projects/spring-authorization-server/wiki/OAuth-2.0-Specifications[related specifications].
The second place to start is to become very familiar with the codebase in the following Spring Security modules:
@@ -35,9 +35,9 @@ The goal is to leverage all the knowledge learned thus far and apply the same to
Submitted work via pull requests should follow the same coding style/conventions and adopt the same or similar design patterns that have been established in Spring Security's OAuth 2.0 support.
== Documentation
Be sure to read the https://docs.spring.io/spring-security/reference[Spring Security Reference], as well as the https://docs.spring.io/spring-security/reference/servlet/oauth2/index.html[OAuth 2.0 Reference], which describes the Client and Resource Server features available.
Be sure to read the https://docs.spring.io/spring-authorization-server/docs/current/reference/html/[Spring Authorization Server Reference] and https://docs.spring.io/spring-security/reference[Spring Security Reference], as well as the https://docs.spring.io/spring-security/reference/servlet/oauth2/index.html[OAuth 2.0 Reference], which describes the Client and Resource Server features available.
Extensive JavaDoc for the Spring Security code is also available in the https://docs.spring.io/spring-security/site/docs/current/api/[Spring Security API Documentation].
JavaDoc is also available for the https://docs.spring.io/spring-authorization-server/docs/current/api/[Spring Authorization Server API] and https://docs.spring.io/spring-security/site/docs/current/api/[Spring Security API].
== Code of Conduct
This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code of conduct].

View File

@@ -23,5 +23,5 @@ dependencies {
implementation "org.hidetake:gradle-ssh-plugin:2.10.1"
implementation "org.jfrog.buildinfo:build-info-extractor-gradle:4.26.1"
implementation "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1"
implementation "org.springframework:spring-core:5.3.17"
implementation "org.springframework:spring-core:5.3.24"
}

View File

@@ -29,6 +29,7 @@ import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.plugins.PluginManager;
import org.gradle.api.tasks.compile.CompileOptions;
import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.api.tasks.testing.Test;
import org.gradle.jvm.tasks.Jar;
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper;
@@ -92,5 +93,8 @@ public class SpringJavaPlugin implements Plugin<Project> {
attributes.put("Automatic-Module-Name", project.getName().replace("-", "."));
manifest.attributes(attributes);
}));
project.getTasks().withType(Test.class, (test) -> {
test.useJUnitPlatform();
});
}
}

View File

@@ -9,15 +9,15 @@ javaPlatform {
dependencies {
api platform("org.springframework:spring-framework-bom:$springFrameworkVersion")
api platform("org.springframework.security:spring-security-bom:$springSecurityVersion")
api platform("com.fasterxml.jackson:jackson-bom:2.13.3")
api platform("com.fasterxml.jackson:jackson-bom:2.14.0")
constraints {
api "com.nimbusds:nimbus-jose-jwt:9.22"
api "com.nimbusds:nimbus-jose-jwt:9.24.4"
api "javax.servlet:javax.servlet-api:4.0.1"
api "junit:junit:4.13.2"
api "org.assertj:assertj-core:3.22.0"
api "org.mockito:mockito-core:4.5.1"
api "com.squareup.okhttp3:mockwebserver:4.9.3"
api "com.squareup.okhttp3:okhttp:4.9.3"
api "org.junit.jupiter:junit-jupiter:5.9.1"
api "org.assertj:assertj-core:3.23.1"
api "org.mockito:mockito-core:4.8.1"
api "com.squareup.okhttp3:mockwebserver:4.10.0"
api "com.squareup.okhttp3:okhttp:4.10.0"
api "com.jayway.jsonpath:json-path:2.7.0"
api "org.hsqldb:hsqldb:2.5.2"
}

View File

@@ -19,15 +19,10 @@ The OAuth2 authorization server `SecurityFilterChain` `@Bean` is configured with
* xref:protocol-endpoints.adoc#oauth2-token-revocation-endpoint[OAuth2 Token Revocation endpoint]
* xref:protocol-endpoints.adoc#oauth2-authorization-server-metadata-endpoint[OAuth2 Authorization Server Metadata endpoint]
* xref:protocol-endpoints.adoc#jwk-set-endpoint[JWK Set endpoint]
* xref:protocol-endpoints.adoc#oidc-provider-configuration-endpoint[OpenID Connect 1.0 Provider Configuration endpoint]
* xref:protocol-endpoints.adoc#oidc-user-info-endpoint[OpenID Connect 1.0 UserInfo endpoint]
[NOTE]
The JWK Set endpoint is configured *only* if a `JWKSource<SecurityContext>` `@Bean` is registered.
[NOTE]
The xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration endpoint] is disabled by default because many deployments do not require dynamic client registration.
The following example shows how to use `OAuth2AuthorizationServerConfiguration` to apply the minimal default configuration:
[source,java]
@@ -55,6 +50,29 @@ public class AuthorizationServerConfig {
[IMPORTANT]
The https://datatracker.ietf.org/doc/html/rfc6749#section-4.1[authorization_code grant] requires the resource owner to be authenticated. Therefore, a user authentication mechanism *must* be configured in addition to the default OAuth2 security configuration.
https://openid.net/specs/openid-connect-core-1_0.html[OpenID Connect 1.0] is disabled in the default configuration. The following example shows how to enable OpenID Connect 1.0 by initializing the `OidcConfigurer`:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults()); // Initialize `OidcConfigurer`
return http.build();
}
----
In addition to the default protocol endpoints, the OAuth2 authorization server `SecurityFilterChain` `@Bean` is configured with the following OpenID Connect 1.0 protocol endpoints:
* xref:protocol-endpoints.adoc#oidc-provider-configuration-endpoint[OpenID Connect 1.0 Provider Configuration endpoint]
* xref:protocol-endpoints.adoc#oidc-user-info-endpoint[OpenID Connect 1.0 UserInfo endpoint]
[NOTE]
The xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration endpoint] is disabled by default because many deployments do not require dynamic client registration.
[TIP]
`OAuth2AuthorizationServerConfiguration.jwtDecoder(JWKSource<SecurityContext>)` is a convenience (`static`) utility method that can be used to register a `JwtDecoder` `@Bean`, which is *REQUIRED* for the xref:protocol-endpoints.adoc#oidc-user-info-endpoint[OpenID Connect 1.0 UserInfo endpoint] and the xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration endpoint].
@@ -83,24 +101,26 @@ Furthermore, it lets you customize the request processing logic for the protocol
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.registeredClientRepository(registeredClientRepository) <1>
.authorizationService(authorizationService) <2>
.authorizationConsentService(authorizationConsentService) <3>
.providerSettings(providerSettings) <4>
.authorizationServerSettings(authorizationServerSettings) <4>
.tokenGenerator(tokenGenerator) <5>
.clientAuthentication(clientAuthentication -> { }) <6>
.authorizationEndpoint(authorizationEndpoint -> { }) <7>
.tokenEndpoint(tokenEndpoint -> { }) <8>
.tokenIntrospectionEndpoint(tokenIntrospectionEndpoint -> { }) <9>
.tokenRevocationEndpoint(tokenRevocationEndpoint -> { }) <10>
.authorizationServerMetadataEndpoint(authorizationServerMetadataEndpoint -> { }) <11>
.oidc(oidc -> oidc
.userInfoEndpoint(userInfoEndpoint -> { }) <11>
.clientRegistrationEndpoint(clientRegistrationEndpoint -> { }) <12>
.providerConfigurationEndpoint(providerConfigurationEndpoint -> { }) <12>
.userInfoEndpoint(userInfoEndpoint -> { }) <13>
.clientRegistrationEndpoint(clientRegistrationEndpoint -> { }) <14>
);
return http.build();
@@ -109,26 +129,28 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
<1> `registeredClientRepository()`: The xref:core-model-components.adoc#registered-client-repository[`RegisteredClientRepository`] (*REQUIRED*) for managing new and existing clients.
<2> `authorizationService()`: The xref:core-model-components.adoc#oauth2-authorization-service[`OAuth2AuthorizationService`] for managing new and existing authorizations.
<3> `authorizationConsentService()`: The xref:core-model-components.adoc#oauth2-authorization-consent-service[`OAuth2AuthorizationConsentService`] for managing new and existing authorization consents.
<4> `providerSettings()`: The <<configuring-provider-settings, `ProviderSettings`>> (*REQUIRED*) for customizing configuration settings for the OAuth2 authorization server.
<4> `authorizationServerSettings()`: The <<configuring-authorization-server-settings, `AuthorizationServerSettings`>> (*REQUIRED*) for customizing configuration settings for the OAuth2 authorization server.
<5> `tokenGenerator()`: The xref:core-model-components.adoc#oauth2-token-generator[`OAuth2TokenGenerator`] for generating tokens supported by the OAuth2 authorization server.
<6> `clientAuthentication()`: The configurer for <<configuring-client-authentication, OAuth2 Client Authentication>>.
<7> `authorizationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-authorization-endpoint[OAuth2 Authorization endpoint].
<8> `tokenEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-token-endpoint[OAuth2 Token endpoint].
<9> `tokenIntrospectionEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-token-introspection-endpoint[OAuth2 Token Introspection endpoint].
<10> `tokenRevocationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-token-revocation-endpoint[OAuth2 Token Revocation endpoint].
<11> `userInfoEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-user-info-endpoint[OpenID Connect 1.0 UserInfo endpoint].
<12> `clientRegistrationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration endpoint].
<11> `authorizationServerMetadataEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-authorization-server-metadata-endpoint[OAuth2 Authorization Server Metadata endpoint].
<12> `providerConfigurationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-provider-configuration-endpoint[OpenID Connect 1.0 Provider Configuration endpoint].
<13> `userInfoEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-user-info-endpoint[OpenID Connect 1.0 UserInfo endpoint].
<14> `clientRegistrationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration endpoint].
[[configuring-provider-settings]]
== Configuring Provider Settings
[[configuring-authorization-server-settings]]
== Configuring Authorization Server Settings
`ProviderSettings` contains the configuration settings for the OAuth2 authorization server (provider).
`AuthorizationServerSettings` contains the configuration settings for the OAuth2 authorization server.
It specifies the `URI` for the protocol endpoints as well as the https://datatracker.ietf.org/doc/html/rfc8414#section-2[issuer identifier].
The default `URI` for the protocol endpoints are as follows:
[source,java]
----
public final class ProviderSettings extends AbstractSettings {
public final class AuthorizationServerSettings extends AbstractSettings {
...
@@ -149,18 +171,18 @@ public final class ProviderSettings extends AbstractSettings {
----
[NOTE]
`ProviderSettings` is a *REQUIRED* component.
`AuthorizationServerSettings` is a *REQUIRED* component.
[TIP]
<<default-configuration, `@Import(OAuth2AuthorizationServerConfiguration.class)`>> automatically registers a `ProviderSettings` `@Bean`, if not already provided.
<<default-configuration, `@Import(OAuth2AuthorizationServerConfiguration.class)`>> automatically registers an `AuthorizationServerSettings` `@Bean`, if not already provided.
The following example shows how to customize the configuration settings and register a `ProviderSettings` `@Bean`:
The following example shows how to customize the configuration settings and register an `AuthorizationServerSettings` `@Bean`:
[source,java]
----
@Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder()
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder()
.issuer("https://example.com")
.authorizationEndpoint("/oauth2/v1/authorize")
.tokenEndpoint("/oauth2/v1/token")
@@ -173,17 +195,14 @@ public ProviderSettings providerSettings() {
}
----
The `ProviderContext` is a context object that holds information about the provider.
It provides access to the `ProviderSettings` and the "`current`" issuer identifier.
The `AuthorizationServerContext` is a context object that holds information of the Authorization Server runtime environment.
It provides access to the `AuthorizationServerSettings` and the "`current`" issuer identifier.
[NOTE]
If the issuer identifier is not configured in `ProviderSettings.builder().issuer(String)`, it is resolved from the current request.
If the issuer identifier is not configured in `AuthorizationServerSettings.builder().issuer(String)`, it is resolved from the current request.
[NOTE]
The `ProviderContext` is accessible through the `ProviderContextHolder`, which associates it with the current request thread by using a `ThreadLocal`.
[NOTE]
The `ProviderContextFilter` associates the `ProviderContext` with the `ProviderContextHolder`.
The `AuthorizationServerContext` is accessible through the `AuthorizationServerContextHolder`, which associates it with the current request thread by using a `ThreadLocal`.
[[configuring-client-authentication]]
== Configuring Client Authentication
@@ -197,26 +216,30 @@ It defines extension points that let you customize the pre-processing, main proc
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.clientAuthentication(clientAuthentication ->
clientAuthentication
.authenticationConverter(authenticationConverter) <1>
.authenticationProvider(authenticationProvider) <2>
.authenticationSuccessHandler(authenticationSuccessHandler) <3>
.errorResponseHandler(errorResponseHandler) <4>
.authenticationConverters(authenticationConvertersConsumer) <2>
.authenticationProvider(authenticationProvider) <3>
.authenticationProviders(authenticationProvidersConsumer) <4>
.authenticationSuccessHandler(authenticationSuccessHandler) <5>
.errorResponseHandler(errorResponseHandler) <6>
);
return http.build();
}
----
<1> `authenticationConverter()`: The `AuthenticationConverter` (_pre-processor_) used when attempting to extract client credentials from `HttpServletRequest` to an instance of `OAuth2ClientAuthenticationToken`.
<2> `authenticationProvider()`: The `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2ClientAuthenticationToken`. (One or more may be added to replace the defaults.)
<3> `authenticationSuccessHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling a successful client authentication and associating the `OAuth2ClientAuthenticationToken` to the `SecurityContext`.
<4> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling a failed client authentication and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-5.2[`OAuth2Error` response].
<1> `authenticationConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract client credentials from `HttpServletRequest` to an instance of `OAuth2ClientAuthenticationToken`.
<2> `authenticationConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2ClientAuthenticationToken`.
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
<5> `authenticationSuccessHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling a successful client authentication and associating the `OAuth2ClientAuthenticationToken` to the `SecurityContext`.
<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling a failed client authentication and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-5.2[`OAuth2Error` response].
`OAuth2ClientAuthenticationConfigurer` configures the `OAuth2ClientAuthenticationFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OAuth2ClientAuthenticationFilter` is the `Filter` that processes client authentication requests.
@@ -230,3 +253,55 @@ The supported client authentication methods are `client_secret_basic`, `client_s
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `JwtClientAssertionAuthenticationProvider`, `ClientSecretAuthenticationProvider`, and `PublicClientAuthenticationProvider`.
* `*AuthenticationSuccessHandler*` -- An internal implementation that associates the "`authenticated`" `OAuth2ClientAuthenticationToken` (current `Authentication`) to the `SecurityContext`.
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthenticationException` to return the OAuth2 error response.
[[configuring-client-authentication-customizing-jwt-client-assertion-validation]]
=== Customizing Jwt Client Assertion Validation
`JwtClientAssertionDecoderFactory.DEFAULT_JWT_VALIDATOR_FACTORY` is the default factory that provides an `OAuth2TokenValidator<Jwt>` for the specified `RegisteredClient` and is used for validating the `iss`, `sub`, `aud`, `exp` and `nbf` claims of the `Jwt` client assertion.
`JwtClientAssertionDecoderFactory` provides the ability to override the default `Jwt` client assertion validation by supplying a custom factory of type `Function<RegisteredClient, OAuth2TokenValidator<Jwt>>` to `setJwtValidatorFactory()`.
[NOTE]
`JwtClientAssertionDecoderFactory` is the default `JwtDecoderFactory` used by `JwtClientAssertionAuthenticationProvider` that provides a `JwtDecoder` for the specified `RegisteredClient` and is used for authenticating a `Jwt` Bearer Token during OAuth2 client authentication.
A common use case for customizing `JwtClientAssertionDecoderFactory` is to validate additional claims in the `Jwt` client assertion.
The following example shows how to configure `JwtClientAssertionAuthenticationProvider` with a customized `JwtClientAssertionDecoderFactory` that validates an additional claim in the `Jwt` client assertion:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.clientAuthentication(clientAuthentication ->
clientAuthentication
.authenticationProviders(configureJwtClientAssertionValidator())
);
return http.build();
}
private Consumer<List<AuthenticationProvider>> configureJwtClientAssertionValidator() {
return (authenticationProviders) ->
authenticationProviders.forEach((authenticationProvider) -> {
if (authenticationProvider instanceof JwtClientAssertionAuthenticationProvider) {
// Customize JwtClientAssertionDecoderFactory
JwtClientAssertionDecoderFactory jwtDecoderFactory = new JwtClientAssertionDecoderFactory();
Function<RegisteredClient, OAuth2TokenValidator<Jwt>> jwtValidatorFactory = (registeredClient) ->
new DelegatingOAuth2TokenValidator<>(
// Use default validators
JwtClientAssertionDecoderFactory.DEFAULT_JWT_VALIDATOR_FACTORY.apply(registeredClient),
// Add custom validator
new JwtClaimValidator<>("claim", "value"::equals));
jwtDecoderFactory.setJwtValidatorFactory(jwtValidatorFactory);
((JwtClientAssertionAuthenticationProvider) authenticationProvider)
.setJwtDecoderFactory(jwtDecoderFactory);
}
});
}
----

View File

@@ -120,8 +120,8 @@ Alternatively, you can configure the `RegisteredClientRepository` through the xr
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
@@ -163,8 +163,9 @@ public class OAuth2Authorization implements Serializable {
private String registeredClientId; <2>
private String principalName; <3>
private AuthorizationGrantType authorizationGrantType; <4>
private Map<Class<? extends OAuth2Token>, Token<?>> tokens; <5>
private Map<String, Object> attributes; <6>
private Set<String> authorizedScopes; <5>
private Map<Class<? extends OAuth2Token>, Token<?>> tokens; <6>
private Map<String, Object> attributes; <7>
...
@@ -174,8 +175,9 @@ public class OAuth2Authorization implements Serializable {
<2> `registeredClientId`: The ID that uniquely identifies the <<registered-client, RegisteredClient>>.
<3> `principalName`: The principal name of the resource owner (or client).
<4> `authorizationGrantType`: The `AuthorizationGrantType` used.
<5> `tokens`: The `OAuth2Token` instances (and associated metadata) specific to the executed authorization grant type.
<6> `attributes`: The additional attributes specific to the executed authorization grant type for example, the authenticated `Principal`, `OAuth2AuthorizationRequest`, authorized scope(s), and others.
<5> `authorizedScopes`: The `Set` of scope(s) authorized for the client.
<6> `tokens`: The `OAuth2Token` instances (and associated metadata) specific to the executed authorization grant type.
<7> `attributes`: The additional attributes specific to the executed authorization grant type for example, the authenticated `Principal`, `OAuth2AuthorizationRequest`, and others.
`OAuth2Authorization` and its associated `OAuth2Token` instances have a set lifespan.
A newly issued `OAuth2Token` is active and becomes inactive when it either expires or is invalidated (revoked).
@@ -213,8 +215,8 @@ Alternatively, you can configure the `OAuth2AuthorizationService` through the xr
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
@@ -285,8 +287,8 @@ Alternatively, you can configure the `OAuth2AuthorizationConsentService` through
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
@@ -316,7 +318,7 @@ public interface OAuth2TokenContext extends Context {
default <T extends Authentication> T getPrincipal() ... <2>
default ProviderContext getProviderContext() ... <3>
default AuthorizationServerContext getAuthorizationServerContext() ... <3>
@Nullable
default OAuth2Authorization getAuthorization() ... <4>
@@ -335,7 +337,7 @@ public interface OAuth2TokenContext extends Context {
----
<1> `getRegisteredClient()`: The <<registered-client, RegisteredClient>> associated with the authorization grant.
<2> `getPrincipal()`: The `Authentication` instance of the resource owner (or client).
<3> `getProviderContext()`: The xref:configuration-model.adoc#configuring-provider-settings[`ProviderContext`] object that holds information related to the provider.
<3> `getAuthorizationServerContext()`: The xref:configuration-model.adoc#configuring-authorization-server-settings[`AuthorizationServerContext`] object that holds information of the Authorization Server runtime environment.
<4> `getAuthorization()`: The <<oauth2-authorization, OAuth2Authorization>> associated with the authorization grant.
<5> `getAuthorizedScopes()`: The scope(s) authorized for the client.
<6> `getTokenType()`: The `OAuth2TokenType` to generate. The supported values are `code`, `access_token`, `refresh_token`, and `id_token`.
@@ -396,8 +398,8 @@ Alternatively, you can configure the `OAuth2TokenGenerator` through the xref:con
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
@@ -488,4 +490,4 @@ public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
If the `OAuth2TokenGenerator` is not provided as a `@Bean` or is not configured through the `OAuth2AuthorizationServerConfigurer`, an `OAuth2TokenCustomizer<JwtEncodingContext>` `@Bean` will automatically be configured with a `JwtGenerator`.
[TIP]
For an example showing how you can xref:guides/how-to-userinfo.adoc#customize-id-token[customize the ID token], see the guide xref:guides/how-to-userinfo.adoc#how-to-userinfo[How-to: Customize the OpenID Connect 1.0 UserInfo response].
For an example showing how you can xref:guides/how-to-userinfo.adoc#customize-id-token[customize the ID token], see the guide xref:guides/how-to-userinfo.adoc#how-to-userinfo[How-to: Customize the OpenID Connect 1.0 UserInfo response].

View File

@@ -11,7 +11,7 @@ repositories {
}
dependencies {
implementation platform("org.springframework.boot:spring-boot-dependencies:2.7.0")
implementation platform("org.springframework.boot:spring-boot-dependencies:2.7.5")
implementation "org.springframework.boot:spring-boot-starter-web"
implementation "org.springframework.boot:spring-boot-starter-thymeleaf"
implementation "org.springframework.boot:spring-boot-starter-security"

View File

@@ -32,18 +32,21 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
@@ -56,6 +59,8 @@ public class SecurityConfig {
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
// @formatter:off
http
// Redirect to the login page when not authenticated from the
@@ -63,7 +68,9 @@ public class SecurityConfig {
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(
new LoginUrlAuthenticationEntryPoint("/login"))
);
)
// Accept access tokens for User Info and/or Client Registration
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
// @formatter:on
return http.build();
@@ -112,6 +119,7 @@ public class SecurityConfig {
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.scope("message.read")
.scope("message.write")
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
@@ -150,8 +158,13 @@ public class SecurityConfig {
}
@Bean // <7>
public ProviderSettings providerSettings() {
return ProviderSettings.builder().build();
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
@Bean // <8>
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
}
}

View File

@@ -31,6 +31,8 @@ public class Authorization {
private String registeredClientId;
private String principalName;
private String authorizationGrantType;
@Column(length = 1000)
private String authorizedScopes;
@Column(length = 4000)
private String attributes;
@Column(length = 500)
@@ -101,6 +103,14 @@ public class Authorization {
this.authorizationGrantType = authorizationGrantType;
}
public String getAuthorizedScopes() {
return this.authorizedScopes;
}
public void setAuthorizedScopes(String authorizedScopes) {
this.authorizedScopes = authorizedScopes;
}
public String getAttributes() {
return attributes;
}

View File

@@ -31,14 +31,14 @@ import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
@@ -115,6 +115,7 @@ public class JpaOAuth2AuthorizationService implements OAuth2AuthorizationService
.id(entity.getId())
.principalName(entity.getPrincipalName())
.authorizationGrantType(resolveAuthorizationGrantType(entity.getAuthorizationGrantType()))
.authorizedScopes(StringUtils.commaDelimitedListToSet(entity.getAuthorizedScopes()))
.attributes(attributes -> attributes.putAll(parseMap(entity.getAttributes())));
if (entity.getState() != null) {
builder.attribute(OAuth2ParameterNames.STATE, entity.getState());
@@ -164,6 +165,7 @@ public class JpaOAuth2AuthorizationService implements OAuth2AuthorizationService
entity.setRegisteredClientId(authorization.getRegisteredClientId());
entity.setPrincipalName(authorization.getPrincipalName());
entity.setAuthorizationGrantType(authorization.getAuthorizationGrantType().getValue());
entity.setAuthorizedScopes(StringUtils.collectionToDelimitedString(authorization.getAuthorizedScopes(), ","));
entity.setAttributes(writeMap(authorization.getAttributes()));
entity.setState(authorization.getAttribute(OAuth2ParameterNames.STATE));

View File

@@ -31,9 +31,9 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

View File

@@ -32,7 +32,6 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
@@ -44,8 +43,10 @@ import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
@@ -57,6 +58,8 @@ public class EnableUserInfoSecurityConfig {
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
// @formatter:off
http
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) // <2>
@@ -158,8 +161,8 @@ public class EnableUserInfoSecurityConfig {
}
@Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder().build();
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
}
// @fold:off

View File

@@ -66,7 +66,6 @@ public class OidcUserInfoService {
.zoneinfo("Europe/Paris")
.locale("en-US")
.phoneNumber("+1 (604) 555-1234;ext=5678")
.phoneNumberVerified("false")
.claim("address", Collections.singletonMap("formatted", "Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance"))
.updatedAt("1970-01-01T00:00:00Z")
.build()

View File

@@ -17,7 +17,7 @@ package sample.userinfo.jwt;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;

View File

@@ -33,8 +33,6 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
@@ -47,10 +45,12 @@ import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@@ -63,8 +63,8 @@ public class JwtUserInfoMapperSecurityConfig {
@Bean // <1>
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
RequestMatcher endpointsMatcher = authorizationServerConfigurer
.getEndpointsMatcher();
@@ -182,8 +182,8 @@ public class JwtUserInfoMapperSecurityConfig {
}
@Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder().build();
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
}
// @fold:off

View File

@@ -30,8 +30,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationConsentService;
@@ -40,9 +38,11 @@ import org.springframework.security.oauth2.server.authorization.OAuth2Authorizat
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.util.StringUtils;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.jose;
import java.math.BigInteger;
@@ -36,12 +35,7 @@ import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* TODO
* This class is a straight copy from Spring Security.
* It should be removed when merging this codebase into Spring Security.
*
* @author Joe Grandja
* @since 5.2
*/
public final class TestKeys {

View File

@@ -36,10 +36,12 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.jwt.JwtDecoder;
@@ -47,8 +49,14 @@ import org.springframework.security.oauth2.server.authorization.OAuth2Authorizat
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.util.StringUtils;
@@ -133,9 +141,25 @@ public class JpaTests {
@EnableWebSecurity
@EnableAutoConfiguration
@ComponentScan
@Import(OAuth2AuthorizationServerConfiguration.class)
static class AuthorizationServerConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
// @formatter:off
http
.exceptionHandling(exceptions ->
exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
// @formatter:on
return http.build();
}
@Bean
public JWKSource<SecurityContext> jwkSource() {
JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
@@ -147,6 +171,11 @@ public class JpaTests {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.test;
import java.io.Closeable;
@@ -44,11 +43,7 @@ import org.springframework.web.filter.OncePerRequestFilter;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
/**
* TODO
* This class is a straight copy from Spring Security.
*
* @author Rob Winch
* @since 5.0
*/
public class SpringTestContext implements Closeable {
@@ -100,12 +95,6 @@ public class SpringTestContext implements Closeable {
return this;
}
// public SpringTestContext context(String configuration) {
// InMemoryXmlWebApplicationContext context = new InMemoryXmlWebApplicationContext(configuration);
// this.context = context;
// return this;
// }
public SpringTestContext mockMvcAfterSpringSecurityOk() {
return addFilter(new OncePerRequestFilter() {
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.test;
import java.lang.reflect.Field;
@@ -27,8 +26,7 @@ import org.junit.jupiter.api.extension.ExtensionContext;
import org.springframework.security.test.context.TestSecurityContextHolder;
/**
* TODO
* This class is a straight copy from Spring Security.
* @author Rob Winch
*/
public class SpringTestContextExtension implements BeforeEachCallback, AfterEachCallback {

View File

@@ -127,7 +127,6 @@ public class EnableUserInfoSecurityConfigTests {
jsonPath("zoneinfo").value("Europe/Paris"),
jsonPath("locale").value("en-US"),
jsonPath("phone_number").value("+1 (604) 555-1234;ext=5678"),
jsonPath("phone_number_verified").value("false"),
jsonPath("address.formatted").value("Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance"),
jsonPath("updated_at").value("1970-01-01T00:00:00Z")
);

View File

@@ -21,7 +21,7 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
/**
* @author Steve Riesenberg

View File

@@ -55,4 +55,5 @@ This is a minimal configuration for getting started quickly. To understand what
<4> An instance of xref:core-model-components.adoc#registered-client-repository[`RegisteredClientRepository`] for managing clients.
<5> An instance of `com.nimbusds.jose.jwk.source.JWKSource` for signing access tokens.
<6> An instance of `java.security.KeyPair` with keys generated on startup used to create the `JWKSource` above.
<7> An instance of xref:configuration-model#configuring-provider-settings[`ProviderSettings`] to configure Spring Authorization Server.
<7> An instance of {spring-security-api-base-url}/org/springframework/security/oauth2/jwt/JwtDecoder.html[`JwtDecoder`] for decoding signed access tokens.
<8> An instance of xref:configuration-model#configuring-authorization-server-settings[`AuthorizationServerSettings`] to configure Spring Authorization Server.

View File

@@ -6,7 +6,7 @@ This site contains reference documentation and how-to guides for Spring Authoriz
[[introducing-spring-authorization-server]]
== Introducing Spring Authorization Server
Spring Authorization Server is a framework that provides implementations of the https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05[OAuth 2.1] and https://openid.net/specs/openid-connect-core-1_0.html[OpenID Connect 1.0] specifications and other related specifications.
Spring Authorization Server is a framework that provides implementations of the https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07[OAuth 2.1] and https://openid.net/specs/openid-connect-core-1_0.html[OpenID Connect 1.0] specifications and other related specifications.
It is built on top of https://spring.io/projects/spring-security[Spring Security] to provide a secure, light-weight, and customizable foundation for building OpenID Connect 1.0 Identity Providers and OAuth2 Authorization Server products.
[[feature-list]]
@@ -25,10 +25,10 @@ Spring Authorization Server supports the following features:
* Client Credentials
* Refresh Token
|
* The OAuth 2.1 Authorization Framework (https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05[draft])
** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05#section-4.1[Authorization Code Grant]
** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05#section-4.2[Client Credentials Grant]
** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05#section-4.3[Refresh Token Grant]
* The OAuth 2.1 Authorization Framework (https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07[draft])
** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07#section-4.1[Authorization Code Grant]
** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07#section-4.2[Client Credentials Grant]
** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07#section-4.3[Refresh Token Grant]
* OpenID Connect Core 1.0 (https://openid.net/specs/openid-connect-core-1_0.html[spec])
** https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth[Authorization Code Flow]
@@ -48,7 +48,7 @@ Spring Authorization Server supports the following features:
* `private_key_jwt`
* `none` (public clients)
|
* The OAuth 2.1 Authorization Framework (https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05#section-2.4[Client Authentication])
* The OAuth 2.1 Authorization Framework (https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07#section-2.4[Client Authentication])
* JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication (https://tools.ietf.org/html/rfc7523[RFC 7523])
* Proof Key for Code Exchange by OAuth Public Clients (PKCE) (https://tools.ietf.org/html/rfc7636[RFC 7636])
@@ -64,9 +64,9 @@ Spring Authorization Server supports the following features:
* xref:protocol-endpoints.adoc#oidc-user-info-endpoint[OpenID Connect 1.0 UserInfo Endpoint]
* xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration Endpoint]
|
* The OAuth 2.1 Authorization Framework (https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05[draft])
** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05#section-3.1[Authorization Endpoint]
** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-05#section-3.2[Token Endpoint]
* The OAuth 2.1 Authorization Framework (https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07[draft])
** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07#section-3.1[Authorization Endpoint]
** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07#section-3.2[Token Endpoint]
* OAuth 2.0 Token Introspection (https://tools.ietf.org/html/rfc7662[RFC 7662])
* OAuth 2.0 Token Revocation (https://tools.ietf.org/html/rfc7009[RFC 7009])
* OAuth 2.0 Authorization Server Metadata (https://tools.ietf.org/html/rfc8414[RFC 8414])

View File

@@ -13,39 +13,113 @@ It defines extension points that let you customize the pre-processing, main proc
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.authorizationEndpoint(authorizationEndpoint ->
authorizationEndpoint
.authorizationRequestConverter(authorizationRequestConverter) <1>
.authenticationProvider(authenticationProvider) <2>
.authorizationResponseHandler(authorizationResponseHandler) <3>
.errorResponseHandler(errorResponseHandler) <4>
.consentPage("/oauth2/v1/authorize") <5>
.authorizationRequestConverters(authorizationRequestConvertersConsumer) <2>
.authenticationProvider(authenticationProvider) <3>
.authenticationProviders(authenticationProvidersConsumer) <4>
.authorizationResponseHandler(authorizationResponseHandler) <5>
.errorResponseHandler(errorResponseHandler) <6>
.consentPage("/oauth2/v1/authorize") <7>
);
return http.build();
}
----
<1> `authorizationRequestConverter()`: The `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1[OAuth2 authorization request] (or consent) from `HttpServletRequest` to an instance of `OAuth2AuthorizationCodeRequestAuthenticationToken`.
<2> `authenticationProvider()`: The `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2AuthorizationCodeRequestAuthenticationToken`. (One or more may be added to replace the defaults.)
<3> `authorizationResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OAuth2AuthorizationCodeRequestAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2[OAuth2AuthorizationResponse].
<4> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthorizationCodeRequestAuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1[OAuth2Error response].
<5> `consentPage()`: The `URI` of the custom consent page to redirect resource owners to if consent is required during the authorization request flow.
<1> `authorizationRequestConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1[OAuth2 authorization request] (or consent) from `HttpServletRequest` to an instance of `OAuth2AuthorizationCodeRequestAuthenticationToken` or `OAuth2AuthorizationConsentAuthenticationToken`.
<2> `authorizationRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2AuthorizationCodeRequestAuthenticationToken` or `OAuth2AuthorizationConsentAuthenticationToken`.
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
<5> `authorizationResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OAuth2AuthorizationCodeRequestAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2[OAuth2AuthorizationResponse].
<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthorizationCodeRequestAuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1[OAuth2Error response].
<7> `consentPage()`: The `URI` of the custom consent page to redirect resource owners to if consent is required during the authorization request flow.
`OAuth2AuthorizationEndpointConfigurer` configures the `OAuth2AuthorizationEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OAuth2AuthorizationEndpointFilter` is the `Filter` that processes OAuth2 authorization requests (and consents).
`OAuth2AuthorizationEndpointFilter` is configured with the following defaults:
* `*AuthenticationConverter*` -- An `OAuth2AuthorizationCodeRequestAuthenticationConverter`.
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OAuth2AuthorizationCodeRequestAuthenticationProvider`.
* `*AuthenticationConverter*` -- A `DelegatingAuthenticationConverter` composed of `OAuth2AuthorizationCodeRequestAuthenticationConverter` and `OAuth2AuthorizationConsentAuthenticationConverter`.
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OAuth2AuthorizationCodeRequestAuthenticationProvider` and `OAuth2AuthorizationConsentAuthenticationProvider`.
* `*AuthenticationSuccessHandler*` -- An internal implementation that handles an "`authenticated`" `OAuth2AuthorizationCodeRequestAuthenticationToken` and returns the `OAuth2AuthorizationResponse`.
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthorizationCodeRequestAuthenticationException` and returns the `OAuth2Error` response.
[[oauth2-authorization-endpoint-customizing-authorization-request-validation]]
=== Customizing Authorization Request Validation
`OAuth2AuthorizationCodeRequestAuthenticationValidator` is the default validator used for validating specific OAuth2 authorization request parameters used in the Authorization Code Grant.
The default implementation validates the `redirect_uri` and `scope` parameters.
If validation fails, an `OAuth2AuthorizationCodeRequestAuthenticationException` is thrown.
`OAuth2AuthorizationCodeRequestAuthenticationProvider` provides the ability to override the default authorization request validation by supplying a custom authentication validator of type `Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext>` to `setAuthenticationValidator()`.
[TIP]
`OAuth2AuthorizationCodeRequestAuthenticationContext` holds the `OAuth2AuthorizationCodeRequestAuthenticationToken`, which contains the OAuth2 authorization request parameters.
[IMPORTANT]
If validation fails, the authentication validator *MUST* throw `OAuth2AuthorizationCodeRequestAuthenticationException`.
A common use case during the development life cycle phase is to allow for `localhost` in the `redirect_uri` parameter.
The following example shows how to configure `OAuth2AuthorizationCodeRequestAuthenticationProvider` with a custom authentication validator that allows for `localhost` in the `redirect_uri` parameter:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.authorizationEndpoint(authorizationEndpoint ->
authorizationEndpoint
.authenticationProviders(configureAuthenticationValidator())
);
return http.build();
}
private Consumer<List<AuthenticationProvider>> configureAuthenticationValidator() {
return (authenticationProviders) ->
authenticationProviders.forEach((authenticationProvider) -> {
if (authenticationProvider instanceof OAuth2AuthorizationCodeRequestAuthenticationProvider) {
Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator =
// Override default redirect_uri validator
new CustomRedirectUriValidator()
// Reuse default scope validator
.andThen(OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_SCOPE_VALIDATOR);
((OAuth2AuthorizationCodeRequestAuthenticationProvider) authenticationProvider)
.setAuthenticationValidator(authenticationValidator);
}
});
}
static class CustomRedirectUriValidator implements Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> {
@Override
public void accept(OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) {
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
authenticationContext.getAuthentication();
RegisteredClient registeredClient = authenticationContext.getRegisteredClient();
String requestedRedirectUri = authorizationCodeRequestAuthentication.getRedirectUri();
// Use exact string matching when comparing client redirect URIs against pre-registered URIs
if (!registeredClient.getRedirectUris().contains(requestedRedirectUri)) {
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST);
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null);
}
}
}
----
[[oauth2-token-endpoint]]
== OAuth2 Token Endpoint
@@ -57,26 +131,30 @@ It defines extension points that let you customize the pre-processing, main proc
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.tokenEndpoint(tokenEndpoint ->
tokenEndpoint
.accessTokenRequestConverter(accessTokenRequestConverter) <1>
.authenticationProvider(authenticationProvider) <2>
.accessTokenResponseHandler(accessTokenResponseHandler) <3>
.errorResponseHandler(errorResponseHandler) <4>
.accessTokenRequestConverters(accessTokenRequestConvertersConsumer) <2>
.authenticationProvider(authenticationProvider) <3>
.authenticationProviders(authenticationProvidersConsumer) <4>
.accessTokenResponseHandler(accessTokenResponseHandler) <5>
.errorResponseHandler(errorResponseHandler) <6>
);
return http.build();
}
----
<1> `accessTokenRequestConverter()`: The `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3[OAuth2 access token request] from `HttpServletRequest` to an instance of `OAuth2AuthorizationGrantAuthenticationToken`.
<2> `authenticationProvider()`: The `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2AuthorizationGrantAuthenticationToken`. (One or more may be added to replace the defaults.)
<3> `accessTokenResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an `OAuth2AccessTokenAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-5.1[`OAuth2AccessTokenResponse`].
<4> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-5.2[OAuth2Error response].
<1> `accessTokenRequestConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3[OAuth2 access token request] from `HttpServletRequest` to an instance of `OAuth2AuthorizationGrantAuthenticationToken`.
<2> `accessTokenRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2AuthorizationGrantAuthenticationToken`.
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
<5> `accessTokenResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an `OAuth2AccessTokenAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-5.1[`OAuth2AccessTokenResponse`].
<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-5.2[OAuth2Error response].
`OAuth2TokenEndpointConfigurer` configures the `OAuth2TokenEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OAuth2TokenEndpointFilter` is the `Filter` that processes OAuth2 access token requests.
@@ -102,33 +180,37 @@ It defines extension points that let you customize the pre-processing, main proc
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.tokenIntrospectionEndpoint(tokenIntrospectionEndpoint ->
tokenIntrospectionEndpoint
.introspectionRequestConverter(introspectionRequestConverter) <1>
.authenticationProvider(authenticationProvider) <2>
.introspectionResponseHandler(introspectionResponseHandler) <3>
.errorResponseHandler(errorResponseHandler) <4>
.introspectionRequestConverters(introspectionRequestConvertersConsumer) <2>
.authenticationProvider(authenticationProvider) <3>
.authenticationProviders(authenticationProvidersConsumer) <4>
.introspectionResponseHandler(introspectionResponseHandler) <5>
.errorResponseHandler(errorResponseHandler) <6>
);
return http.build();
}
----
<1> `introspectionRequestConverter()`: The `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc7662#section-2.1[OAuth2 introspection request] from `HttpServletRequest` to an instance of `OAuth2TokenIntrospectionAuthenticationToken`.
<2> `authenticationProvider()`: The `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2TokenIntrospectionAuthenticationToken`. (One or more may be added to replace the defaults.)
<3> `introspectionResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OAuth2TokenIntrospectionAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc7662#section-2.2[OAuth2TokenIntrospection response].
<4> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc7662#section-2.3[OAuth2Error response].
<1> `introspectionRequestConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc7662#section-2.1[OAuth2 introspection request] from `HttpServletRequest` to an instance of `OAuth2TokenIntrospectionAuthenticationToken`.
<2> `introspectionRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2TokenIntrospectionAuthenticationToken`.
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
<5> `introspectionResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OAuth2TokenIntrospectionAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc7662#section-2.2[OAuth2TokenIntrospection response].
<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc7662#section-2.3[OAuth2Error response].
`OAuth2TokenIntrospectionEndpointConfigurer` configures the `OAuth2TokenIntrospectionEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OAuth2TokenIntrospectionEndpointFilter` is the `Filter` that processes OAuth2 introspection requests.
`OAuth2TokenIntrospectionEndpointFilter` is configured with the following defaults:
* `*AuthenticationConverter*` -- An internal implementation that returns the `OAuth2TokenIntrospectionAuthenticationToken`.
* `*AuthenticationConverter*` -- An `OAuth2TokenIntrospectionAuthenticationConverter`.
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OAuth2TokenIntrospectionAuthenticationProvider`.
* `*AuthenticationSuccessHandler*` -- An internal implementation that handles an "`authenticated`" `OAuth2TokenIntrospectionAuthenticationToken` and returns the `OAuth2TokenIntrospection` response.
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthenticationException` and returns the `OAuth2Error` response.
@@ -145,33 +227,37 @@ It defines extension points that let you customize the pre-processing, main proc
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.tokenRevocationEndpoint(tokenRevocationEndpoint ->
tokenRevocationEndpoint
.revocationRequestConverter(revocationRequestConverter) <1>
.authenticationProvider(authenticationProvider) <2>
.revocationResponseHandler(revocationResponseHandler) <3>
.errorResponseHandler(errorResponseHandler) <4>
.revocationRequestConverter(revocationRequestConverter) <1>
.revocationRequestConverters(revocationRequestConvertersConsumer) <2>
.authenticationProvider(authenticationProvider) <3>
.authenticationProviders(authenticationProvidersConsumer) <4>
.revocationResponseHandler(revocationResponseHandler) <5>
.errorResponseHandler(errorResponseHandler) <6>
);
return http.build();
}
----
<1> `revocationRequestConverter()`: The `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc7009#section-2.1[OAuth2 revocation request] from `HttpServletRequest` to an instance of `OAuth2TokenRevocationAuthenticationToken`.
<2> `authenticationProvider()`: The `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2TokenRevocationAuthenticationToken`. (One or more may be added to replace the defaults.)
<3> `revocationResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OAuth2TokenRevocationAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc7009#section-2.2[OAuth2 revocation response].
<4> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc7009#section-2.2.1[OAuth2Error response].
<1> `revocationRequestConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc7009#section-2.1[OAuth2 revocation request] from `HttpServletRequest` to an instance of `OAuth2TokenRevocationAuthenticationToken`.
<2> `revocationRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2TokenRevocationAuthenticationToken`.
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
<5> `revocationResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OAuth2TokenRevocationAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc7009#section-2.2[OAuth2 revocation response].
<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc7009#section-2.2.1[OAuth2Error response].
`OAuth2TokenRevocationEndpointConfigurer` configures the `OAuth2TokenRevocationEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OAuth2TokenRevocationEndpointFilter` is the `Filter` that processes OAuth2 revocation requests.
`OAuth2TokenRevocationEndpointFilter` is configured with the following defaults:
* `*AuthenticationConverter*` -- An internal implementation that returns the `OAuth2TokenRevocationAuthenticationToken`.
* `*AuthenticationConverter*` -- An `OAuth2TokenRevocationAuthenticationConverter`.
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OAuth2TokenRevocationAuthenticationProvider`.
* `*AuthenticationSuccessHandler*` -- An internal implementation that handles an "`authenticated`" `OAuth2TokenRevocationAuthenticationToken` and returns the OAuth2 revocation response.
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthenticationException` and returns the `OAuth2Error` response.
@@ -179,10 +265,31 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
[[oauth2-authorization-server-metadata-endpoint]]
== OAuth2 Authorization Server Metadata Endpoint
`OAuth2AuthorizationServerConfigurer` provides support for the https://datatracker.ietf.org/doc/html/rfc8414#section-3[OAuth2 Authorization Server Metadata endpoint].
`OAuth2AuthorizationServerMetadataEndpointConfigurer` provides the ability to customize the https://datatracker.ietf.org/doc/html/rfc8414#section-3[OAuth2 Authorization Server Metadata endpoint].
It defines an extension point that lets you customize the https://datatracker.ietf.org/doc/html/rfc8414#section-3.2[OAuth2 Authorization Server Metadata response].
`OAuth2AuthorizationServerConfigurer` configures the `OAuth2AuthorizationServerMetadataEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OAuth2AuthorizationServerMetadataEndpointFilter` is the `Filter` that processes https://datatracker.ietf.org/doc/html/rfc8414#section-3.1[OAuth2 authorization server metadata requests] and returns the https://datatracker.ietf.org/doc/html/rfc8414#section-3.2[OAuth2AuthorizationServerMetadata response].
`OAuth2AuthorizationServerMetadataEndpointConfigurer` provides the following configuration option:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.authorizationServerMetadataEndpoint(authorizationServerMetadataEndpoint ->
authorizationServerMetadataEndpoint
.authorizationServerMetadataCustomizer(authorizationServerMetadataCustomizer)); <1>
return http.build();
}
----
<1> `authorizationServerMetadataCustomizer()`: The `Consumer` providing access to the `OAuth2AuthorizationServerMetadata.Builder` allowing the ability to customize the claims of the Authorization Server's configuration.
`OAuth2AuthorizationServerMetadataEndpointConfigurer` configures the `OAuth2AuthorizationServerMetadataEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OAuth2AuthorizationServerMetadataEndpointFilter` is the `Filter` that returns the https://datatracker.ietf.org/doc/html/rfc8414#section-3.2[OAuth2AuthorizationServerMetadata response].
[[jwk-set-endpoint]]
== JWK Set Endpoint
@@ -198,46 +305,87 @@ The JWK Set endpoint is configured *only* if a `JWKSource<SecurityContext>` `@Be
[[oidc-provider-configuration-endpoint]]
== OpenID Connect 1.0 Provider Configuration Endpoint
`OidcConfigurer` provides support for the https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[OpenID Connect 1.0 Provider Configuration endpoint].
`OidcProviderConfigurationEndpointConfigurer` provides the ability to customize the https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[OpenID Connect 1.0 Provider Configuration endpoint].
It defines an extension point that lets you customize the https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse[OpenID Provider Configuration response].
`OidcConfigurer` configures the `OidcProviderConfigurationEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OidcProviderConfigurationEndpointFilter` is the `Filter` that returns the https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse[OidcProviderConfiguration response].
[[oidc-user-info-endpoint]]
== OpenID Connect 1.0 UserInfo Endpoint
`OidcUserInfoEndpointConfigurer` provides the ability to customize the https://openid.net/specs/openid-connect-core-1_0.html#UserInfo[OpenID Connect 1.0 UserInfo endpoint].
It defines extension points that let you customize the https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse[UserInfo response].
`OidcUserInfoEndpointConfigurer` provides the following configuration option:
`OidcProviderConfigurationEndpointConfigurer` provides the following configuration option:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.oidc(oidc ->
oidc
.userInfoEndpoint(userInfoEndpoint ->
userInfoEndpoint.userInfoMapper(userInfoMapper) <1>
.providerConfigurationEndpoint(providerConfigurationEndpoint ->
providerConfigurationEndpoint
.providerConfigurationCustomizer(providerConfigurationCustomizer) <1>
)
);
return http.build();
}
----
<1> `userInfoMapper()`: The `Function` used to extract claims from `OidcUserInfoAuthenticationContext` to an instance of `OidcUserInfo`.
<1> `providerConfigurationCustomizer()`: The `Consumer` providing access to the `OidcProviderConfiguration.Builder` allowing the ability to customize the claims of the OpenID Provider's configuration.
`OidcProviderConfigurationEndpointConfigurer` configures the `OidcProviderConfigurationEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OidcProviderConfigurationEndpointFilter` is the `Filter` that returns the https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse[OidcProviderConfiguration response].
[[oidc-user-info-endpoint]]
== OpenID Connect 1.0 UserInfo Endpoint
`OidcUserInfoEndpointConfigurer` provides the ability to customize the https://openid.net/specs/openid-connect-core-1_0.html#UserInfo[OpenID Connect 1.0 UserInfo endpoint].
It defines extension points that let you customize the pre-processing, main processing, and post-processing logic for https://openid.net/specs/openid-connect-core-1_0.html#UserInfoRequest[UserInfo requests].
`OidcUserInfoEndpointConfigurer` provides the following configuration options:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.oidc(oidc ->
oidc
.userInfoEndpoint(userInfoEndpoint ->
userInfoEndpoint
.userInfoRequestConverter(userInfoRequestConverter) <1>
.userInfoRequestConverters(userInfoRequestConvertersConsumer) <2>
.authenticationProvider(authenticationProvider) <3>
.authenticationProviders(authenticationProvidersConsumer) <4>
.userInfoResponseHandler(userInfoResponseHandler) <5>
.errorResponseHandler(errorResponseHandler) <6>
.userInfoMapper(userInfoMapper) <7>
)
);
return http.build();
}
----
<1> `userInfoRequestConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://openid.net/specs/openid-connect-core-1_0.html#UserInfoRequest[UserInfo request] from `HttpServletRequest` to an instance of `OidcUserInfoAuthenticationToken`.
<2> `userInfoRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OidcUserInfoAuthenticationToken`.
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
<5> `userInfoResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OidcUserInfoAuthenticationToken` and returning the https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse[UserInfo response].
<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://openid.net/specs/openid-connect-core-1_0.html#UserInfoError[UserInfo Error response].
<7> `userInfoMapper()`: The `Function` used to extract claims from `OidcUserInfoAuthenticationContext` to an instance of `OidcUserInfo`.
`OidcUserInfoEndpointConfigurer` configures the `OidcUserInfoEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OidcUserInfoEndpointFilter` is the `Filter` that processes https://openid.net/specs/openid-connect-core-1_0.html#UserInfoRequest[UserInfo requests] and returns the https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse[OidcUserInfo response].
`OidcUserInfoEndpointFilter` is configured with the following defaults:
* `*AuthenticationConverter*` -- An internal implementation that obtains the `Authentication` from the `SecurityContext` and creates an `OidcUserInfoAuthenticationToken` with the principal.
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OidcUserInfoAuthenticationProvider`, which is associated with an internal implementation of `userInfoMapper` that extracts https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims[standard claims] from the https://openid.net/specs/openid-connect-core-1_0.html#IDToken[ID Token] based on the https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims[scopes requested] during authorization.
* `*AuthenticationSuccessHandler*` -- An internal implementation that handles an "`authenticated`" `OidcUserInfoAuthenticationToken` and returns the `OidcUserInfo` response.
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthenticationException` and returns the `OAuth2Error` response.
[TIP]
You can customize the ID Token by providing an xref:core-model-components.adoc#oauth2-token-customizer[`OAuth2TokenCustomizer<JwtEncodingContext>`] `@Bean`.
@@ -249,8 +397,8 @@ The following example shows how to enable the OAuth2 resource server configurati
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
...
@@ -275,26 +423,42 @@ The guide xref:guides/how-to-userinfo.adoc#how-to-userinfo[How-to: Customize the
[[oidc-client-registration-endpoint]]
== OpenID Connect 1.0 Client Registration Endpoint
`OidcClientRegistrationEndpointConfigurer` configures the https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration[OpenID Connect 1.0 Client Registration endpoint].
The following example shows how to enable (disabled by default) the OpenID Connect 1.0 Client Registration endpoint:
`OidcClientRegistrationEndpointConfigurer` provides the ability to customize the https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration[OpenID Connect 1.0 Client Registration endpoint].
It defines extension points that let you customize the pre-processing, main processing, and post-processing logic for https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationRequest[Client Registration requests] or https://openid.net/specs/openid-connect-registration-1_0.html#ReadRequest[Client Read requests].
`OidcClientRegistrationEndpointConfigurer` provides the following configuration options:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.oidc(oidc ->
oidc
.clientRegistrationEndpoint(Customizer.withDefaults())
.clientRegistrationEndpoint(clientRegistrationEndpoint ->
clientRegistrationEndpoint
.clientRegistrationRequestConverter(clientRegistrationRequestConverter) <1>
.clientRegistrationRequestConverters(clientRegistrationRequestConvertersConsumers) <2>
.authenticationProvider(authenticationProvider) <3>
.authenticationProviders(authenticationProvidersConsumer) <4>
.clientRegistrationResponseHandler(clientRegistrationResponseHandler) <5>
.errorResponseHandler(errorResponseHandler) <6>
)
);
return http.build();
}
----
<1> `clientRegistrationRequestConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract a https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationRequest[Client Registration request] or https://openid.net/specs/openid-connect-registration-1_0.html#ReadRequest[Client Read request] from `HttpServletRequest` to an instance of `OidcClientRegistrationAuthenticationToken`.
<2> `clientRegistrationRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OidcClientRegistrationAuthenticationToken`.
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
<5> `clientRegistrationResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OidcClientRegistrationAuthenticationToken` and returning the https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationResponse[Client Registration response] or https://openid.net/specs/openid-connect-registration-1_0.html#ReadResponse[Client Read response].
<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationError[Client Registration Error response] or https://openid.net/specs/openid-connect-registration-1_0.html#ReadError[Client Read Error response].
[NOTE]
The OpenID Connect 1.0 Client Registration endpoint is disabled by default because many deployments do not require dynamic client registration.
@@ -307,7 +471,10 @@ The OpenID Connect 1.0 Client Registration endpoint is disabled by default becau
`OidcClientRegistrationEndpointFilter` is configured with the following defaults:
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OidcClientRegistrationAuthenticationProvider`.
* `*AuthenticationConverter*` -- An `OidcClientRegistrationAuthenticationConverter`.
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OidcClientRegistrationAuthenticationProvider` and `OidcClientConfigurationAuthenticationProvider`.
* `*AuthenticationSuccessHandler*` -- An internal implementation that handles an "`authenticated`" `OidcClientRegistrationAuthenticationToken` and returns the `OidcClientRegistration` response.
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthenticationException` and returns the `OAuth2Error` response.
The OpenID Connect 1.0 Client Registration endpoint is an https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration[OAuth2 protected resource], which *REQUIRES* an access token to be sent as a bearer token in the Client Registration (or Client Read) request.
@@ -323,8 +490,8 @@ The following example shows how to enable the OAuth2 resource server configurati
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
...

View File

@@ -1,9 +1,9 @@
version=0.3.1
version=0.4.0
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
org.gradle.caching=true
springFrameworkVersion=5.3.20
springSecurityVersion=5.7.1
springFrameworkVersion=5.3.24
springSecurityVersion=5.8.0
springJavaformatVersion=0.0.31
springJavaformatExcludePackages=org/springframework/security/config org/springframework/security/oauth2
checkstyleToolVersion=8.34

View File

@@ -21,7 +21,7 @@ dependencies {
testImplementation "org.springframework.security:spring-security-test"
testImplementation "org.springframework:spring-webmvc"
testImplementation "junit:junit"
testImplementation "org.junit.jupiter:junit-jupiter"
testImplementation "org.assertj:assertj-core"
testImplementation "org.mockito:mockito-core"
testImplementation "com.jayway.jsonpath:json-path"

View File

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

View File

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

View File

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

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core;
package org.springframework.security.oauth2.server.authorization;
import java.io.Serializable;
import java.net.URI;
@@ -25,6 +25,7 @@ import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.springframework.security.oauth2.server.authorization.util.SpringAuthorizationServerVersion;
import org.springframework.util.Assert;
/**
@@ -39,7 +40,7 @@ import org.springframework.util.Assert;
* @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 static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
private final Map<String, Object> claims;
protected AbstractOAuth2AuthorizationServerMetadata(Map<String, Object> claims) {
@@ -273,6 +274,17 @@ public abstract class AbstractOAuth2AuthorizationServerMetadata implements OAuth
return getThis();
}
/**
* Use this {@code registration_endpoint} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
*
* @param clientRegistrationEndpoint the {@code URL} of the OAuth 2.0 Dynamic Client Registration Endpoint
* @return the {@link AbstractBuilder} for further configuration
* @since 0.4.0
*/
public B clientRegistrationEndpoint(String clientRegistrationEndpoint) {
return claim(OAuth2AuthorizationServerMetadataClaimNames.REGISTRATION_ENDPOINT, clientRegistrationEndpoint);
}
/**
* 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.
@@ -368,6 +380,9 @@ public abstract class AbstractOAuth2AuthorizationServerMetadata implements OAuth
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.REGISTRATION_ENDPOINT) != null) {
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.REGISTRATION_ENDPOINT), "clientRegistrationEndpoint must be a valid URL");
}
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");

View File

@@ -24,9 +24,7 @@ import java.util.concurrent.ConcurrentHashMap;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.util.Assert;

View File

@@ -49,10 +49,8 @@ import org.springframework.lang.Nullable;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
@@ -87,6 +85,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
+ "registered_client_id, "
+ "principal_name, "
+ "authorization_grant_type, "
+ "authorized_scopes, "
+ "attributes, "
+ "state, "
+ "authorization_code_value, "
@@ -128,12 +127,12 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
// @formatter:off
private static final String SAVE_AUTHORIZATION_SQL = "INSERT INTO " + TABLE_NAME
+ " (" + COLUMN_NAMES + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+ " (" + COLUMN_NAMES + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
// @formatter:on
// @formatter:off
private static final String UPDATE_AUTHORIZATION_SQL = "UPDATE " + TABLE_NAME
+ " SET registered_client_id = ?, principal_name = ?, authorization_grant_type = ?, attributes = ?, state = ?,"
+ " SET registered_client_id = ?, principal_name = ?, authorization_grant_type = ?, authorized_scopes = ?, attributes = ?, state = ?,"
+ " authorization_code_value = ?, authorization_code_issued_at = ?, authorization_code_expires_at = ?, authorization_code_metadata = ?,"
+ " access_token_value = ?, access_token_issued_at = ?, access_token_expires_at = ?, access_token_metadata = ?, access_token_type = ?, access_token_scopes = ?,"
+ " oidc_id_token_value = ?, oidc_id_token_issued_at = ?, oidc_id_token_expires_at = ?, oidc_id_token_metadata = ?,"
@@ -344,11 +343,17 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
String id = rs.getString("id");
String principalName = rs.getString("principal_name");
String authorizationGrantType = rs.getString("authorization_grant_type");
Set<String> authorizedScopes = Collections.emptySet();
String authorizedScopesString = rs.getString("authorized_scopes");
if (authorizedScopesString != null) {
authorizedScopes = StringUtils.commaDelimitedListToSet(authorizedScopesString);
}
Map<String, Object> attributes = parseMap(getLobValue(rs, "attributes"));
builder.id(id)
.principalName(principalName)
.authorizationGrantType(new AuthorizationGrantType(authorizationGrantType))
.authorizedScopes(authorizedScopes)
.attributes((attrs) -> attrs.putAll(attributes));
String state = rs.getString("state");
@@ -487,6 +492,12 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
parameters.add(new SqlParameterValue(Types.VARCHAR, authorization.getPrincipalName()));
parameters.add(new SqlParameterValue(Types.VARCHAR, authorization.getAuthorizationGrantType().getValue()));
String authorizedScopes = null;
if (!CollectionUtils.isEmpty(authorization.getAuthorizedScopes())) {
authorizedScopes = StringUtils.collectionToDelimitedString(authorization.getAuthorizedScopes(), ",");
}
parameters.add(new SqlParameterValue(Types.VARCHAR, authorizedScopes));
String attributes = writeMap(authorization.getAttributes());
parameters.add(mapToSqlParameter("attributes", attributes));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,8 +19,10 @@ import java.io.Serializable;
import java.time.Instant;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
@@ -29,8 +31,8 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.core.Version;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.util.SpringAuthorizationServerVersion;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@@ -50,19 +52,12 @@ import org.springframework.util.StringUtils;
* @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 static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
private String id;
private String registeredClientId;
private String principalName;
private AuthorizationGrantType authorizationGrantType;
private Set<String> authorizedScopes;
private Map<Class<? extends OAuth2Token>, Token<?>> tokens;
private Map<String, Object> attributes;
@@ -105,6 +100,16 @@ public class OAuth2Authorization implements Serializable {
return this.authorizationGrantType;
}
/**
* Returns the authorized scope(s).
*
* @return the {@code Set} of authorized scope(s)
* @since 0.4.0
*/
public Set<String> getAuthorizedScopes() {
return this.authorizedScopes;
}
/**
* Returns the {@link Token} of type {@link OAuth2AccessToken}.
*
@@ -194,6 +199,7 @@ public class OAuth2Authorization implements Serializable {
Objects.equals(this.registeredClientId, that.registeredClientId) &&
Objects.equals(this.principalName, that.principalName) &&
Objects.equals(this.authorizationGrantType, that.authorizationGrantType) &&
Objects.equals(this.authorizedScopes, that.authorizedScopes) &&
Objects.equals(this.tokens, that.tokens) &&
Objects.equals(this.attributes, that.attributes);
}
@@ -201,7 +207,7 @@ public class OAuth2Authorization implements Serializable {
@Override
public int hashCode() {
return Objects.hash(this.id, this.registeredClientId, this.principalName,
this.authorizationGrantType, this.tokens, this.attributes);
this.authorizationGrantType, this.authorizedScopes, this.tokens, this.attributes);
}
/**
@@ -227,6 +233,7 @@ public class OAuth2Authorization implements Serializable {
.id(authorization.getId())
.principalName(authorization.getPrincipalName())
.authorizationGrantType(authorization.getAuthorizationGrantType())
.authorizedScopes(authorization.getAuthorizedScopes())
.tokens(authorization.tokens)
.attributes(attrs -> attrs.putAll(authorization.getAttributes()));
}
@@ -238,7 +245,7 @@ public class OAuth2Authorization implements Serializable {
* @since 0.1.0
*/
public static class Token<T extends OAuth2Token> implements Serializable {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
protected static final String TOKEN_METADATA_NAMESPACE = "metadata.token.";
/**
@@ -375,11 +382,12 @@ public class OAuth2Authorization implements Serializable {
* A builder for {@link OAuth2Authorization}.
*/
public static class Builder implements Serializable {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
private String id;
private final String registeredClientId;
private String principalName;
private AuthorizationGrantType authorizationGrantType;
private Set<String> authorizedScopes;
private Map<Class<? extends OAuth2Token>, Token<?>> tokens = new HashMap<>();
private final Map<String, Object> attributes = new HashMap<>();
@@ -420,6 +428,18 @@ public class OAuth2Authorization implements Serializable {
return this;
}
/**
* Sets the authorized scope(s).
*
* @param authorizedScopes the {@code Set} of authorized scope(s)
* @return the {@link Builder}
* @since 0.4.0
*/
public Builder authorizedScopes(Set<String> authorizedScopes) {
this.authorizedScopes = authorizedScopes;
return this;
}
/**
* Sets the {@link OAuth2AccessToken access token}.
*
@@ -522,6 +542,12 @@ public class OAuth2Authorization implements Serializable {
authorization.registeredClientId = this.registeredClientId;
authorization.principalName = this.principalName;
authorization.authorizationGrantType = this.authorizationGrantType;
authorization.authorizedScopes =
Collections.unmodifiableSet(
!CollectionUtils.isEmpty(this.authorizedScopes) ?
new HashSet<>(this.authorizedScopes) :
new HashSet<>()
);
authorization.tokens = Collections.unmodifiableMap(this.tokens);
authorization.attributes = Collections.unmodifiableMap(this.attributes);
return authorization;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,10 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core;
package org.springframework.security.oauth2.server.authorization;
import java.time.Instant;
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
/**
* An implementation of an {@link AbstractOAuth2Token}
* representing an OAuth 2.0 Authorization Code Grant.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,8 +25,8 @@ import java.util.function.Consumer;
import org.springframework.lang.NonNull;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.core.Version;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.util.SpringAuthorizationServerVersion;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@@ -43,7 +43,7 @@ import org.springframework.util.CollectionUtils;
* @since 0.1.2
*/
public final class OAuth2AuthorizationConsent implements Serializable {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
private static final String AUTHORITIES_SCOPE_PREFIX = "SCOPE_";
private final String registeredClientId;
@@ -152,7 +152,7 @@ public final class OAuth2AuthorizationConsent implements Serializable {
* A builder for {@link OAuth2AuthorizationConsent}.
*/
public static final class Builder implements Serializable {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
private final String registeredClientId;
private final String principalName;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core;
package org.springframework.security.oauth2.server.authorization;
import java.util.Map;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core;
package org.springframework.security.oauth2.server.authorization;
import java.net.URL;
import java.util.List;
import org.springframework.security.oauth2.core.ClaimAccessor;
/**
* 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.
@@ -139,6 +141,16 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc
return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED);
}
/**
* Returns the {@code URL} of the OAuth 2.0 Dynamic Client Registration Endpoint {@code (registration_endpoint)}.
*
* @return the {@code URL} of the OAuth 2.0 Dynamic Client Registration Endpoint
* @since 0.4.0
*/
default URL getClientRegistrationEndpoint() {
return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.REGISTRATION_ENDPOINT);
}
/**
* Returns the Proof Key for Code Exchange (PKCE) {@code code_challenge_method} values supported {@code (code_challenge_methods_supported)}.
*

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core;
package org.springframework.security.oauth2.server.authorization;
/**
* The names of the "claims" an Authorization Server describes about its configuration,
@@ -86,6 +86,12 @@ public class OAuth2AuthorizationServerMetadataClaimNames {
*/
public static final String INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED = "introspection_endpoint_auth_methods_supported";
/**
* {@code registration_endpoint} - the {@code URL} of the OAuth 2.0 Dynamic Client Registration Endpoint
* @since 0.4.0
*/
public static final String REGISTRATION_ENDPOINT = "registration_endpoint";
/**
* {@code code_challenge_methods_supported} - the Proof Key for Code Exchange (PKCE) {@code code_challenge_method} values supported
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.security.oauth2.core;
package org.springframework.security.oauth2.server.authorization;
import java.io.Serializable;
import java.net.URI;
@@ -27,6 +27,9 @@ import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimAccessor;
import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames;
import org.springframework.security.oauth2.server.authorization.util.SpringAuthorizationServerVersion;
import org.springframework.util.Assert;
/**
@@ -39,7 +42,7 @@ import org.springframework.util.Assert;
* @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 static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
private final Map<String, Object> claims;
private OAuth2TokenIntrospection(Map<String, Object> claims) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,10 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core;
package org.springframework.security.oauth2.server.authorization;
import java.io.Serializable;
import org.springframework.security.oauth2.server.authorization.util.SpringAuthorizationServerVersion;
import org.springframework.util.Assert;
/**
@@ -27,7 +28,7 @@ import org.springframework.util.Assert;
* @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 OAuth2TokenType implements Serializable {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
public static final OAuth2TokenType ACCESS_TOKEN = new OAuth2TokenType("access_token");
public static final OAuth2TokenType REFRESH_TOKEN = new OAuth2TokenType("refresh_token");
private final String value;

View File

@@ -15,6 +15,11 @@
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import java.time.Instant;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
@@ -45,6 +50,7 @@ import org.springframework.util.Assert;
*/
public final class ClientSecretAuthenticationProvider implements AuthenticationProvider {
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-3.2.1";
private final Log logger = LogFactory.getLog(getClass());
private final RegisteredClientRepository registeredClientRepository;
private final CodeVerifierAuthenticator codeVerifierAuthenticator;
private PasswordEncoder passwordEncoder;
@@ -93,6 +99,10 @@ public final class ClientSecretAuthenticationProvider implements AuthenticationP
throwInvalidClient(OAuth2ParameterNames.CLIENT_ID);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Retrieved registered client");
}
if (!registeredClient.getClientAuthenticationMethods().contains(
clientAuthentication.getClientAuthenticationMethod())) {
throwInvalidClient("authentication_method");
@@ -107,9 +117,22 @@ public final class ClientSecretAuthenticationProvider implements AuthenticationP
throwInvalidClient(OAuth2ParameterNames.CLIENT_SECRET);
}
if (registeredClient.getClientSecretExpiresAt() != null &&
Instant.now().isAfter(registeredClient.getClientSecretExpiresAt())) {
throwInvalidClient("client_secret_expires_at");
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Validated client authentication parameters");
}
// Validate the "code_verifier" parameter for the confidential client, if available
this.codeVerifierAuthenticator.authenticateIfAvailable(clientAuthentication, registeredClient);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Authenticated client secret");
}
return new OAuth2ClientAuthenticationToken(registeredClient,
clientAuthentication.getClientAuthenticationMethod(), clientAuthentication.getCredentials());
}

View File

@@ -21,16 +21,19 @@ import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -47,6 +50,7 @@ import org.springframework.util.StringUtils;
*/
final class CodeVerifierAuthenticator {
private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE);
private final Log logger = LogFactory.getLog(getClass());
private final OAuth2AuthorizationService authorizationService;
CodeVerifierAuthenticator(OAuth2AuthorizationService authorizationService) {
@@ -81,6 +85,10 @@ final class CodeVerifierAuthenticator {
throwInvalidGrant(OAuth2ParameterNames.CODE);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Retrieved authorization with authorization code");
}
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
OAuth2AuthorizationRequest.class.getName());
@@ -90,10 +98,17 @@ final class CodeVerifierAuthenticator {
if (registeredClient.getClientSettings().isRequireProofKey()) {
throwInvalidGrant(PkceParameterNames.CODE_CHALLENGE);
} else {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not authenticate code verifier since requireProofKey=false");
}
return false;
}
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Validated code verifier parameters");
}
String codeChallengeMethod = (String) authorizationRequest.getAdditionalParameters()
.get(PkceParameterNames.CODE_CHALLENGE_METHOD);
String codeVerifier = (String) parameters.get(PkceParameterNames.CODE_VERIFIER);
@@ -101,6 +116,10 @@ final class CodeVerifierAuthenticator {
throwInvalidGrant(PkceParameterNames.CODE_VERIFIER);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Authenticated code verifier");
}
return true;
}

View File

@@ -15,53 +15,30 @@
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
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.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.security.oauth2.jwt.JwtClaimValidator;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
import org.springframework.security.oauth2.jwt.JwtException;
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.context.ProviderContext;
import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;
/**
* An {@link AuthenticationProvider} implementation used for OAuth 2.0 Client Authentication,
* which authenticates the (JWT) {@link OAuth2ParameterNames#CLIENT_ASSERTION client_assertion} parameter.
* which authenticates the {@link Jwt} {@link OAuth2ParameterNames#CLIENT_ASSERTION client_assertion} parameter.
*
* @author Rafal Lewczuk
* @author Joe Grandja
@@ -70,14 +47,16 @@ import org.springframework.web.util.UriComponentsBuilder;
* @see OAuth2ClientAuthenticationToken
* @see RegisteredClientRepository
* @see OAuth2AuthorizationService
* @see JwtClientAssertionDecoderFactory
*/
public final class JwtClientAssertionAuthenticationProvider implements AuthenticationProvider {
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-3.2.1";
private static final ClientAuthenticationMethod JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD =
new ClientAuthenticationMethod("urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
private final Log logger = LogFactory.getLog(getClass());
private final RegisteredClientRepository registeredClientRepository;
private final CodeVerifierAuthenticator codeVerifierAuthenticator;
private final JwtClientAssertionDecoderFactory jwtClientAssertionDecoderFactory;
private JwtDecoderFactory<RegisteredClient> jwtDecoderFactory;
/**
* Constructs a {@code JwtClientAssertionAuthenticationProvider} using the provided parameters.
@@ -91,7 +70,7 @@ public final class JwtClientAssertionAuthenticationProvider implements Authentic
Assert.notNull(authorizationService, "authorizationService cannot be null");
this.registeredClientRepository = registeredClientRepository;
this.codeVerifierAuthenticator = new CodeVerifierAuthenticator(authorizationService);
this.jwtClientAssertionDecoderFactory = new JwtClientAssertionDecoderFactory();
this.jwtDecoderFactory = new JwtClientAssertionDecoderFactory();
}
@Override
@@ -109,6 +88,10 @@ public final class JwtClientAssertionAuthenticationProvider implements Authentic
throwInvalidClient(OAuth2ParameterNames.CLIENT_ID);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Retrieved registered client");
}
if (!registeredClient.getClientAuthenticationMethods().contains(ClientAuthenticationMethod.PRIVATE_KEY_JWT) &&
!registeredClient.getClientAuthenticationMethods().contains(ClientAuthenticationMethod.CLIENT_SECRET_JWT)) {
throwInvalidClient("authentication_method");
@@ -119,13 +102,17 @@ public final class JwtClientAssertionAuthenticationProvider implements Authentic
}
Jwt jwtAssertion = null;
JwtDecoder jwtDecoder = this.jwtClientAssertionDecoderFactory.createDecoder(registeredClient);
JwtDecoder jwtDecoder = this.jwtDecoderFactory.createDecoder(registeredClient);
try {
jwtAssertion = jwtDecoder.decode(clientAuthentication.getCredentials().toString());
} catch (JwtException ex) {
throwInvalidClient(OAuth2ParameterNames.CLIENT_ASSERTION, ex);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Validated client authentication parameters");
}
// Validate the "code_verifier" parameter for the confidential client, if available
this.codeVerifierAuthenticator.authenticateIfAvailable(clientAuthentication, registeredClient);
@@ -134,6 +121,10 @@ public final class JwtClientAssertionAuthenticationProvider implements Authentic
ClientAuthenticationMethod.PRIVATE_KEY_JWT :
ClientAuthenticationMethod.CLIENT_SECRET_JWT;
if (this.logger.isTraceEnabled()) {
this.logger.trace("Authenticated client assertion");
}
return new OAuth2ClientAuthenticationToken(registeredClient, clientAuthenticationMethod, jwtAssertion);
}
@@ -142,6 +133,19 @@ public final class JwtClientAssertionAuthenticationProvider implements Authentic
return OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication);
}
/**
* Sets the {@link JwtDecoderFactory} that provides a {@link JwtDecoder} for the specified {@link RegisteredClient}
* and is used for authenticating a {@link Jwt} Bearer Token during OAuth 2.0 Client Authentication.
* The default factory is {@link JwtClientAssertionDecoderFactory}.
*
* @param jwtDecoderFactory the {@link JwtDecoderFactory} that provides a {@link JwtDecoder} for the specified {@link RegisteredClient}
* @since 0.4.0
*/
public void setJwtDecoderFactory(JwtDecoderFactory<RegisteredClient> jwtDecoderFactory) {
Assert.notNull(jwtDecoderFactory, "jwtDecoderFactory cannot be null");
this.jwtDecoderFactory = jwtDecoderFactory;
}
private static void throwInvalidClient(String parameterName) {
throwInvalidClient(parameterName, null);
}
@@ -155,112 +159,4 @@ public final class JwtClientAssertionAuthenticationProvider implements Authentic
throw new OAuth2AuthenticationException(error, error.toString(), cause);
}
private static class JwtClientAssertionDecoderFactory implements JwtDecoderFactory<RegisteredClient> {
private static final String JWT_CLIENT_AUTHENTICATION_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc7523#section-3";
private static final Map<JwsAlgorithm, String> JCA_ALGORITHM_MAPPINGS;
static {
Map<JwsAlgorithm, String> mappings = new HashMap<>();
mappings.put(MacAlgorithm.HS256, "HmacSHA256");
mappings.put(MacAlgorithm.HS384, "HmacSHA384");
mappings.put(MacAlgorithm.HS512, "HmacSHA512");
JCA_ALGORITHM_MAPPINGS = Collections.unmodifiableMap(mappings);
}
private final Map<String, JwtDecoder> jwtDecoders = new ConcurrentHashMap<>();
@Override
public JwtDecoder createDecoder(RegisteredClient registeredClient) {
Assert.notNull(registeredClient, "registeredClient cannot be null");
return this.jwtDecoders.computeIfAbsent(registeredClient.getId(), (key) -> {
NimbusJwtDecoder jwtDecoder = buildDecoder(registeredClient);
jwtDecoder.setJwtValidator(createJwtValidator(registeredClient));
return jwtDecoder;
});
}
private static NimbusJwtDecoder buildDecoder(RegisteredClient registeredClient) {
JwsAlgorithm jwsAlgorithm = registeredClient.getClientSettings().getTokenEndpointAuthenticationSigningAlgorithm();
if (jwsAlgorithm instanceof SignatureAlgorithm) {
String jwkSetUrl = registeredClient.getClientSettings().getJwkSetUrl();
if (!StringUtils.hasText(jwkSetUrl)) {
OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT,
"Failed to find a Signature Verifier for Client: '"
+ registeredClient.getId()
+ "'. Check to ensure you have configured the JWK Set URL.",
JWT_CLIENT_AUTHENTICATION_ERROR_URI);
throw new OAuth2AuthenticationException(oauth2Error);
}
return NimbusJwtDecoder.withJwkSetUri(jwkSetUrl).jwsAlgorithm((SignatureAlgorithm) jwsAlgorithm).build();
}
if (jwsAlgorithm instanceof MacAlgorithm) {
String clientSecret = registeredClient.getClientSecret();
if (!StringUtils.hasText(clientSecret)) {
OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT,
"Failed to find a Signature Verifier for Client: '"
+ registeredClient.getId()
+ "'. Check to ensure you have configured the client secret.",
JWT_CLIENT_AUTHENTICATION_ERROR_URI);
throw new OAuth2AuthenticationException(oauth2Error);
}
SecretKeySpec secretKeySpec = new SecretKeySpec(clientSecret.getBytes(StandardCharsets.UTF_8),
JCA_ALGORITHM_MAPPINGS.get(jwsAlgorithm));
return NimbusJwtDecoder.withSecretKey(secretKeySpec).macAlgorithm((MacAlgorithm) jwsAlgorithm).build();
}
OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT,
"Failed to find a Signature Verifier for Client: '"
+ registeredClient.getId()
+ "'. Check to ensure you have configured a valid JWS Algorithm: '" + jwsAlgorithm + "'.",
JWT_CLIENT_AUTHENTICATION_ERROR_URI);
throw new OAuth2AuthenticationException(oauth2Error);
}
private static OAuth2TokenValidator<Jwt> createJwtValidator(RegisteredClient registeredClient) {
String clientId = registeredClient.getClientId();
return new DelegatingOAuth2TokenValidator<>(
new JwtClaimValidator<>(JwtClaimNames.ISS, clientId::equals),
new JwtClaimValidator<>(JwtClaimNames.SUB, clientId::equals),
new JwtClaimValidator<>(JwtClaimNames.AUD, containsProviderAudience()),
new JwtClaimValidator<>(JwtClaimNames.EXP, Objects::nonNull),
new JwtTimestampValidator()
);
}
private static Predicate<List<String>> containsProviderAudience() {
return (audienceClaim) -> {
if (CollectionUtils.isEmpty(audienceClaim)) {
return false;
}
List<String> providerAudience = getProviderAudience();
for (String audience : audienceClaim) {
if (providerAudience.contains(audience)) {
return true;
}
}
return false;
};
}
private static List<String> getProviderAudience() {
ProviderContext providerContext = ProviderContextHolder.getProviderContext();
if (!StringUtils.hasText(providerContext.getIssuer())) {
return Collections.emptyList();
}
ProviderSettings providerSettings = providerContext.getProviderSettings();
List<String> providerAudience = new ArrayList<>();
providerAudience.add(providerContext.getIssuer());
providerAudience.add(asUrl(providerContext.getIssuer(), providerSettings.getTokenEndpoint()));
providerAudience.add(asUrl(providerContext.getIssuer(), providerSettings.getTokenIntrospectionEndpoint()));
providerAudience.add(asUrl(providerContext.getIssuer(), providerSettings.getTokenRevocationEndpoint()));
return providerAudience;
}
private static String asUrl(String issuer, String endpoint) {
return UriComponentsBuilder.fromUriString(issuer).path(endpoint).build().toUriString();
}
}
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import javax.crypto.spec.SecretKeySpec;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
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.OAuth2TokenValidator;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.security.oauth2.jwt.JwtClaimValidator;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;
/**
* A {@link JwtDecoderFactory factory} that provides a {@link JwtDecoder} for the specified {@link RegisteredClient}
* and is used for authenticating a {@link Jwt} Bearer Token during OAuth 2.0 Client Authentication.
*
* @author Rafal Lewczuk
* @author Joe Grandja
* @since 0.4.0
* @see JwtDecoderFactory
* @see RegisteredClient
* @see OAuth2TokenValidator
* @see JwtClientAssertionAuthenticationProvider
* @see ClientAuthenticationMethod#PRIVATE_KEY_JWT
* @see ClientAuthenticationMethod#CLIENT_SECRET_JWT
*/
public final class JwtClientAssertionDecoderFactory implements JwtDecoderFactory<RegisteredClient> {
/**
* The default {@code OAuth2TokenValidator<Jwt>} factory that validates the {@link JwtClaimNames#ISS iss},
* {@link JwtClaimNames#SUB sub}, {@link JwtClaimNames#AUD aud}, {@link JwtClaimNames#EXP exp} and
* {@link JwtClaimNames#NBF nbf} claims of the {@link Jwt} for the specified {@link RegisteredClient}.
*/
public static final Function<RegisteredClient, OAuth2TokenValidator<Jwt>> DEFAULT_JWT_VALIDATOR_FACTORY = defaultJwtValidatorFactory();
private static final String JWT_CLIENT_AUTHENTICATION_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc7523#section-3";
private static final Map<JwsAlgorithm, String> JCA_ALGORITHM_MAPPINGS;
static {
Map<JwsAlgorithm, String> mappings = new HashMap<>();
mappings.put(MacAlgorithm.HS256, "HmacSHA256");
mappings.put(MacAlgorithm.HS384, "HmacSHA384");
mappings.put(MacAlgorithm.HS512, "HmacSHA512");
JCA_ALGORITHM_MAPPINGS = Collections.unmodifiableMap(mappings);
}
private final Map<String, JwtDecoder> jwtDecoders = new ConcurrentHashMap<>();
private Function<RegisteredClient, OAuth2TokenValidator<Jwt>> jwtValidatorFactory = DEFAULT_JWT_VALIDATOR_FACTORY;
@Override
public JwtDecoder createDecoder(RegisteredClient registeredClient) {
Assert.notNull(registeredClient, "registeredClient cannot be null");
return this.jwtDecoders.computeIfAbsent(registeredClient.getId(), (key) -> {
NimbusJwtDecoder jwtDecoder = buildDecoder(registeredClient);
jwtDecoder.setJwtValidator(this.jwtValidatorFactory.apply(registeredClient));
return jwtDecoder;
});
}
/**
* Sets the factory that provides an {@link OAuth2TokenValidator}
* for the specified {@link RegisteredClient} and is used by the {@link JwtDecoder}.
* The default {@code OAuth2TokenValidator<Jwt>} factory is {@link #DEFAULT_JWT_VALIDATOR_FACTORY}.
*
* @param jwtValidatorFactory the factory that provides an {@link OAuth2TokenValidator} for the specified {@link RegisteredClient}
*/
public void setJwtValidatorFactory(Function<RegisteredClient, OAuth2TokenValidator<Jwt>> jwtValidatorFactory) {
Assert.notNull(jwtValidatorFactory, "jwtValidatorFactory cannot be null");
this.jwtValidatorFactory = jwtValidatorFactory;
}
private static NimbusJwtDecoder buildDecoder(RegisteredClient registeredClient) {
JwsAlgorithm jwsAlgorithm = registeredClient.getClientSettings().getTokenEndpointAuthenticationSigningAlgorithm();
if (jwsAlgorithm instanceof SignatureAlgorithm) {
String jwkSetUrl = registeredClient.getClientSettings().getJwkSetUrl();
if (!StringUtils.hasText(jwkSetUrl)) {
OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT,
"Failed to find a Signature Verifier for Client: '"
+ registeredClient.getId()
+ "'. Check to ensure you have configured the JWK Set URL.",
JWT_CLIENT_AUTHENTICATION_ERROR_URI);
throw new OAuth2AuthenticationException(oauth2Error);
}
return NimbusJwtDecoder.withJwkSetUri(jwkSetUrl).jwsAlgorithm((SignatureAlgorithm) jwsAlgorithm).build();
}
if (jwsAlgorithm instanceof MacAlgorithm) {
String clientSecret = registeredClient.getClientSecret();
if (!StringUtils.hasText(clientSecret)) {
OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT,
"Failed to find a Signature Verifier for Client: '"
+ registeredClient.getId()
+ "'. Check to ensure you have configured the client secret.",
JWT_CLIENT_AUTHENTICATION_ERROR_URI);
throw new OAuth2AuthenticationException(oauth2Error);
}
SecretKeySpec secretKeySpec = new SecretKeySpec(clientSecret.getBytes(StandardCharsets.UTF_8),
JCA_ALGORITHM_MAPPINGS.get(jwsAlgorithm));
return NimbusJwtDecoder.withSecretKey(secretKeySpec).macAlgorithm((MacAlgorithm) jwsAlgorithm).build();
}
OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT,
"Failed to find a Signature Verifier for Client: '"
+ registeredClient.getId()
+ "'. Check to ensure you have configured a valid JWS Algorithm: '" + jwsAlgorithm + "'.",
JWT_CLIENT_AUTHENTICATION_ERROR_URI);
throw new OAuth2AuthenticationException(oauth2Error);
}
private static Function<RegisteredClient, OAuth2TokenValidator<Jwt>> defaultJwtValidatorFactory() {
return (registeredClient) -> {
String clientId = registeredClient.getClientId();
return new DelegatingOAuth2TokenValidator<>(
new JwtClaimValidator<>(JwtClaimNames.ISS, clientId::equals),
new JwtClaimValidator<>(JwtClaimNames.SUB, clientId::equals),
new JwtClaimValidator<>(JwtClaimNames.AUD, containsAudience()),
new JwtClaimValidator<>(JwtClaimNames.EXP, Objects::nonNull),
new JwtTimestampValidator()
);
};
}
private static Predicate<List<String>> containsAudience() {
return (audienceClaim) -> {
if (CollectionUtils.isEmpty(audienceClaim)) {
return false;
}
List<String> audienceList = getAudience();
for (String audience : audienceClaim) {
if (audienceList.contains(audience)) {
return true;
}
}
return false;
};
}
private static List<String> getAudience() {
AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext();
if (!StringUtils.hasText(authorizationServerContext.getIssuer())) {
return Collections.emptyList();
}
AuthorizationServerSettings authorizationServerSettings = authorizationServerContext.getAuthorizationServerSettings();
List<String> audience = new ArrayList<>();
audience.add(authorizationServerContext.getIssuer());
audience.add(asUrl(authorizationServerContext.getIssuer(), authorizationServerSettings.getTokenEndpoint()));
audience.add(asUrl(authorizationServerContext.getIssuer(), authorizationServerSettings.getTokenIntrospectionEndpoint()));
audience.add(asUrl(authorizationServerContext.getIssuer(), authorizationServerSettings.getTokenRevocationEndpoint()));
return audience;
}
private static String asUrl(String issuer, String endpoint) {
return UriComponentsBuilder.fromUriString(issuer).path(endpoint).build().toUriString();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,18 +15,18 @@
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import java.util.Collections;
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.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.security.oauth2.server.authorization.util.SpringAuthorizationServerVersion;
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 and (optional) Refresh Token.
@@ -41,7 +41,7 @@ import java.util.Map;
* @see OAuth2ClientAuthenticationToken
*/
public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
private final RegisteredClient registeredClient;
private final Authentication clientPrincipal;
private final OAuth2AccessToken accessToken;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,56 +13,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.authentication;
package org.springframework.security.oauth2.server.authorization.authentication;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.context.Context;
import org.springframework.security.oauth2.server.authorization.context.Context;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* A context that holds an {@link Authentication} and (optionally) additional information.
* A context that holds an {@link Authentication} and (optionally) additional information
* and is used in an {@link AuthenticationProvider}.
*
* @author Joe Grandja
* @since 0.2.0
* @see Context
*/
public class OAuth2AuthenticationContext implements Context {
private final Map<Object, Object> context;
/**
* Constructs an {@code OAuth2AuthenticationContext} using the provided parameters.
*
* @param authentication the {@code Authentication}
* @param context a {@code Map} of additional context information
*/
public OAuth2AuthenticationContext(Authentication authentication, @Nullable Map<Object, Object> context) {
Assert.notNull(authentication, "authentication cannot be null");
Map<Object, Object> ctx = new HashMap<>();
if (!CollectionUtils.isEmpty(context)) {
ctx.putAll(context);
}
ctx.put(Authentication.class, authentication);
this.context = Collections.unmodifiableMap(ctx);
}
/**
* Constructs an {@code OAuth2AuthenticationContext} using the provided parameters.
*
* @param context a {@code Map} of context information, must contain the {@code Authentication}
* @since 0.2.1
*/
public OAuth2AuthenticationContext(Map<Object, Object> context) {
Assert.notEmpty(context, "context cannot be empty");
Assert.notNull(context.get(Authentication.class), "authentication cannot be null");
this.context = Collections.unmodifiableMap(new HashMap<>(context));
}
public interface OAuth2AuthenticationContext extends Context {
/**
* Returns the {@link Authentication} associated to the context.
@@ -71,23 +41,10 @@ public class OAuth2AuthenticationContext implements Context {
* @return the {@link Authentication}
*/
@SuppressWarnings("unchecked")
public <T extends Authentication> T getAuthentication() {
default <T extends Authentication> T getAuthentication() {
return (T) get(Authentication.class);
}
@SuppressWarnings("unchecked")
@Nullable
@Override
public <V> V get(Object key) {
return hasKey(key) ? (V) this.context.get(key) : null;
}
@Override
public boolean hasKey(Object key) {
Assert.notNull(key, "key cannot be null");
return this.context.containsKey(key);
}
/**
* A builder for subclasses of {@link OAuth2AuthenticationContext}.
*
@@ -95,7 +52,7 @@ public class OAuth2AuthenticationContext implements Context {
* @param <B> the type of the builder
* @since 0.2.1
*/
protected static abstract class AbstractBuilder<T extends OAuth2AuthenticationContext, B extends AbstractBuilder<T, B>> {
abstract class AbstractBuilder<T extends OAuth2AuthenticationContext, B extends AbstractBuilder<T, B>> {
private final Map<Object, Object> context = new HashMap<>();
protected AbstractBuilder(Authentication authentication) {

View File

@@ -18,11 +18,11 @@ 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.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.OAuth2Token;
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.

View File

@@ -20,6 +20,10 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
@@ -28,12 +32,10 @@ import org.springframework.security.oauth2.core.ClaimAccessor;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2AuthorizationCode;
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.OAuth2Token;
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.oidc.OidcIdToken;
@@ -41,9 +43,11 @@ import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
@@ -72,6 +76,7 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
new OAuth2TokenType(OAuth2ParameterNames.CODE);
private static final OAuth2TokenType ID_TOKEN_TOKEN_TYPE =
new OAuth2TokenType(OidcParameterNames.ID_TOKEN);
private final Log logger = LogFactory.getLog(getClass());
private final OAuth2AuthorizationService authorizationService;
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
@@ -99,11 +104,20 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
getAuthenticatedClientElseThrowInvalidClient(authorizationCodeAuthentication);
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Retrieved registered client");
}
OAuth2Authorization authorization = this.authorizationService.findByToken(
authorizationCodeAuthentication.getCode(), AUTHORIZATION_CODE_TOKEN_TYPE);
if (authorization == null) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Retrieved authorization with authorization code");
}
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode =
authorization.getToken(OAuth2AuthorizationCode.class);
@@ -115,6 +129,9 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
// Invalidate the authorization code given that a different client is attempting to use it
authorization = OAuth2AuthenticationProviderUtils.invalidate(authorization, authorizationCode.getToken());
this.authorizationService.save(authorization);
if (this.logger.isWarnEnabled()) {
this.logger.warn(LogMessage.format("Invalidated authorization code used by registered client '%s'", registeredClient.getId()));
}
}
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
}
@@ -128,13 +145,17 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Validated token request parameters");
}
// @formatter:off
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
.registeredClient(registeredClient)
.principal(authorization.getAttribute(Principal.class.getName()))
.providerContext(ProviderContextHolder.getProviderContext())
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
.authorization(authorization)
.authorizedScopes(authorization.getAttribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME))
.authorizedScopes(authorization.getAuthorizedScopes())
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrant(authorizationCodeAuthentication);
// @formatter:on
@@ -149,6 +170,11 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
"The token generator failed to generate the access token.", ERROR_URI);
throw new OAuth2AuthenticationException(error);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Generated access token");
}
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
@@ -172,6 +198,11 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
"The token generator failed to generate the refresh token.", ERROR_URI);
throw new OAuth2AuthenticationException(error);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Generated refresh token");
}
refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
authorizationBuilder.refreshToken(refreshToken);
}
@@ -191,6 +222,11 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
"The token generator failed to generate the ID token.", ERROR_URI);
throw new OAuth2AuthenticationException(error);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Generated id token");
}
idToken = new OidcIdToken(generatedIdToken.getTokenValue(), generatedIdToken.getIssuedAt(),
generatedIdToken.getExpiresAt(), ((Jwt) generatedIdToken).getClaims());
authorizationBuilder.token(idToken, (metadata) ->
@@ -206,12 +242,20 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
this.authorizationService.save(authorization);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Saved authorization");
}
Map<String, Object> additionalParameters = Collections.emptyMap();
if (idToken != null) {
additionalParameters = new HashMap<>();
additionalParameters.put(OidcParameterNames.ID_TOKEN, idToken.getTokenValue());
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Authenticated token request");
}
return new OAuth2AccessTokenAuthenticationToken(
registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters);
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import java.time.Instant;
import java.util.Base64;
import org.springframework.lang.Nullable;
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
import org.springframework.security.crypto.keygen.StringKeyGenerator;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
/**
* An {@link OAuth2TokenGenerator} that generates an {@link OAuth2AuthorizationCode}.
*
* @author Joe Grandja
* @since 0.4.0
* @see OAuth2TokenGenerator
* @see OAuth2AuthorizationCode
* @see OAuth2AuthorizationCodeRequestAuthenticationProvider
* @see OAuth2AuthorizationConsentAuthenticationProvider
*/
final class OAuth2AuthorizationCodeGenerator implements OAuth2TokenGenerator<OAuth2AuthorizationCode> {
private final StringKeyGenerator authorizationCodeGenerator =
new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
@Nullable
@Override
public OAuth2AuthorizationCode generate(OAuth2TokenContext context) {
if (context.getTokenType() == null ||
!OAuth2ParameterNames.CODE.equals(context.getTokenType().getValue())) {
return null;
}
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plus(context.getRegisteredClient().getTokenSettings().getAuthorizationCodeTimeToLive());
return new OAuth2AuthorizationCode(this.authorizationCodeGenerator.generateKey(), issuedAt, expiresAt);
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.util.Assert;
/**
* An {@link OAuth2AuthenticationContext} that holds an {@link OAuth2AuthorizationCodeRequestAuthenticationToken} and additional information
* and is used when validating the OAuth 2.0 Authorization Request used in the Authorization Code Grant.
*
* @author Joe Grandja
* @since 0.4.0
* @see OAuth2AuthenticationContext
* @see OAuth2AuthorizationCodeRequestAuthenticationToken
* @see OAuth2AuthorizationCodeRequestAuthenticationProvider#setAuthenticationValidator(Consumer)
*/
public final class OAuth2AuthorizationCodeRequestAuthenticationContext implements OAuth2AuthenticationContext {
private final Map<Object, Object> context;
private OAuth2AuthorizationCodeRequestAuthenticationContext(Map<Object, Object> context) {
this.context = Collections.unmodifiableMap(new HashMap<>(context));
}
@SuppressWarnings("unchecked")
@Nullable
@Override
public <V> V get(Object key) {
return hasKey(key) ? (V) this.context.get(key) : null;
}
@Override
public boolean hasKey(Object key) {
Assert.notNull(key, "key cannot be null");
return this.context.containsKey(key);
}
/**
* Returns the {@link RegisteredClient registered client}.
*
* @return the {@link RegisteredClient}
*/
public RegisteredClient getRegisteredClient() {
return get(RegisteredClient.class);
}
/**
* Constructs a new {@link Builder} with the provided {@link OAuth2AuthorizationCodeRequestAuthenticationToken}.
*
* @param authentication the {@link OAuth2AuthorizationCodeRequestAuthenticationToken}
* @return the {@link Builder}
*/
public static Builder with(OAuth2AuthorizationCodeRequestAuthenticationToken authentication) {
return new Builder(authentication);
}
/**
* A builder for {@link OAuth2AuthorizationCodeRequestAuthenticationContext}.
*/
public static final class Builder extends AbstractBuilder<OAuth2AuthorizationCodeRequestAuthenticationContext, Builder> {
private Builder(OAuth2AuthorizationCodeRequestAuthenticationToken authentication) {
super(authentication);
}
/**
* Sets the {@link RegisteredClient registered client}.
*
* @param registeredClient the {@link RegisteredClient}
* @return the {@link Builder} for further configuration
*/
public Builder registeredClient(RegisteredClient registeredClient) {
return put(RegisteredClient.class, registeredClient);
}
/**
* Builds a new {@link OAuth2AuthorizationCodeRequestAuthenticationContext}.
*
* @return the {@link OAuth2AuthorizationCodeRequestAuthenticationContext}
*/
public OAuth2AuthorizationCodeRequestAuthenticationContext build() {
Assert.notNull(get(RegisteredClient.class), "registeredClient cannot be null");
return new OAuth2AuthorizationCodeRequestAuthenticationContext(getContext());
}
}
}

View File

@@ -16,60 +16,52 @@
package org.springframework.security.oauth2.server.authorization.authentication;
import java.security.Principal;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import org.springframework.lang.Nullable;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
import org.springframework.security.crypto.keygen.StringKeyGenerator;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AuthorizationCode;
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.authentication.OAuth2AuthenticationContext;
import org.springframework.security.oauth2.core.authentication.OAuth2AuthenticationValidator;
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.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
/**
* An {@link AuthenticationProvider} implementation for the OAuth 2.0 Authorization Request (and Consent)
* An {@link AuthenticationProvider} implementation for the OAuth 2.0 Authorization Request
* used in the Authorization Code Grant.
*
* @author Joe Grandja
* @author Steve Riesenberg
* @since 0.1.2
* @see OAuth2AuthorizationCodeRequestAuthenticationToken
* @see OAuth2AuthorizationCodeRequestAuthenticationValidator
* @see OAuth2AuthorizationCodeAuthenticationProvider
* @see OAuth2AuthorizationConsentAuthenticationProvider
* @see RegisteredClientRepository
* @see OAuth2AuthorizationService
* @see OAuth2AuthorizationConsentService
@@ -78,17 +70,15 @@ import org.springframework.web.util.UriComponentsBuilder;
public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implements AuthenticationProvider {
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1";
private static final String PKCE_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc7636#section-4.4.1";
private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE);
private static final StringKeyGenerator DEFAULT_STATE_GENERATOR =
new Base64StringKeyGenerator(Base64.getUrlEncoder());
private static final Function<String, OAuth2AuthenticationValidator> DEFAULT_AUTHENTICATION_VALIDATOR_RESOLVER =
createDefaultAuthenticationValidatorResolver();
private final Log logger = LogFactory.getLog(getClass());
private final RegisteredClientRepository registeredClientRepository;
private final OAuth2AuthorizationService authorizationService;
private final OAuth2AuthorizationConsentService authorizationConsentService;
private OAuth2TokenGenerator<OAuth2AuthorizationCode> authorizationCodeGenerator = new OAuth2AuthorizationCodeGenerator();
private Function<String, OAuth2AuthenticationValidator> authenticationValidatorResolver = DEFAULT_AUTHENTICATION_VALIDATOR_RESOLVER;
private Consumer<OAuth2AuthorizationConsentAuthenticationContext> authorizationConsentCustomizer;
private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator =
new OAuth2AuthorizationCodeRequestAuthenticationValidator();
/**
* Constructs an {@code OAuth2AuthorizationCodeRequestAuthenticationProvider} using the provided parameters.
@@ -112,76 +102,6 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
(OAuth2AuthorizationCodeRequestAuthenticationToken) authentication;
return authorizationCodeRequestAuthentication.isConsent() ?
authenticateAuthorizationConsent(authentication) :
authenticateAuthorizationRequest(authentication);
}
@Override
public boolean supports(Class<?> authentication) {
return OAuth2AuthorizationCodeRequestAuthenticationToken.class.isAssignableFrom(authentication);
}
/**
* Sets the {@link OAuth2TokenGenerator} that generates the {@link OAuth2AuthorizationCode}.
*
* @param authorizationCodeGenerator the {@link OAuth2TokenGenerator} that generates the {@link OAuth2AuthorizationCode}
* @since 0.2.3
*/
public void setAuthorizationCodeGenerator(OAuth2TokenGenerator<OAuth2AuthorizationCode> authorizationCodeGenerator) {
Assert.notNull(authorizationCodeGenerator, "authorizationCodeGenerator cannot be null");
this.authorizationCodeGenerator = authorizationCodeGenerator;
}
/**
* Sets the resolver that resolves an {@link OAuth2AuthenticationValidator} from the provided OAuth 2.0 Authorization Request parameter.
*
* <p>
* The following OAuth 2.0 Authorization Request parameters are supported:
* <ol>
* <li>{@link OAuth2ParameterNames#REDIRECT_URI}</li>
* <li>{@link OAuth2ParameterNames#SCOPE}</li>
* </ol>
*
* <p>
* <b>NOTE:</b> The resolved {@link OAuth2AuthenticationValidator} MUST throw {@link OAuth2AuthorizationCodeRequestAuthenticationException} if validation fails.
*
* @param authenticationValidatorResolver the resolver that resolves an {@link OAuth2AuthenticationValidator} from the provided OAuth 2.0 Authorization Request parameter
*/
public void setAuthenticationValidatorResolver(Function<String, OAuth2AuthenticationValidator> authenticationValidatorResolver) {
Assert.notNull(authenticationValidatorResolver, "authenticationValidatorResolver cannot be null");
this.authenticationValidatorResolver = authenticationValidatorResolver;
}
/**
* Sets the {@code Consumer} providing access to the {@link OAuth2AuthorizationConsentAuthenticationContext}
* containing an {@link OAuth2AuthorizationConsent.Builder} and additional context information.
*
* <p>
* The following context attributes are available:
* <ul>
* <li>The {@link OAuth2AuthorizationConsent.Builder} used to build the authorization consent
* prior to {@link OAuth2AuthorizationConsentService#save(OAuth2AuthorizationConsent)}.</li>
* <li>The {@link Authentication} of type
* {@link OAuth2AuthorizationCodeRequestAuthenticationToken}.</li>
* <li>The {@link RegisteredClient} associated with the authorization request.</li>
* <li>The {@link OAuth2Authorization} associated with the state token presented in the
* authorization consent request.</li>
* <li>The {@link OAuth2AuthorizationRequest} associated with the authorization consent request.</li>
* </ul>
*
* @param authorizationConsentCustomizer the {@code Consumer} providing access to the
* {@link OAuth2AuthorizationConsentAuthenticationContext} containing an {@link OAuth2AuthorizationConsent.Builder}
*/
public void setAuthorizationConsentCustomizer(Consumer<OAuth2AuthorizationConsentAuthenticationContext> authorizationConsentCustomizer) {
Assert.notNull(authorizationConsentCustomizer, "authorizationConsentCustomizer cannot be null");
this.authorizationConsentCustomizer = authorizationConsentCustomizer;
}
private Authentication authenticateAuthorizationRequest(Authentication authentication) throws AuthenticationException {
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
(OAuth2AuthorizationCodeRequestAuthenticationToken) authentication;
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(
authorizationCodeRequestAuthentication.getClientId());
if (registeredClient == null) {
@@ -189,22 +109,21 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
authorizationCodeRequestAuthentication, null);
}
Map<Object, Object> context = new HashMap<>();
context.put(RegisteredClient.class, registeredClient);
OAuth2AuthenticationContext authenticationContext = new OAuth2AuthenticationContext(
authorizationCodeRequestAuthentication, context);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Retrieved registered client");
}
OAuth2AuthenticationValidator redirectUriValidator = resolveAuthenticationValidator(OAuth2ParameterNames.REDIRECT_URI);
redirectUriValidator.validate(authenticationContext);
OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext =
OAuth2AuthorizationCodeRequestAuthenticationContext.with(authorizationCodeRequestAuthentication)
.registeredClient(registeredClient)
.build();
this.authenticationValidator.accept(authenticationContext);
if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE)) {
throwError(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT, OAuth2ParameterNames.CLIENT_ID,
authorizationCodeRequestAuthentication, registeredClient);
}
OAuth2AuthenticationValidator scopeValidator = resolveAuthenticationValidator(OAuth2ParameterNames.SCOPE);
scopeValidator.validate(authenticationContext);
// code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE)
String codeChallenge = (String) authorizationCodeRequestAuthentication.getAdditionalParameters().get(PkceParameterNames.CODE_CHALLENGE);
if (StringUtils.hasText(codeChallenge)) {
@@ -218,12 +137,19 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
authorizationCodeRequestAuthentication, registeredClient, null);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Validated authorization code request parameters");
}
// ---------------
// The request is valid - ensure the resource owner is authenticated
// ---------------
Authentication principal = (Authentication) authorizationCodeRequestAuthentication.getPrincipal();
if (!isPrincipalAuthenticated(principal)) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not authenticate authorization code request since principal not authenticated");
}
// Return the authorization request as-is where isAuthenticated() is false
return authorizationCodeRequestAuthentication;
}
@@ -245,17 +171,22 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
OAuth2Authorization authorization = authorizationBuilder(registeredClient, principal, authorizationRequest)
.attribute(OAuth2ParameterNames.STATE, state)
.build();
if (this.logger.isTraceEnabled()) {
logger.trace("Generated authorization consent state");
}
this.authorizationService.save(authorization);
Set<String> currentAuthorizedScopes = currentAuthorizationConsent != null ?
currentAuthorizationConsent.getScopes() : null;
return OAuth2AuthorizationCodeRequestAuthenticationToken.with(registeredClient.getClientId(), principal)
.authorizationUri(authorizationRequest.getAuthorizationUri())
.scopes(currentAuthorizedScopes)
.state(state)
.consentRequired(true)
.build();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Saved authorization");
}
return new OAuth2AuthorizationConsentAuthenticationToken(authorizationRequest.getAuthorizationUri(),
registeredClient.getClientId(), principal, state, currentAuthorizedScopes, null);
}
OAuth2TokenContext tokenContext = createAuthorizationCodeTokenContext(
@@ -267,161 +198,65 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Generated authorization code");
}
OAuth2Authorization authorization = authorizationBuilder(registeredClient, principal, authorizationRequest)
.authorizedScopes(authorizationRequest.getScopes())
.token(authorizationCode)
.attribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME, authorizationRequest.getScopes())
.build();
this.authorizationService.save(authorization);
String redirectUri = authorizationRequest.getRedirectUri();
if (!StringUtils.hasText(redirectUri)) {
redirectUri = registeredClient.getRedirectUris().iterator().next();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Saved authorization");
}
return OAuth2AuthorizationCodeRequestAuthenticationToken.with(registeredClient.getClientId(), principal)
.authorizationUri(authorizationRequest.getAuthorizationUri())
.redirectUri(redirectUri)
.scopes(authorizationRequest.getScopes())
.state(authorizationRequest.getState())
.authorizationCode(authorizationCode)
.build();
}
private OAuth2AuthenticationValidator resolveAuthenticationValidator(String parameterName) {
OAuth2AuthenticationValidator authenticationValidator = this.authenticationValidatorResolver.apply(parameterName);
return authenticationValidator != null ?
authenticationValidator :
DEFAULT_AUTHENTICATION_VALIDATOR_RESOLVER.apply(parameterName);
}
private Authentication authenticateAuthorizationConsent(Authentication authentication) throws AuthenticationException {
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
(OAuth2AuthorizationCodeRequestAuthenticationToken) authentication;
OAuth2Authorization authorization = this.authorizationService.findByToken(
authorizationCodeRequestAuthentication.getState(), STATE_TOKEN_TYPE);
if (authorization == null) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE,
authorizationCodeRequestAuthentication, null, null);
}
// The 'in-flight' authorization must be associated to the current principal
Authentication principal = (Authentication) authorizationCodeRequestAuthentication.getPrincipal();
if (!isPrincipalAuthenticated(principal) || !principal.getName().equals(authorization.getPrincipalName())) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE,
authorizationCodeRequestAuthentication, null, null);
}
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(
authorizationCodeRequestAuthentication.getClientId());
if (registeredClient == null || !registeredClient.getId().equals(authorization.getRegisteredClientId())) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID,
authorizationCodeRequestAuthentication, registeredClient);
}
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
Set<String> requestedScopes = authorizationRequest.getScopes();
Set<String> authorizedScopes = new HashSet<>(authorizationCodeRequestAuthentication.getScopes());
if (!requestedScopes.containsAll(authorizedScopes)) {
throwError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE,
authorizationCodeRequestAuthentication, registeredClient, authorizationRequest);
}
OAuth2AuthorizationConsent currentAuthorizationConsent = this.authorizationConsentService.findById(
authorization.getRegisteredClientId(), authorization.getPrincipalName());
Set<String> currentAuthorizedScopes = currentAuthorizationConsent != null ?
currentAuthorizationConsent.getScopes() : Collections.emptySet();
if (!currentAuthorizedScopes.isEmpty()) {
for (String requestedScope : requestedScopes) {
if (currentAuthorizedScopes.contains(requestedScope)) {
authorizedScopes.add(requestedScope);
}
}
}
if (!authorizedScopes.isEmpty() && requestedScopes.contains(OidcScopes.OPENID)) {
// 'openid' scope is auto-approved as it does not require consent
authorizedScopes.add(OidcScopes.OPENID);
}
OAuth2AuthorizationConsent.Builder authorizationConsentBuilder;
if (currentAuthorizationConsent != null) {
authorizationConsentBuilder = OAuth2AuthorizationConsent.from(currentAuthorizationConsent);
} else {
authorizationConsentBuilder = OAuth2AuthorizationConsent.withId(
authorization.getRegisteredClientId(), authorization.getPrincipalName());
}
authorizedScopes.forEach(authorizationConsentBuilder::scope);
if (this.authorizationConsentCustomizer != null) {
// @formatter:off
OAuth2AuthorizationConsentAuthenticationContext authorizationConsentAuthenticationContext =
OAuth2AuthorizationConsentAuthenticationContext.with(authorizationCodeRequestAuthentication)
.authorizationConsent(authorizationConsentBuilder)
.registeredClient(registeredClient)
.authorization(authorization)
.authorizationRequest(authorizationRequest)
.build();
// @formatter:on
this.authorizationConsentCustomizer.accept(authorizationConsentAuthenticationContext);
}
Set<GrantedAuthority> authorities = new HashSet<>();
authorizationConsentBuilder.authorities(authorities::addAll);
if (authorities.isEmpty()) {
// Authorization consent denied (or revoked)
if (currentAuthorizationConsent != null) {
this.authorizationConsentService.remove(currentAuthorizationConsent);
}
this.authorizationService.remove(authorization);
throwError(OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID,
authorizationCodeRequestAuthentication, registeredClient, authorizationRequest);
}
OAuth2AuthorizationConsent authorizationConsent = authorizationConsentBuilder.build();
if (!authorizationConsent.equals(currentAuthorizationConsent)) {
this.authorizationConsentService.save(authorizationConsent);
}
OAuth2TokenContext tokenContext = createAuthorizationCodeTokenContext(
authorizationCodeRequestAuthentication, registeredClient, authorization, authorizedScopes);
OAuth2AuthorizationCode authorizationCode = this.authorizationCodeGenerator.generate(tokenContext);
if (authorizationCode == null) {
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
"The token generator failed to generate the authorization code.", ERROR_URI);
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null);
}
OAuth2Authorization updatedAuthorization = OAuth2Authorization.from(authorization)
.token(authorizationCode)
.attributes(attrs -> {
attrs.remove(OAuth2ParameterNames.STATE);
attrs.put(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME, authorizedScopes);
})
.build();
this.authorizationService.save(updatedAuthorization);
String redirectUri = authorizationRequest.getRedirectUri();
if (!StringUtils.hasText(redirectUri)) {
redirectUri = registeredClient.getRedirectUris().iterator().next();
}
return OAuth2AuthorizationCodeRequestAuthenticationToken.with(registeredClient.getClientId(), principal)
.authorizationUri(authorizationRequest.getAuthorizationUri())
.redirectUri(redirectUri)
.scopes(authorizedScopes)
.state(authorizationRequest.getState())
.authorizationCode(authorizationCode)
.build();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Authenticated authorization code request");
}
return new OAuth2AuthorizationCodeRequestAuthenticationToken(authorizationRequest.getAuthorizationUri(),
registeredClient.getClientId(), principal, authorizationCode, redirectUri,
authorizationRequest.getState(), authorizationRequest.getScopes());
}
private static Function<String, OAuth2AuthenticationValidator> createDefaultAuthenticationValidatorResolver() {
Map<String, OAuth2AuthenticationValidator> authenticationValidators = new HashMap<>();
authenticationValidators.put(OAuth2ParameterNames.REDIRECT_URI, new DefaultRedirectUriOAuth2AuthenticationValidator());
authenticationValidators.put(OAuth2ParameterNames.SCOPE, new DefaultScopeOAuth2AuthenticationValidator());
return authenticationValidators::get;
@Override
public boolean supports(Class<?> authentication) {
return OAuth2AuthorizationCodeRequestAuthenticationToken.class.isAssignableFrom(authentication);
}
/**
* Sets the {@link OAuth2TokenGenerator} that generates the {@link OAuth2AuthorizationCode}.
*
* @param authorizationCodeGenerator the {@link OAuth2TokenGenerator} that generates the {@link OAuth2AuthorizationCode}
* @since 0.2.3
*/
public void setAuthorizationCodeGenerator(OAuth2TokenGenerator<OAuth2AuthorizationCode> authorizationCodeGenerator) {
Assert.notNull(authorizationCodeGenerator, "authorizationCodeGenerator cannot be null");
this.authorizationCodeGenerator = authorizationCodeGenerator;
}
/**
* Sets the {@code Consumer} providing access to the {@link OAuth2AuthorizationCodeRequestAuthenticationContext}
* and is responsible for validating specific OAuth 2.0 Authorization Request parameters
* associated in the {@link OAuth2AuthorizationCodeRequestAuthenticationToken}.
* The default authentication validator is {@link OAuth2AuthorizationCodeRequestAuthenticationValidator}.
*
* <p>
* <b>NOTE:</b> The authentication validator MUST throw {@link OAuth2AuthorizationCodeRequestAuthenticationException} if validation fails.
*
* @param authenticationValidator the {@code Consumer} providing access to the {@link OAuth2AuthorizationCodeRequestAuthenticationContext} and is responsible for validating specific OAuth 2.0 Authorization Request parameters
* @since 0.4.0
*/
public void setAuthenticationValidator(Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator) {
Assert.notNull(authenticationValidator, "authenticationValidator cannot be null");
this.authenticationValidator = authenticationValidator;
}
private static OAuth2Authorization.Builder authorizationBuilder(RegisteredClient registeredClient, Authentication principal,
@@ -441,7 +276,7 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
.registeredClient(registeredClient)
.principal((Authentication) authorizationCodeRequestAuthentication.getPrincipal())
.providerContext(ProviderContextHolder.getProviderContext())
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
.tokenType(new OAuth2TokenType(OAuth2ParameterNames.CODE))
.authorizedScopes(authorizedScopes)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
@@ -484,14 +319,7 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
private static void throwError(String errorCode, String parameterName,
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication,
RegisteredClient registeredClient) {
throwError(errorCode, parameterName, authorizationCodeRequestAuthentication, registeredClient, null);
}
private static void throwError(String errorCode, String parameterName,
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication,
RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest) {
throwError(errorCode, parameterName, ERROR_URI,
authorizationCodeRequestAuthentication, registeredClient, authorizationRequest);
throwError(errorCode, parameterName, ERROR_URI, authorizationCodeRequestAuthentication, registeredClient, null);
}
private static void throwError(String errorCode, String parameterName, String errorUri,
@@ -505,31 +333,19 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication,
RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest) {
boolean redirectOnError = true;
String redirectUri = resolveRedirectUri(authorizationRequest, registeredClient);
if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_REQUEST) &&
(parameterName.equals(OAuth2ParameterNames.CLIENT_ID) ||
parameterName.equals(OAuth2ParameterNames.REDIRECT_URI) ||
parameterName.equals(OAuth2ParameterNames.STATE))) {
redirectOnError = false;
redirectUri = null; // Prevent redirects
}
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = authorizationCodeRequestAuthentication;
if (redirectOnError && !StringUtils.hasText(authorizationCodeRequestAuthentication.getRedirectUri())) {
String redirectUri = resolveRedirectUri(authorizationRequest, registeredClient);
String state = authorizationCodeRequestAuthentication.isConsent() && authorizationRequest != null ?
authorizationRequest.getState() : authorizationCodeRequestAuthentication.getState();
authorizationCodeRequestAuthenticationResult = from(authorizationCodeRequestAuthentication)
.redirectUri(redirectUri)
.state(state)
.build();
authorizationCodeRequestAuthenticationResult.setAuthenticated(authorizationCodeRequestAuthentication.isAuthenticated());
} else if (!redirectOnError && StringUtils.hasText(authorizationCodeRequestAuthentication.getRedirectUri())) {
authorizationCodeRequestAuthenticationResult = from(authorizationCodeRequestAuthentication)
.redirectUri(null) // Prevent redirects
.build();
authorizationCodeRequestAuthenticationResult.setAuthenticated(authorizationCodeRequestAuthentication.isAuthenticated());
}
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
authorizationCodeRequestAuthentication.getAuthorizationUri(), authorizationCodeRequestAuthentication.getClientId(),
(Authentication) authorizationCodeRequestAuthentication.getPrincipal(), redirectUri,
authorizationCodeRequestAuthentication.getState(), authorizationCodeRequestAuthentication.getScopes(),
authorizationCodeRequestAuthentication.getAdditionalParameters());
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, authorizationCodeRequestAuthenticationResult);
}
@@ -544,152 +360,4 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
return null;
}
private static OAuth2AuthorizationCodeRequestAuthenticationToken.Builder from(OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication) {
return OAuth2AuthorizationCodeRequestAuthenticationToken.with(authorizationCodeRequestAuthentication.getClientId(), (Authentication) authorizationCodeRequestAuthentication.getPrincipal())
.authorizationUri(authorizationCodeRequestAuthentication.getAuthorizationUri())
.redirectUri(authorizationCodeRequestAuthentication.getRedirectUri())
.scopes(authorizationCodeRequestAuthentication.getScopes())
.state(authorizationCodeRequestAuthentication.getState())
.additionalParameters(authorizationCodeRequestAuthentication.getAdditionalParameters())
.authorizationCode(authorizationCodeRequestAuthentication.getAuthorizationCode());
}
private static class OAuth2AuthorizationCodeGenerator implements OAuth2TokenGenerator<OAuth2AuthorizationCode> {
private final StringKeyGenerator authorizationCodeGenerator =
new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
@Nullable
@Override
public OAuth2AuthorizationCode generate(OAuth2TokenContext context) {
if (context.getTokenType() == null ||
!OAuth2ParameterNames.CODE.equals(context.getTokenType().getValue())) {
return null;
}
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plus(5, ChronoUnit.MINUTES); // TODO Allow configuration for authorization code time-to-live
return new OAuth2AuthorizationCode(this.authorizationCodeGenerator.generateKey(), issuedAt, expiresAt);
}
}
private static class DefaultRedirectUriOAuth2AuthenticationValidator implements OAuth2AuthenticationValidator {
@Override
public void validate(OAuth2AuthenticationContext authenticationContext) {
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
authenticationContext.getAuthentication();
RegisteredClient registeredClient = authenticationContext.get(RegisteredClient.class);
String requestedRedirectUri = authorizationCodeRequestAuthentication.getRedirectUri();
if (StringUtils.hasText(requestedRedirectUri)) {
// ***** redirect_uri is available in authorization request
UriComponents requestedRedirect = null;
try {
requestedRedirect = UriComponentsBuilder.fromUriString(requestedRedirectUri).build();
} catch (Exception ex) { }
if (requestedRedirect == null || requestedRedirect.getFragment() != null) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI,
authorizationCodeRequestAuthentication, registeredClient);
}
String requestedRedirectHost = requestedRedirect.getHost();
if (requestedRedirectHost == null || requestedRedirectHost.equals("localhost")) {
// As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-9.7.1
// While redirect URIs using localhost (i.e., "http://localhost:{port}/{path}")
// function similarly to loopback IP redirects described in Section 10.3.3,
// the use of "localhost" is NOT RECOMMENDED.
OAuth2Error error = new OAuth2Error(
OAuth2ErrorCodes.INVALID_REQUEST,
"localhost is not allowed for the redirect_uri (" + requestedRedirectUri + "). " +
"Use the IP literal (127.0.0.1) instead.",
"https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-9.7.1");
throwError(error, OAuth2ParameterNames.REDIRECT_URI,
authorizationCodeRequestAuthentication, registeredClient, null);
}
if (!isLoopbackAddress(requestedRedirectHost)) {
// As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-9.7
// When comparing client redirect URIs against pre-registered URIs,
// authorization servers MUST utilize exact string matching.
if (!registeredClient.getRedirectUris().contains(requestedRedirectUri)) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI,
authorizationCodeRequestAuthentication, registeredClient);
}
} else {
// As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-10.3.3
// The authorization server MUST allow any port to be specified at the
// time of the request for loopback IP redirect URIs, to accommodate
// clients that obtain an available ephemeral port from the operating
// system at the time of the request.
boolean validRedirectUri = false;
for (String registeredRedirectUri : registeredClient.getRedirectUris()) {
UriComponentsBuilder registeredRedirect = UriComponentsBuilder.fromUriString(registeredRedirectUri);
registeredRedirect.port(requestedRedirect.getPort());
if (registeredRedirect.build().toString().equals(requestedRedirect.toString())) {
validRedirectUri = true;
break;
}
}
if (!validRedirectUri) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI,
authorizationCodeRequestAuthentication, registeredClient);
}
}
} else {
// ***** redirect_uri is NOT available in authorization request
if (authorizationCodeRequestAuthentication.getScopes().contains(OidcScopes.OPENID) ||
registeredClient.getRedirectUris().size() != 1) {
// redirect_uri is REQUIRED for OpenID Connect
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI,
authorizationCodeRequestAuthentication, registeredClient);
}
}
}
private static boolean isLoopbackAddress(String host) {
// IPv6 loopback address should either be "0:0:0:0:0:0:0:1" or "::1"
if ("[0:0:0:0:0:0:0:1]".equals(host) || "[::1]".equals(host)) {
return true;
}
// IPv4 loopback address ranges from 127.0.0.1 to 127.255.255.255
String[] ipv4Octets = host.split("\\.");
if (ipv4Octets.length != 4) {
return false;
}
try {
int[] address = new int[ipv4Octets.length];
for (int i=0; i < ipv4Octets.length; i++) {
address[i] = Integer.parseInt(ipv4Octets[i]);
}
return address[0] == 127 && address[1] >= 0 && address[1] <= 255 && address[2] >= 0 &&
address[2] <= 255 && address[3] >= 1 && address[3] <= 255;
} catch (NumberFormatException ex) {
return false;
}
}
}
private static class DefaultScopeOAuth2AuthenticationValidator implements OAuth2AuthenticationValidator {
@Override
public void validate(OAuth2AuthenticationContext authenticationContext) {
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
authenticationContext.getAuthentication();
RegisteredClient registeredClient = authenticationContext.get(RegisteredClient.class);
Set<String> requestedScopes = authorizationCodeRequestAuthentication.getScopes();
Set<String> allowedScopes = registeredClient.getScopes();
if (!requestedScopes.isEmpty() && !allowedScopes.containsAll(requestedScopes)) {
throwError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE,
authorizationCodeRequestAuthentication, registeredClient);
}
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,45 +15,104 @@
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.Version;
import org.springframework.security.oauth2.core.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.util.SpringAuthorizationServerVersion;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* An {@link Authentication} implementation for the OAuth 2.0 Authorization Request (and Consent)
* An {@link Authentication} implementation for the OAuth 2.0 Authorization Request
* used in the Authorization Code Grant.
*
* @author Joe Grandja
* @since 0.1.2
* @see OAuth2AuthorizationCodeRequestAuthenticationProvider
* @see OAuth2AuthorizationConsentAuthenticationProvider
*/
public final class OAuth2AuthorizationCodeRequestAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
private String authorizationUri;
private String clientId;
private Authentication principal;
private String redirectUri;
private Set<String> scopes;
private String state;
private Map<String, Object> additionalParameters;
private boolean consentRequired;
private boolean consent;
private OAuth2AuthorizationCode authorizationCode;
public class OAuth2AuthorizationCodeRequestAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
private final String authorizationUri;
private final String clientId;
private final Authentication principal;
private final String redirectUri;
private final String state;
private final Set<String> scopes;
private final Map<String, Object> additionalParameters;
private final OAuth2AuthorizationCode authorizationCode;
private OAuth2AuthorizationCodeRequestAuthenticationToken() {
/**
* Constructs an {@code OAuth2AuthorizationCodeRequestAuthenticationToken} using the provided parameters.
*
* @param authorizationUri the authorization URI
* @param clientId the client identifier
* @param principal the {@code Principal} (Resource Owner)
* @param redirectUri the redirect uri
* @param state the state
* @param scopes the requested scope(s)
* @param additionalParameters the additional parameters
* @since 0.4.0
*/
public OAuth2AuthorizationCodeRequestAuthenticationToken(String authorizationUri, String clientId, Authentication principal,
@Nullable String redirectUri, @Nullable String state, @Nullable Set<String> scopes, @Nullable Map<String, Object> additionalParameters) {
super(Collections.emptyList());
Assert.hasText(authorizationUri, "authorizationUri cannot be empty");
Assert.hasText(clientId, "clientId cannot be empty");
Assert.notNull(principal, "principal cannot be null");
this.authorizationUri = authorizationUri;
this.clientId = clientId;
this.principal = principal;
this.redirectUri = redirectUri;
this.state = state;
this.scopes = Collections.unmodifiableSet(
scopes != null ?
new HashSet<>(scopes) :
Collections.emptySet());
this.additionalParameters = Collections.unmodifiableMap(
additionalParameters != null ?
new HashMap<>(additionalParameters) :
Collections.emptyMap());
this.authorizationCode = null;
}
/**
* Constructs an {@code OAuth2AuthorizationCodeRequestAuthenticationToken} using the provided parameters.
*
* @param authorizationUri the authorization URI
* @param clientId the client identifier
* @param principal the {@code Principal} (Resource Owner)
* @param authorizationCode the {@link OAuth2AuthorizationCode}
* @param redirectUri the redirect uri
* @param state the state
* @param scopes the authorized scope(s)
* @since 0.4.0
*/
public OAuth2AuthorizationCodeRequestAuthenticationToken(String authorizationUri, String clientId, Authentication principal,
OAuth2AuthorizationCode authorizationCode, @Nullable String redirectUri, @Nullable String state, @Nullable Set<String> scopes) {
super(Collections.emptyList());
Assert.hasText(authorizationUri, "authorizationUri cannot be empty");
Assert.hasText(clientId, "clientId cannot be empty");
Assert.notNull(principal, "principal cannot be null");
Assert.notNull(authorizationCode, "authorizationCode cannot be null");
this.authorizationUri = authorizationUri;
this.clientId = clientId;
this.principal = principal;
this.authorizationCode = authorizationCode;
this.redirectUri = redirectUri;
this.state = state;
this.scopes = Collections.unmodifiableSet(
scopes != null ?
new HashSet<>(scopes) :
Collections.emptySet());
this.additionalParameters = Collections.emptyMap();
setAuthenticated(true);
}
@Override
@@ -94,15 +153,6 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationToken extends Abs
return this.redirectUri;
}
/**
* Returns the requested (or authorized) scope(s).
*
* @return the requested (or authorized) scope(s), or an empty {@code Set} if not available
*/
public Set<String> getScopes() {
return this.scopes;
}
/**
* Returns the state.
*
@@ -113,34 +163,24 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationToken extends Abs
return this.state;
}
/**
* Returns the requested (or authorized) scope(s).
*
* @return the requested (or authorized) scope(s), or an empty {@code Set} if not available
*/
public Set<String> getScopes() {
return this.scopes;
}
/**
* Returns the additional parameters.
*
* @return the additional parameters
* @return the additional parameters, or an empty {@code Map} if not available
*/
public Map<String, Object> getAdditionalParameters() {
return this.additionalParameters;
}
/**
* Returns {@code true} if authorization consent is required, {@code false} otherwise.
*
* @return {@code true} if authorization consent is required, {@code false} otherwise
*/
public boolean isConsentRequired() {
return this.consentRequired;
}
/**
* Returns {@code true} if this {@code Authentication} represents an authorization consent request,
* {@code false} otherwise.
*
* @return {@code true} if this {@code Authentication} represents an authorization consent request, {@code false} otherwise
*/
public boolean isConsent() {
return this.consent;
}
/**
* Returns the {@link OAuth2AuthorizationCode}.
*
@@ -151,170 +191,4 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationToken extends Abs
return this.authorizationCode;
}
/**
* Returns a new {@link Builder}, initialized with the given client identifier
* and {@code Principal} (Resource Owner).
*
* @param clientId the client identifier
* @param principal the {@code Principal} (Resource Owner)
* @return the {@link Builder}
*/
public static Builder with(@NonNull String clientId, @NonNull Authentication principal) {
Assert.hasText(clientId, "clientId cannot be empty");
Assert.notNull(principal, "principal cannot be null");
return new Builder(clientId, principal);
}
/**
* A builder for {@link OAuth2AuthorizationCodeRequestAuthenticationToken}.
*/
public static final class Builder implements Serializable {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
private String authorizationUri;
private String clientId;
private Authentication principal;
private String redirectUri;
private Set<String> scopes;
private String state;
private Map<String, Object> additionalParameters;
private boolean consentRequired;
private boolean consent;
private OAuth2AuthorizationCode authorizationCode;
private Builder(String clientId, Authentication principal) {
this.clientId = clientId;
this.principal = principal;
}
/**
* Sets the authorization URI.
*
* @param authorizationUri the authorization URI
* @return the {@link Builder}
*/
public Builder authorizationUri(String authorizationUri) {
this.authorizationUri = authorizationUri;
return this;
}
/**
* Sets the redirect uri.
*
* @param redirectUri the redirect uri
* @return the {@link Builder}
*/
public Builder redirectUri(String redirectUri) {
this.redirectUri = redirectUri;
return this;
}
/**
* Sets the requested (or authorized) scope(s).
*
* @param scopes the requested (or authorized) scope(s)
* @return the {@link Builder}
*/
public Builder scopes(Set<String> scopes) {
if (scopes != null) {
this.scopes = new HashSet<>(scopes);
}
return this;
}
/**
* Sets the state.
*
* @param state the state
* @return the {@link Builder}
*/
public Builder state(String state) {
this.state = state;
return this;
}
/**
* Sets the additional parameters.
*
* @param additionalParameters the additional parameters
* @return the {@link Builder}
*/
public Builder additionalParameters(Map<String, Object> additionalParameters) {
if (additionalParameters != null) {
this.additionalParameters = new HashMap<>(additionalParameters);
}
return this;
}
/**
* Set to {@code true} if authorization consent is required, {@code false} otherwise.
*
* @param consentRequired {@code true} if authorization consent is required, {@code false} otherwise
* @return the {@link Builder}
*/
public Builder consentRequired(boolean consentRequired) {
this.consentRequired = consentRequired;
return this;
}
/**
* Set to {@code true} if this {@code Authentication} represents an authorization consent request, {@code false} otherwise.
*
* @param consent {@code true} if this {@code Authentication} represents an authorization consent request, {@code false} otherwise
* @return the {@link Builder}
*/
public Builder consent(boolean consent) {
this.consent = consent;
return this;
}
/**
* Sets the {@link OAuth2AuthorizationCode}.
*
* @param authorizationCode the {@link OAuth2AuthorizationCode}
* @return the {@link Builder}
*/
public Builder authorizationCode(OAuth2AuthorizationCode authorizationCode) {
this.authorizationCode = authorizationCode;
return this;
}
/**
* Builds a new {@link OAuth2AuthorizationCodeRequestAuthenticationToken}.
*
* @return the {@link OAuth2AuthorizationCodeRequestAuthenticationToken}
*/
public OAuth2AuthorizationCodeRequestAuthenticationToken build() {
Assert.hasText(this.authorizationUri, "authorizationUri cannot be empty");
if (this.consent) {
Assert.hasText(this.state, "state cannot be empty");
}
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
new OAuth2AuthorizationCodeRequestAuthenticationToken();
authentication.authorizationUri = this.authorizationUri;
authentication.clientId = this.clientId;
authentication.principal = this.principal;
authentication.redirectUri = this.redirectUri;
authentication.scopes = Collections.unmodifiableSet(
!CollectionUtils.isEmpty(this.scopes) ?
this.scopes :
Collections.emptySet());
authentication.state = this.state;
authentication.additionalParameters = Collections.unmodifiableMap(
!CollectionUtils.isEmpty(this.additionalParameters) ?
this.additionalParameters :
Collections.emptyMap());
authentication.consentRequired = this.consentRequired;
authentication.consent = this.consent;
authentication.authorizationCode = this.authorizationCode;
if (this.authorizationCode != null || this.consentRequired) {
authentication.setAuthenticated(true);
}
return authentication;
}
}
}

View File

@@ -0,0 +1,211 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import java.util.Set;
import java.util.function.Consumer;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
/**
* A {@code Consumer} providing access to the {@link OAuth2AuthorizationCodeRequestAuthenticationContext}
* containing an {@link OAuth2AuthorizationCodeRequestAuthenticationToken}
* and is the default {@link OAuth2AuthorizationCodeRequestAuthenticationProvider#setAuthenticationValidator(Consumer) authentication validator}
* used for validating specific OAuth 2.0 Authorization Request parameters used in the Authorization Code Grant.
*
* <p>
* The default implementation first validates {@link OAuth2AuthorizationCodeRequestAuthenticationToken#getRedirectUri()}
* and then {@link OAuth2AuthorizationCodeRequestAuthenticationToken#getScopes()}.
* If validation fails, an {@link OAuth2AuthorizationCodeRequestAuthenticationException} is thrown.
*
* @author Joe Grandja
* @since 0.4.0
* @see OAuth2AuthorizationCodeRequestAuthenticationContext
* @see OAuth2AuthorizationCodeRequestAuthenticationToken
* @see OAuth2AuthorizationCodeRequestAuthenticationProvider#setAuthenticationValidator(Consumer)
*/
public final class OAuth2AuthorizationCodeRequestAuthenticationValidator implements Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> {
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1";
/**
* The default validator for {@link OAuth2AuthorizationCodeRequestAuthenticationToken#getScopes()}.
*/
public static final Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> DEFAULT_SCOPE_VALIDATOR =
OAuth2AuthorizationCodeRequestAuthenticationValidator::validateScope;
/**
* The default validator for {@link OAuth2AuthorizationCodeRequestAuthenticationToken#getRedirectUri()}.
*/
public static final Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> DEFAULT_REDIRECT_URI_VALIDATOR =
OAuth2AuthorizationCodeRequestAuthenticationValidator::validateRedirectUri;
private final Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator =
DEFAULT_REDIRECT_URI_VALIDATOR.andThen(DEFAULT_SCOPE_VALIDATOR);
@Override
public void accept(OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) {
this.authenticationValidator.accept(authenticationContext);
}
private static void validateScope(OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) {
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
authenticationContext.getAuthentication();
RegisteredClient registeredClient = authenticationContext.getRegisteredClient();
Set<String> requestedScopes = authorizationCodeRequestAuthentication.getScopes();
Set<String> allowedScopes = registeredClient.getScopes();
if (!requestedScopes.isEmpty() && !allowedScopes.containsAll(requestedScopes)) {
throwError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE,
authorizationCodeRequestAuthentication, registeredClient);
}
}
private static void validateRedirectUri(OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) {
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
authenticationContext.getAuthentication();
RegisteredClient registeredClient = authenticationContext.getRegisteredClient();
String requestedRedirectUri = authorizationCodeRequestAuthentication.getRedirectUri();
if (StringUtils.hasText(requestedRedirectUri)) {
// ***** redirect_uri is available in authorization request
UriComponents requestedRedirect = null;
try {
requestedRedirect = UriComponentsBuilder.fromUriString(requestedRedirectUri).build();
} catch (Exception ex) { }
if (requestedRedirect == null || requestedRedirect.getFragment() != null) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI,
authorizationCodeRequestAuthentication, registeredClient);
}
String requestedRedirectHost = requestedRedirect.getHost();
if (requestedRedirectHost == null || requestedRedirectHost.equals("localhost")) {
// As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07#section-9.7.1
// While redirect URIs using localhost (i.e., "http://localhost:{port}/{path}")
// function similarly to loopback IP redirects described in Section 10.3.3,
// the use of "localhost" is NOT RECOMMENDED.
OAuth2Error error = new OAuth2Error(
OAuth2ErrorCodes.INVALID_REQUEST,
"localhost is not allowed for the redirect_uri (" + requestedRedirectUri + "). " +
"Use the IP literal (127.0.0.1) instead.",
"https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07#section-9.7.1");
throwError(error, OAuth2ParameterNames.REDIRECT_URI,
authorizationCodeRequestAuthentication, registeredClient);
}
if (!isLoopbackAddress(requestedRedirectHost)) {
// As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07#section-9.7
// When comparing client redirect URIs against pre-registered URIs,
// authorization servers MUST utilize exact string matching.
if (!registeredClient.getRedirectUris().contains(requestedRedirectUri)) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI,
authorizationCodeRequestAuthentication, registeredClient);
}
} else {
// As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07#section-10.3.3
// The authorization server MUST allow any port to be specified at the
// time of the request for loopback IP redirect URIs, to accommodate
// clients that obtain an available ephemeral port from the operating
// system at the time of the request.
boolean validRedirectUri = false;
for (String registeredRedirectUri : registeredClient.getRedirectUris()) {
UriComponentsBuilder registeredRedirect = UriComponentsBuilder.fromUriString(registeredRedirectUri);
registeredRedirect.port(requestedRedirect.getPort());
if (registeredRedirect.build().toString().equals(requestedRedirect.toString())) {
validRedirectUri = true;
break;
}
}
if (!validRedirectUri) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI,
authorizationCodeRequestAuthentication, registeredClient);
}
}
} else {
// ***** redirect_uri is NOT available in authorization request
if (authorizationCodeRequestAuthentication.getScopes().contains(OidcScopes.OPENID) ||
registeredClient.getRedirectUris().size() != 1) {
// redirect_uri is REQUIRED for OpenID Connect
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI,
authorizationCodeRequestAuthentication, registeredClient);
}
}
}
private static boolean isLoopbackAddress(String host) {
// IPv6 loopback address should either be "0:0:0:0:0:0:0:1" or "::1"
if ("[0:0:0:0:0:0:0:1]".equals(host) || "[::1]".equals(host)) {
return true;
}
// IPv4 loopback address ranges from 127.0.0.1 to 127.255.255.255
String[] ipv4Octets = host.split("\\.");
if (ipv4Octets.length != 4) {
return false;
}
try {
int[] address = new int[ipv4Octets.length];
for (int i=0; i < ipv4Octets.length; i++) {
address[i] = Integer.parseInt(ipv4Octets[i]);
}
return address[0] == 127 && address[1] >= 0 && address[1] <= 255 && address[2] >= 0 &&
address[2] <= 255 && address[3] >= 1 && address[3] <= 255;
} catch (NumberFormatException ex) {
return false;
}
}
private static void throwError(String errorCode, String parameterName,
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication,
RegisteredClient registeredClient) {
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, ERROR_URI);
throwError(error, parameterName, authorizationCodeRequestAuthentication, registeredClient);
}
private static void throwError(OAuth2Error error, String parameterName,
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication,
RegisteredClient registeredClient) {
String redirectUri = StringUtils.hasText(authorizationCodeRequestAuthentication.getRedirectUri()) ?
authorizationCodeRequestAuthentication.getRedirectUri() :
registeredClient.getRedirectUris().iterator().next();
if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_REQUEST) &&
parameterName.equals(OAuth2ParameterNames.REDIRECT_URI)) {
redirectUri = null; // Prevent redirects
}
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
authorizationCodeRequestAuthentication.getAuthorizationUri(), authorizationCodeRequestAuthentication.getClientId(),
(Authentication) authorizationCodeRequestAuthentication.getPrincipal(), redirectUri,
authorizationCodeRequestAuthentication.getState(), authorizationCodeRequestAuthentication.getScopes(),
authorizationCodeRequestAuthentication.getAdditionalParameters());
authorizationCodeRequestAuthenticationResult.setAuthenticated(true);
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, authorizationCodeRequestAuthenticationResult);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,9 +15,12 @@
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import org.springframework.security.oauth2.core.authentication.OAuth2AuthenticationContext;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
@@ -33,11 +36,26 @@ import org.springframework.util.Assert;
* @since 0.2.1
* @see OAuth2AuthenticationContext
* @see OAuth2AuthorizationConsent
* @see OAuth2AuthorizationConsentAuthenticationProvider#setAuthorizationConsentCustomizer(Consumer)
*/
public final class OAuth2AuthorizationConsentAuthenticationContext extends OAuth2AuthenticationContext {
public final class OAuth2AuthorizationConsentAuthenticationContext implements OAuth2AuthenticationContext {
private final Map<Object, Object> context;
private OAuth2AuthorizationConsentAuthenticationContext(Map<Object, Object> context) {
super(context);
this.context = Collections.unmodifiableMap(new HashMap<>(context));
}
@SuppressWarnings("unchecked")
@Nullable
@Override
public <V> V get(Object key) {
return hasKey(key) ? (V) this.context.get(key) : null;
}
@Override
public boolean hasKey(Object key) {
Assert.notNull(key, "key cannot be null");
return this.context.containsKey(key);
}
/**
@@ -77,12 +95,12 @@ public final class OAuth2AuthorizationConsentAuthenticationContext extends OAuth
}
/**
* Constructs a new {@link Builder} with the provided {@link OAuth2AuthorizationCodeRequestAuthenticationToken}.
* Constructs a new {@link Builder} with the provided {@link OAuth2AuthorizationConsentAuthenticationToken}.
*
* @param authentication the {@link OAuth2AuthorizationCodeRequestAuthenticationToken}
* @param authentication the {@link OAuth2AuthorizationConsentAuthenticationToken}
* @return the {@link Builder}
*/
public static Builder with(OAuth2AuthorizationCodeRequestAuthenticationToken authentication) {
public static Builder with(OAuth2AuthorizationConsentAuthenticationToken authentication) {
return new Builder(authentication);
}
@@ -91,7 +109,7 @@ public final class OAuth2AuthorizationConsentAuthenticationContext extends OAuth
*/
public static final class Builder extends AbstractBuilder<OAuth2AuthorizationConsentAuthenticationContext, Builder> {
private Builder(OAuth2AuthorizationCodeRequestAuthenticationToken authentication) {
private Builder(OAuth2AuthorizationConsentAuthenticationToken authentication) {
super(authentication);
}

View File

@@ -0,0 +1,359 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
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.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* An {@link AuthenticationProvider} implementation for the OAuth 2.0 Authorization Consent
* used in the Authorization Code Grant.
*
* @author Joe Grandja
* @since 0.4.0
* @see OAuth2AuthorizationConsentAuthenticationToken
* @see OAuth2AuthorizationConsent
* @see OAuth2AuthorizationCodeRequestAuthenticationProvider
* @see RegisteredClientRepository
* @see OAuth2AuthorizationService
* @see OAuth2AuthorizationConsentService
*/
public final class OAuth2AuthorizationConsentAuthenticationProvider implements AuthenticationProvider {
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1";
private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE);
private final Log logger = LogFactory.getLog(getClass());
private final RegisteredClientRepository registeredClientRepository;
private final OAuth2AuthorizationService authorizationService;
private final OAuth2AuthorizationConsentService authorizationConsentService;
private OAuth2TokenGenerator<OAuth2AuthorizationCode> authorizationCodeGenerator = new OAuth2AuthorizationCodeGenerator();
private Consumer<OAuth2AuthorizationConsentAuthenticationContext> authorizationConsentCustomizer;
/**
* Constructs an {@code OAuth2AuthorizationConsentAuthenticationProvider} using the provided parameters.
*
* @param registeredClientRepository the repository of registered clients
* @param authorizationService the authorization service
* @param authorizationConsentService the authorization consent service
*/
public OAuth2AuthorizationConsentAuthenticationProvider(RegisteredClientRepository registeredClientRepository,
OAuth2AuthorizationService authorizationService, OAuth2AuthorizationConsentService authorizationConsentService) {
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
Assert.notNull(authorizationService, "authorizationService cannot be null");
Assert.notNull(authorizationConsentService, "authorizationConsentService cannot be null");
this.registeredClientRepository = registeredClientRepository;
this.authorizationService = authorizationService;
this.authorizationConsentService = authorizationConsentService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication =
(OAuth2AuthorizationConsentAuthenticationToken) authentication;
OAuth2Authorization authorization = this.authorizationService.findByToken(
authorizationConsentAuthentication.getState(), STATE_TOKEN_TYPE);
if (authorization == null) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE,
authorizationConsentAuthentication, null, null);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Retrieved authorization with authorization consent state");
}
// The 'in-flight' authorization must be associated to the current principal
Authentication principal = (Authentication) authorizationConsentAuthentication.getPrincipal();
if (!isPrincipalAuthenticated(principal) || !principal.getName().equals(authorization.getPrincipalName())) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE,
authorizationConsentAuthentication, null, null);
}
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(
authorizationConsentAuthentication.getClientId());
if (registeredClient == null || !registeredClient.getId().equals(authorization.getRegisteredClientId())) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID,
authorizationConsentAuthentication, registeredClient, null);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Retrieved registered client");
}
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
Set<String> requestedScopes = authorizationRequest.getScopes();
Set<String> authorizedScopes = new HashSet<>(authorizationConsentAuthentication.getScopes());
if (!requestedScopes.containsAll(authorizedScopes)) {
throwError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE,
authorizationConsentAuthentication, registeredClient, authorizationRequest);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Validated authorization consent request parameters");
}
OAuth2AuthorizationConsent currentAuthorizationConsent = this.authorizationConsentService.findById(
authorization.getRegisteredClientId(), authorization.getPrincipalName());
Set<String> currentAuthorizedScopes = currentAuthorizationConsent != null ?
currentAuthorizationConsent.getScopes() : Collections.emptySet();
if (!currentAuthorizedScopes.isEmpty()) {
for (String requestedScope : requestedScopes) {
if (currentAuthorizedScopes.contains(requestedScope)) {
authorizedScopes.add(requestedScope);
}
}
}
if (!authorizedScopes.isEmpty() && requestedScopes.contains(OidcScopes.OPENID)) {
// 'openid' scope is auto-approved as it does not require consent
authorizedScopes.add(OidcScopes.OPENID);
}
OAuth2AuthorizationConsent.Builder authorizationConsentBuilder;
if (currentAuthorizationConsent != null) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Retrieved existing authorization consent");
}
authorizationConsentBuilder = OAuth2AuthorizationConsent.from(currentAuthorizationConsent);
} else {
authorizationConsentBuilder = OAuth2AuthorizationConsent.withId(
authorization.getRegisteredClientId(), authorization.getPrincipalName());
}
authorizedScopes.forEach(authorizationConsentBuilder::scope);
if (this.authorizationConsentCustomizer != null) {
// @formatter:off
OAuth2AuthorizationConsentAuthenticationContext authorizationConsentAuthenticationContext =
OAuth2AuthorizationConsentAuthenticationContext.with(authorizationConsentAuthentication)
.authorizationConsent(authorizationConsentBuilder)
.registeredClient(registeredClient)
.authorization(authorization)
.authorizationRequest(authorizationRequest)
.build();
// @formatter:on
this.authorizationConsentCustomizer.accept(authorizationConsentAuthenticationContext);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Customized authorization consent");
}
}
Set<GrantedAuthority> authorities = new HashSet<>();
authorizationConsentBuilder.authorities(authorities::addAll);
if (authorities.isEmpty()) {
// Authorization consent denied (or revoked)
if (currentAuthorizationConsent != null) {
this.authorizationConsentService.remove(currentAuthorizationConsent);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Revoked authorization consent");
}
}
this.authorizationService.remove(authorization);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Removed authorization");
}
throwError(OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID,
authorizationConsentAuthentication, registeredClient, authorizationRequest);
}
OAuth2AuthorizationConsent authorizationConsent = authorizationConsentBuilder.build();
if (!authorizationConsent.equals(currentAuthorizationConsent)) {
this.authorizationConsentService.save(authorizationConsent);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Saved authorization consent");
}
}
OAuth2TokenContext tokenContext = createAuthorizationCodeTokenContext(
authorizationConsentAuthentication, registeredClient, authorization, authorizedScopes);
OAuth2AuthorizationCode authorizationCode = this.authorizationCodeGenerator.generate(tokenContext);
if (authorizationCode == null) {
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
"The token generator failed to generate the authorization code.", ERROR_URI);
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Generated authorization code");
}
OAuth2Authorization updatedAuthorization = OAuth2Authorization.from(authorization)
.authorizedScopes(authorizedScopes)
.token(authorizationCode)
.attributes(attrs -> {
attrs.remove(OAuth2ParameterNames.STATE);
})
.build();
this.authorizationService.save(updatedAuthorization);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Saved authorization");
}
String redirectUri = authorizationRequest.getRedirectUri();
if (!StringUtils.hasText(redirectUri)) {
redirectUri = registeredClient.getRedirectUris().iterator().next();
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Authenticated authorization consent request");
}
return new OAuth2AuthorizationCodeRequestAuthenticationToken(
authorizationRequest.getAuthorizationUri(), registeredClient.getClientId(), principal, authorizationCode,
redirectUri, authorizationRequest.getState(), authorizedScopes);
}
@Override
public boolean supports(Class<?> authentication) {
return OAuth2AuthorizationConsentAuthenticationToken.class.isAssignableFrom(authentication);
}
/**
* Sets the {@link OAuth2TokenGenerator} that generates the {@link OAuth2AuthorizationCode}.
*
* @param authorizationCodeGenerator the {@link OAuth2TokenGenerator} that generates the {@link OAuth2AuthorizationCode}
*/
public void setAuthorizationCodeGenerator(OAuth2TokenGenerator<OAuth2AuthorizationCode> authorizationCodeGenerator) {
Assert.notNull(authorizationCodeGenerator, "authorizationCodeGenerator cannot be null");
this.authorizationCodeGenerator = authorizationCodeGenerator;
}
/**
* Sets the {@code Consumer} providing access to the {@link OAuth2AuthorizationConsentAuthenticationContext}
* containing an {@link OAuth2AuthorizationConsent.Builder} and additional context information.
*
* <p>
* The following context attributes are available:
* <ul>
* <li>The {@link OAuth2AuthorizationConsent.Builder} used to build the authorization consent
* prior to {@link OAuth2AuthorizationConsentService#save(OAuth2AuthorizationConsent)}.</li>
* <li>The {@link Authentication} of type
* {@link OAuth2AuthorizationConsentAuthenticationToken}.</li>
* <li>The {@link RegisteredClient} associated with the authorization request.</li>
* <li>The {@link OAuth2Authorization} associated with the state token presented in the
* authorization consent request.</li>
* <li>The {@link OAuth2AuthorizationRequest} associated with the authorization consent request.</li>
* </ul>
*
* @param authorizationConsentCustomizer the {@code Consumer} providing access to the
* {@link OAuth2AuthorizationConsentAuthenticationContext} containing an {@link OAuth2AuthorizationConsent.Builder}
*/
public void setAuthorizationConsentCustomizer(Consumer<OAuth2AuthorizationConsentAuthenticationContext> authorizationConsentCustomizer) {
Assert.notNull(authorizationConsentCustomizer, "authorizationConsentCustomizer cannot be null");
this.authorizationConsentCustomizer = authorizationConsentCustomizer;
}
private static OAuth2TokenContext createAuthorizationCodeTokenContext(
OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication,
RegisteredClient registeredClient, OAuth2Authorization authorization, Set<String> authorizedScopes) {
// @formatter:off
return DefaultOAuth2TokenContext.builder()
.registeredClient(registeredClient)
.principal((Authentication) authorizationConsentAuthentication.getPrincipal())
.authorization(authorization)
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
.tokenType(new OAuth2TokenType(OAuth2ParameterNames.CODE))
.authorizedScopes(authorizedScopes)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrant(authorizationConsentAuthentication)
.build();
// @formatter:on
}
private static boolean isPrincipalAuthenticated(Authentication principal) {
return principal != null &&
!AnonymousAuthenticationToken.class.isAssignableFrom(principal.getClass()) &&
principal.isAuthenticated();
}
private static void throwError(String errorCode, String parameterName,
OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication,
RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest) {
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, ERROR_URI);
throwError(error, parameterName, authorizationConsentAuthentication, registeredClient, authorizationRequest);
}
private static void throwError(OAuth2Error error, String parameterName,
OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication,
RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest) {
String redirectUri = resolveRedirectUri(authorizationRequest, registeredClient);
if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_REQUEST) &&
(parameterName.equals(OAuth2ParameterNames.CLIENT_ID) ||
parameterName.equals(OAuth2ParameterNames.STATE))) {
redirectUri = null; // Prevent redirects
}
String state = authorizationRequest != null ?
authorizationRequest.getState() :
authorizationConsentAuthentication.getState();
Set<String> requestedScopes = authorizationRequest != null ?
authorizationRequest.getScopes() :
authorizationConsentAuthentication.getScopes();
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
authorizationConsentAuthentication.getAuthorizationUri(), authorizationConsentAuthentication.getClientId(),
(Authentication) authorizationConsentAuthentication.getPrincipal(), redirectUri,
state, requestedScopes, null);
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, authorizationCodeRequestAuthenticationResult);
}
private static String resolveRedirectUri(OAuth2AuthorizationRequest authorizationRequest, RegisteredClient registeredClient) {
if (authorizationRequest != null && StringUtils.hasText(authorizationRequest.getRedirectUri())) {
return authorizationRequest.getRedirectUri();
}
if (registeredClient != null) {
return registeredClient.getRedirectUris().iterator().next();
}
return null;
}
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.server.authorization.util.SpringAuthorizationServerVersion;
import org.springframework.util.Assert;
/**
* An {@link Authentication} implementation for the OAuth 2.0 Authorization Consent
* used in the Authorization Code Grant.
*
* @author Joe Grandja
* @since 0.4.0
* @see OAuth2AuthorizationConsentAuthenticationProvider
* @see OAuth2AuthorizationCodeRequestAuthenticationProvider
*/
public class OAuth2AuthorizationConsentAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
private final String authorizationUri;
private final String clientId;
private final Authentication principal;
private final String state;
private final Set<String> scopes;
private final Map<String, Object> additionalParameters;
/**
* Constructs an {@code OAuth2AuthorizationConsentAuthenticationToken} using the provided parameters.
*
* @param authorizationUri the authorization URI
* @param clientId the client identifier
* @param principal the {@code Principal} (Resource Owner)
* @param state the state
* @param scopes the requested (or authorized) scope(s)
* @param additionalParameters the additional parameters
*/
public OAuth2AuthorizationConsentAuthenticationToken(String authorizationUri, String clientId, Authentication principal,
String state, @Nullable Set<String> scopes, @Nullable Map<String, Object> additionalParameters) {
super(Collections.emptyList());
Assert.hasText(authorizationUri, "authorizationUri cannot be empty");
Assert.hasText(clientId, "clientId cannot be empty");
Assert.notNull(principal, "principal cannot be null");
Assert.hasText(state, "state cannot be empty");
this.authorizationUri = authorizationUri;
this.clientId = clientId;
this.principal = principal;
this.state = state;
this.scopes = Collections.unmodifiableSet(
scopes != null ?
new HashSet<>(scopes) :
Collections.emptySet());
this.additionalParameters = Collections.unmodifiableMap(
additionalParameters != null ?
new HashMap<>(additionalParameters) :
Collections.emptyMap());
setAuthenticated(true);
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public Object getCredentials() {
return "";
}
/**
* Returns the authorization URI.
*
* @return the authorization URI
*/
public String getAuthorizationUri() {
return this.authorizationUri;
}
/**
* Returns the client identifier.
*
* @return the client identifier
*/
public String getClientId() {
return this.clientId;
}
/**
* Returns the state.
*
* @return the state
*/
public String getState() {
return this.state;
}
/**
* Returns the requested (or authorized) scope(s).
*
* @return the requested (or authorized) scope(s), or an empty {@code Set} if not available
*/
public Set<String> getScopes() {
return this.scopes;
}
/**
* Returns the additional parameters.
*
* @return the additional parameters, or an empty {@code Map} if not available
*/
public Map<String, Object> getAdditionalParameters() {
return this.additionalParameters;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@ 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.security.oauth2.server.authorization.util.SpringAuthorizationServerVersion;
import org.springframework.util.Assert;
/**
@@ -37,7 +37,7 @@ import org.springframework.util.Assert;
* @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 static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
private final AuthorizationGrantType authorizationGrantType;
private final Authentication clientPrincipal;
private final Map<String, Object> additionalParameters;

View File

@@ -23,8 +23,8 @@ import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.Transient;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.Version;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.util.SpringAuthorizationServerVersion;
import org.springframework.util.Assert;
/**
@@ -42,7 +42,7 @@ import org.springframework.util.Assert;
*/
@Transient
public class OAuth2ClientAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
private final String clientId;
private final RegisteredClient registeredClient;
private final ClientAuthenticationMethod clientAuthenticationMethod;

View File

@@ -15,9 +15,13 @@
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
@@ -28,11 +32,11 @@ 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.OAuth2Token;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
@@ -56,6 +60,7 @@ import static org.springframework.security.oauth2.server.authorization.authentic
*/
public final class OAuth2ClientCredentialsAuthenticationProvider implements AuthenticationProvider {
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
private final Log logger = LogFactory.getLog(getClass());
private final OAuth2AuthorizationService authorizationService;
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
@@ -83,11 +88,15 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth
getAuthenticatedClientElseThrowInvalidClient(clientCredentialsAuthentication);
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Retrieved registered client");
}
if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.CLIENT_CREDENTIALS)) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
}
Set<String> authorizedScopes = registeredClient.getScopes(); // Default to configured scopes
Set<String> authorizedScopes = Collections.emptySet();
if (!CollectionUtils.isEmpty(clientCredentialsAuthentication.getScopes())) {
for (String requestedScope : clientCredentialsAuthentication.getScopes()) {
if (!registeredClient.getScopes().contains(requestedScope)) {
@@ -97,11 +106,15 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth
authorizedScopes = new LinkedHashSet<>(clientCredentialsAuthentication.getScopes());
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Validated token request parameters");
}
// @formatter:off
OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder()
.registeredClient(registeredClient)
.principal(clientPrincipal)
.providerContext(ProviderContextHolder.getProviderContext())
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
.authorizedScopes(authorizedScopes)
.tokenType(OAuth2TokenType.ACCESS_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
@@ -115,6 +128,11 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth
"The token generator failed to generate the access token.", ERROR_URI);
throw new OAuth2AuthenticationException(error);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Generated access token");
}
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
@@ -123,7 +141,7 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
.principalName(clientPrincipal.getName())
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.attribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME, authorizedScopes);
.authorizedScopes(authorizedScopes);
// @formatter:on
if (generatedAccessToken instanceof ClaimAccessor) {
authorizationBuilder.token(accessToken, (metadata) ->
@@ -136,6 +154,12 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth
this.authorizationService.save(authorization);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Saved authorization");
// This log is kept separate for consistency with other providers
this.logger.trace("Authenticated token request");
}
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken);
}

View File

@@ -21,6 +21,9 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
@@ -32,15 +35,15 @@ 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.OAuth2Token;
import org.springframework.security.oauth2.core.OAuth2TokenType;
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.Jwt;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
@@ -65,6 +68,7 @@ import static org.springframework.security.oauth2.server.authorization.authentic
public final class OAuth2RefreshTokenAuthenticationProvider implements AuthenticationProvider {
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
private static final OAuth2TokenType ID_TOKEN_TOKEN_TYPE = new OAuth2TokenType(OidcParameterNames.ID_TOKEN);
private final Log logger = LogFactory.getLog(getClass());
private final OAuth2AuthorizationService authorizationService;
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
@@ -92,12 +96,20 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic
getAuthenticatedClientElseThrowInvalidClient(refreshTokenAuthentication);
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Retrieved registered client");
}
OAuth2Authorization authorization = this.authorizationService.findByToken(
refreshTokenAuthentication.getRefreshToken(), OAuth2TokenType.REFRESH_TOKEN);
if (authorization == null) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Retrieved authorization with refresh token");
}
if (!registeredClient.getId().equals(authorization.getRegisteredClientId())) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
}
@@ -118,10 +130,15 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic
// The requested scope MUST NOT include any scope not originally granted by the resource owner,
// and if omitted is treated as equal to the scope originally granted by the resource owner.
Set<String> scopes = refreshTokenAuthentication.getScopes();
Set<String> authorizedScopes = authorization.getAttribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME);
Set<String> authorizedScopes = authorization.getAuthorizedScopes();
if (!authorizedScopes.containsAll(scopes)) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_SCOPE);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Validated token request parameters");
}
if (scopes.isEmpty()) {
scopes = authorizedScopes;
}
@@ -130,7 +147,7 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
.registeredClient(registeredClient)
.principal(authorization.getAttribute(Principal.class.getName()))
.providerContext(ProviderContextHolder.getProviderContext())
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
.authorization(authorization)
.authorizedScopes(scopes)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
@@ -147,6 +164,11 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic
"The token generator failed to generate the access token.", ERROR_URI);
throw new OAuth2AuthenticationException(error);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Generated access token");
}
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
@@ -169,6 +191,11 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic
"The token generator failed to generate the refresh token.", ERROR_URI);
throw new OAuth2AuthenticationException(error);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Generated refresh token");
}
currentRefreshToken = (OAuth2RefreshToken) generatedRefreshToken;
authorizationBuilder.refreshToken(currentRefreshToken);
}
@@ -188,6 +215,11 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic
"The token generator failed to generate the ID token.", ERROR_URI);
throw new OAuth2AuthenticationException(error);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Generated id token");
}
idToken = new OidcIdToken(generatedIdToken.getTokenValue(), generatedIdToken.getIssuedAt(),
generatedIdToken.getExpiresAt(), ((Jwt) generatedIdToken).getClaims());
authorizationBuilder.token(idToken, (metadata) ->
@@ -200,12 +232,20 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic
this.authorizationService.save(authorization);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Saved authorization");
}
Map<String, Object> additionalParameters = Collections.emptyMap();
if (idToken != null) {
additionalParameters = new HashMap<>();
additionalParameters.put(OidcParameterNames.ID_TOKEN, idToken.getTokenValue());
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Authenticated token request");
}
return new OAuth2AccessTokenAuthenticationToken(
registeredClient, clientPrincipal, accessToken, currentRefreshToken, additionalParameters);
}

View File

@@ -20,17 +20,20 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2Token;
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.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenIntrospection;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.util.Assert;
@@ -53,6 +56,7 @@ public final class OAuth2TokenIntrospectionAuthenticationProvider implements Aut
private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class);
private static final TypeDescriptor LIST_STRING_TYPE_DESCRIPTOR =
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class));
private final Log logger = LogFactory.getLog(getClass());
private final RegisteredClientRepository registeredClientRepository;
private final OAuth2AuthorizationService authorizationService;
@@ -81,13 +85,23 @@ public final class OAuth2TokenIntrospectionAuthenticationProvider implements Aut
OAuth2Authorization authorization = this.authorizationService.findByToken(
tokenIntrospectionAuthentication.getToken(), null);
if (authorization == null) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not authenticate token introspection request since token was not found");
}
// Return the authentication request when token not found
return tokenIntrospectionAuthentication;
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Retrieved authorization with token");
}
OAuth2Authorization.Token<OAuth2Token> authorizedToken =
authorization.getToken(tokenIntrospectionAuthentication.getToken());
if (!authorizedToken.isActive()) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not introspect token since not active");
}
return new OAuth2TokenIntrospectionAuthenticationToken(tokenIntrospectionAuthentication.getToken(),
clientPrincipal, OAuth2TokenIntrospection.builder().build());
}
@@ -95,6 +109,10 @@ public final class OAuth2TokenIntrospectionAuthenticationProvider implements Aut
RegisteredClient authorizedClient = this.registeredClientRepository.findById(authorization.getRegisteredClientId());
OAuth2TokenIntrospection tokenClaims = withActiveTokenClaims(authorizedToken, authorizedClient);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Authenticated token introspection request");
}
return new OAuth2TokenIntrospectionAuthenticationToken(authorizedToken.getToken().getTokenValue(),
clientPrincipal, tokenClaims);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,8 +22,8 @@ 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.OAuth2TokenIntrospection;
import org.springframework.security.oauth2.core.Version;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenIntrospection;
import org.springframework.security.oauth2.server.authorization.util.SpringAuthorizationServerVersion;
import org.springframework.util.Assert;
/**
@@ -37,7 +37,7 @@ import org.springframework.util.Assert;
* @see OAuth2TokenIntrospectionAuthenticationProvider
*/
public class OAuth2TokenIntrospectionAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
private final String token;
private final Authentication clientPrincipal;
private final String tokenTypeHint;

View File

@@ -15,6 +15,9 @@
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
@@ -39,6 +42,7 @@ import static org.springframework.security.oauth2.server.authorization.authentic
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7009#section-2.1">Section 2.1 Revocation Request</a>
*/
public final class OAuth2TokenRevocationAuthenticationProvider implements AuthenticationProvider {
private final Log logger = LogFactory.getLog(getClass());
private final OAuth2AuthorizationService authorizationService;
/**
@@ -63,6 +67,9 @@ public final class OAuth2TokenRevocationAuthenticationProvider implements Authen
OAuth2Authorization authorization = this.authorizationService.findByToken(
tokenRevocationAuthentication.getToken(), null);
if (authorization == null) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not authenticate token revocation request since token was not found");
}
// Return the authentication request when token not found
return tokenRevocationAuthentication;
}
@@ -75,6 +82,12 @@ public final class OAuth2TokenRevocationAuthenticationProvider implements Authen
authorization = OAuth2AuthenticationProviderUtils.invalidate(authorization, token.getToken());
this.authorizationService.save(authorization);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Saved authorization with revoked token");
// This log is kept separate for consistency with other providers
this.logger.trace("Authenticated token revocation request");
}
return new OAuth2TokenRevocationAuthenticationToken(token.getToken(), clientPrincipal);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@ import org.springframework.lang.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.core.Version;
import org.springframework.security.oauth2.server.authorization.util.SpringAuthorizationServerVersion;
import org.springframework.util.Assert;
/**
@@ -34,7 +34,7 @@ import org.springframework.util.Assert;
* @see OAuth2TokenRevocationAuthenticationProvider
*/
public class OAuth2TokenRevocationAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
private final String token;
private final Authentication clientPrincipal;
private final String tokenTypeHint;

View File

@@ -15,6 +15,9 @@
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
@@ -42,6 +45,7 @@ import org.springframework.util.Assert;
*/
public final class PublicClientAuthenticationProvider implements AuthenticationProvider {
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-3.2.1";
private final Log logger = LogFactory.getLog(getClass());
private final RegisteredClientRepository registeredClientRepository;
private final CodeVerifierAuthenticator codeVerifierAuthenticator;
@@ -74,14 +78,26 @@ public final class PublicClientAuthenticationProvider implements AuthenticationP
throwInvalidClient(OAuth2ParameterNames.CLIENT_ID);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Retrieved registered client");
}
if (!registeredClient.getClientAuthenticationMethods().contains(
clientAuthentication.getClientAuthenticationMethod())) {
throwInvalidClient("authentication_method");
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Validated client authentication parameters");
}
// Validate the "code_verifier" parameter for the public client
this.codeVerifierAuthenticator.authenticateRequired(clientAuthentication, registeredClient);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Authenticated public client");
}
return new OAuth2ClientAuthenticationToken(registeredClient,
clientAuthentication.getClientAuthenticationMethod(), null);
}

View File

@@ -39,11 +39,11 @@ import org.springframework.jdbc.core.SqlParameterValue;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2TokenFormat;
import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
import org.springframework.security.oauth2.server.authorization.config.ConfigurationSettingNames;
import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.ConfigurationSettingNames;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -100,6 +100,8 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
+ " WHERE " + PK_FILTER;
// @formatter:on
private static final String COUNT_REGISTERED_CLIENT_SQL = "SELECT COUNT(*) FROM " + TABLE_NAME + " WHERE ";
private final JdbcOperations jdbcOperations;
private RowMapper<RegisteredClient> registeredClientRowMapper;
private Function<RegisteredClient, List<SqlParameterValue>> registeredClientParametersMapper;
@@ -141,11 +143,31 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
}
private void insertRegisteredClient(RegisteredClient registeredClient) {
assertUniqueIdentifiers(registeredClient);
List<SqlParameterValue> parameters = this.registeredClientParametersMapper.apply(registeredClient);
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray());
this.jdbcOperations.update(INSERT_REGISTERED_CLIENT_SQL, pss);
}
private void assertUniqueIdentifiers(RegisteredClient registeredClient) {
Integer count = this.jdbcOperations.queryForObject(
COUNT_REGISTERED_CLIENT_SQL + "client_id = ?",
Integer.class,
registeredClient.getClientId());
if (count != null && count > 0) {
throw new IllegalArgumentException("Registered client must be unique. " +
"Found duplicate client identifier: " + registeredClient.getClientId());
}
count = this.jdbcOperations.queryForObject(
COUNT_REGISTERED_CLIENT_SQL + "client_secret = ?",
Integer.class,
registeredClient.getClientSecret());
if (count != null && count > 0) {
throw new IllegalArgumentException("Registered client must be unique. " +
"Found duplicate client secret for identifier: " + registeredClient.getId());
}
}
@Override
public RegisteredClient findById(String id) {
Assert.hasText(id, "id cannot be empty");

View File

@@ -28,9 +28,9 @@ import java.util.function.Consumer;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.Version;
import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.oauth2.server.authorization.util.SpringAuthorizationServerVersion;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@@ -44,7 +44,7 @@ import org.springframework.util.StringUtils;
* @since 0.0.1
*/
public class RegisteredClient implements Serializable {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
private String id;
private String clientId;
private Instant clientIdIssuedAt;
@@ -243,7 +243,7 @@ public class RegisteredClient implements Serializable {
* A builder for {@link RegisteredClient}.
*/
public static class Builder implements Serializable {
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
private String id;
private String clientId;
private Instant clientIdIssuedAt;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configuration;
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration;
import java.util.HashSet;
import java.util.Set;
@@ -31,10 +31,10 @@ import org.springframework.context.annotation.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.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -57,8 +57,8 @@ public class OAuth2AuthorizationServerConfiguration {
// @formatter:off
public static void applyDefaultSecurity(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
RequestMatcher endpointsMatcher = authorizationServerConfigurer
.getEndpointsMatcher();
@@ -90,7 +90,7 @@ public class OAuth2AuthorizationServerConfiguration {
@Bean
RegisterMissingBeanPostProcessor registerMissingBeanPostProcessor() {
RegisterMissingBeanPostProcessor postProcessor = new RegisterMissingBeanPostProcessor();
postProcessor.addBeanDefinition(ProviderSettings.class, () -> ProviderSettings.builder().build());
postProcessor.addBeanDefinition(AuthorizationServerSettings.class, () -> AuthorizationServerSettings.builder().build());
return postProcessor;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configuration;
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration;
import java.util.ArrayList;
import java.util.List;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.util.matcher.RequestMatcher;
/**
@@ -32,9 +32,9 @@ abstract class AbstractOAuth2Configurer {
this.objectPostProcessor = objectPostProcessor;
}
abstract <B extends HttpSecurityBuilder<B>> void init(B builder);
abstract void init(HttpSecurity httpSecurity);
abstract <B extends HttpSecurityBuilder<B>> void configure(B builder);
abstract void configure(HttpSecurity httpSecurity);
abstract RequestMatcher getRequestMatcher();

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
import java.io.IOException;
import java.util.function.Supplier;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.UriComponentsBuilder;
/**
* A {@code Filter} that associates the {@link AuthorizationServerContext} to the {@link AuthorizationServerContextHolder}.
*
* @author Joe Grandja
* @since 0.2.2
* @see AuthorizationServerContext
* @see AuthorizationServerContextHolder
* @see AuthorizationServerSettings
*/
final class AuthorizationServerContextFilter extends OncePerRequestFilter {
private final AuthorizationServerSettings authorizationServerSettings;
AuthorizationServerContextFilter(AuthorizationServerSettings authorizationServerSettings) {
Assert.notNull(authorizationServerSettings, "authorizationServerSettings cannot be null");
this.authorizationServerSettings = authorizationServerSettings;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
AuthorizationServerContext authorizationServerContext =
new DefaultAuthorizationServerContext(
() -> resolveIssuer(this.authorizationServerSettings, request),
this.authorizationServerSettings);
AuthorizationServerContextHolder.setContext(authorizationServerContext);
filterChain.doFilter(request, response);
} finally {
AuthorizationServerContextHolder.resetContext();
}
}
private static String resolveIssuer(AuthorizationServerSettings authorizationServerSettings, HttpServletRequest request) {
return authorizationServerSettings.getIssuer() != null ?
authorizationServerSettings.getIssuer() :
getContextPath(request);
}
private static String getContextPath(HttpServletRequest request) {
// @formatter:off
return UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
.replacePath(request.getContextPath())
.replaceQuery(null)
.fragment(null)
.build()
.toUriString();
// @formatter:on
}
private static final class DefaultAuthorizationServerContext implements AuthorizationServerContext {
private final Supplier<String> issuerSupplier;
private final AuthorizationServerSettings authorizationServerSettings;
private DefaultAuthorizationServerContext(Supplier<String> issuerSupplier, AuthorizationServerSettings authorizationServerSettings) {
this.issuerSupplier = issuerSupplier;
this.authorizationServerSettings = authorizationServerSettings;
}
@Override
public String getIssuer() {
return this.issuerSupplier.get();
}
@Override
public AuthorizationServerSettings getAuthorizationServerSettings() {
return this.authorizationServerSettings;
}
}
}

View File

@@ -13,10 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import javax.servlet.http.HttpServletRequest;
@@ -24,14 +25,21 @@ import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationContext;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationValidator;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.authentication.DelegatingAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
@@ -52,11 +60,14 @@ import org.springframework.util.StringUtils;
*/
public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2Configurer {
private RequestMatcher requestMatcher;
private AuthenticationConverter authorizationRequestConverter;
private final List<AuthenticationConverter> authorizationRequestConverters = new ArrayList<>();
private Consumer<List<AuthenticationConverter>> authorizationRequestConvertersConsumer = (authorizationRequestConverters) -> {};
private final List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
private Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer = (authenticationProviders) -> {};
private AuthenticationSuccessHandler authorizationResponseHandler;
private AuthenticationFailureHandler errorResponseHandler;
private String consentPage;
private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationCodeRequestAuthenticationValidator;
/**
* Restrict for internal use only.
@@ -66,14 +77,32 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
}
/**
* Sets the {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
* to an instance of {@link OAuth2AuthorizationCodeRequestAuthenticationToken} used for authenticating the request.
* Adds an {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
* to an instance of {@link OAuth2AuthorizationCodeRequestAuthenticationToken} or {@link OAuth2AuthorizationConsentAuthenticationToken}
* used for authenticating the request.
*
* @param authorizationRequestConverter the {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
* @param authorizationRequestConverter an {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
* @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration
*/
public OAuth2AuthorizationEndpointConfigurer authorizationRequestConverter(AuthenticationConverter authorizationRequestConverter) {
this.authorizationRequestConverter = authorizationRequestConverter;
Assert.notNull(authorizationRequestConverter, "authorizationRequestConverter cannot be null");
this.authorizationRequestConverters.add(authorizationRequestConverter);
return this;
}
/**
* Sets the {@code Consumer} providing access to the {@code List} of default
* and (optionally) added {@link #authorizationRequestConverter(AuthenticationConverter) AuthenticationConverter}'s
* allowing the ability to add, remove, or customize a specific {@link AuthenticationConverter}.
*
* @param authorizationRequestConvertersConsumer the {@code Consumer} providing access to the {@code List} of default and (optionally) added {@link AuthenticationConverter}'s
* @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration
* @since 0.4.0
*/
public OAuth2AuthorizationEndpointConfigurer authorizationRequestConverters(
Consumer<List<AuthenticationConverter>> authorizationRequestConvertersConsumer) {
Assert.notNull(authorizationRequestConvertersConsumer, "authorizationRequestConvertersConsumer cannot be null");
this.authorizationRequestConvertersConsumer = authorizationRequestConvertersConsumer;
return this;
}
@@ -89,6 +118,22 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
return this;
}
/**
* Sets the {@code Consumer} providing access to the {@code List} of default
* and (optionally) added {@link #authenticationProvider(AuthenticationProvider) AuthenticationProvider}'s
* allowing the ability to add, remove, or customize a specific {@link AuthenticationProvider}.
*
* @param authenticationProvidersConsumer the {@code Consumer} providing access to the {@code List} of default and (optionally) added {@link AuthenticationProvider}'s
* @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration
* @since 0.4.0
*/
public OAuth2AuthorizationEndpointConfigurer authenticationProviders(
Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer) {
Assert.notNull(authenticationProvidersConsumer, "authenticationProvidersConsumer cannot be null");
this.authenticationProvidersConsumer = authenticationProvidersConsumer;
return this;
}
/**
* Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2AuthorizationCodeRequestAuthenticationToken}
* and returning the {@link OAuth2AuthorizationResponse Authorization Response}.
@@ -132,7 +177,7 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
*
* <ul>
* <li>It must be an HTTP POST</li>
* <li>It must be submitted to {@link ProviderSettings#getAuthorizationEndpoint()} ()}</li>
* <li>It must be submitted to {@link AuthorizationServerSettings#getAuthorizationEndpoint()}</li>
* <li>It must include the received {@code client_id} as an HTTP parameter</li>
* <li>It must include the received {@code state} as an HTTP parameter</li>
* <li>It must include the list of {@code scope}s the {@code Resource Owner}
@@ -147,37 +192,50 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
return this;
}
@Override
<B extends HttpSecurityBuilder<B>> void init(B builder) {
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
this.requestMatcher = new OrRequestMatcher(
new AntPathRequestMatcher(
providerSettings.getAuthorizationEndpoint(),
HttpMethod.GET.name()),
new AntPathRequestMatcher(
providerSettings.getAuthorizationEndpoint(),
HttpMethod.POST.name()));
List<AuthenticationProvider> authenticationProviders =
!this.authenticationProviders.isEmpty() ?
this.authenticationProviders :
createDefaultAuthenticationProviders(builder);
authenticationProviders.forEach(authenticationProvider ->
builder.authenticationProvider(postProcess(authenticationProvider)));
void addAuthorizationCodeRequestAuthenticationValidator(
Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator) {
this.authorizationCodeRequestAuthenticationValidator =
this.authorizationCodeRequestAuthenticationValidator == null ?
authenticationValidator :
this.authorizationCodeRequestAuthenticationValidator.andThen(authenticationValidator);
}
@Override
<B extends HttpSecurityBuilder<B>> void configure(B builder) {
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
void init(HttpSecurity httpSecurity) {
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
this.requestMatcher = new OrRequestMatcher(
new AntPathRequestMatcher(
authorizationServerSettings.getAuthorizationEndpoint(),
HttpMethod.GET.name()),
new AntPathRequestMatcher(
authorizationServerSettings.getAuthorizationEndpoint(),
HttpMethod.POST.name()));
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
if (!this.authenticationProviders.isEmpty()) {
authenticationProviders.addAll(0, this.authenticationProviders);
}
this.authenticationProvidersConsumer.accept(authenticationProviders);
authenticationProviders.forEach(authenticationProvider ->
httpSecurity.authenticationProvider(postProcess(authenticationProvider)));
}
@Override
void configure(HttpSecurity httpSecurity) {
AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class);
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
OAuth2AuthorizationEndpointFilter authorizationEndpointFilter =
new OAuth2AuthorizationEndpointFilter(
authenticationManager,
providerSettings.getAuthorizationEndpoint());
if (this.authorizationRequestConverter != null) {
authorizationEndpointFilter.setAuthenticationConverter(this.authorizationRequestConverter);
authorizationServerSettings.getAuthorizationEndpoint());
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
if (!this.authorizationRequestConverters.isEmpty()) {
authenticationConverters.addAll(0, this.authorizationRequestConverters);
}
this.authorizationRequestConvertersConsumer.accept(authenticationConverters);
authorizationEndpointFilter.setAuthenticationConverter(
new DelegatingAuthenticationConverter(authenticationConverters));
if (this.authorizationResponseHandler != null) {
authorizationEndpointFilter.setAuthenticationSuccessHandler(this.authorizationResponseHandler);
}
@@ -187,7 +245,7 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
if (StringUtils.hasText(this.consentPage)) {
authorizationEndpointFilter.setConsentPage(this.consentPage);
}
builder.addFilterBefore(postProcess(authorizationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
httpSecurity.addFilterBefore(postProcess(authorizationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
}
@Override
@@ -195,16 +253,37 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
return this.requestMatcher;
}
private <B extends HttpSecurityBuilder<B>> List<AuthenticationProvider> createDefaultAuthenticationProviders(B builder) {
private static List<AuthenticationConverter> createDefaultAuthenticationConverters() {
List<AuthenticationConverter> authenticationConverters = new ArrayList<>();
authenticationConverters.add(new OAuth2AuthorizationCodeRequestAuthenticationConverter());
authenticationConverters.add(new OAuth2AuthorizationConsentAuthenticationConverter());
return authenticationConverters;
}
private List<AuthenticationProvider> createDefaultAuthenticationProviders(HttpSecurity httpSecurity) {
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
OAuth2AuthorizationCodeRequestAuthenticationProvider authorizationCodeRequestAuthenticationProvider =
new OAuth2AuthorizationCodeRequestAuthenticationProvider(
OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
OAuth2ConfigurerUtils.getAuthorizationService(builder),
OAuth2ConfigurerUtils.getAuthorizationConsentService(builder));
OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity),
OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity),
OAuth2ConfigurerUtils.getAuthorizationConsentService(httpSecurity));
if (this.authorizationCodeRequestAuthenticationValidator != null) {
authorizationCodeRequestAuthenticationProvider.setAuthenticationValidator(
new OAuth2AuthorizationCodeRequestAuthenticationValidator()
.andThen(this.authorizationCodeRequestAuthenticationValidator));
}
authenticationProviders.add(authorizationCodeRequestAuthenticationProvider);
OAuth2AuthorizationConsentAuthenticationProvider authorizationConsentAuthenticationProvider =
new OAuth2AuthorizationConsentAuthenticationProvider(
OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity),
OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity),
OAuth2ConfigurerUtils.getAuthorizationConsentService(httpSecurity));
authenticationProviders.add(authorizationConsentAuthenticationProvider);
return authenticationProviders;
}

View File

@@ -13,10 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
import java.net.URI;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.nimbusds.jose.jwk.source.JWKSource;
@@ -24,21 +26,24 @@ import com.nimbusds.jose.jwk.source.JWKSource;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
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.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.ProviderContextFilter;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.context.SecurityContextHolderFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -55,6 +60,7 @@ import org.springframework.util.Assert;
* @since 0.0.1
* @see AbstractHttpConfigurer
* @see OAuth2ClientAuthenticationConfigurer
* @see OAuth2AuthorizationServerMetadataEndpointConfigurer
* @see OAuth2AuthorizationEndpointConfigurer
* @see OAuth2TokenEndpointConfigurer
* @see OAuth2TokenIntrospectionEndpointConfigurer
@@ -64,22 +70,13 @@ import org.springframework.util.Assert;
* @see OAuth2AuthorizationService
* @see OAuth2AuthorizationConsentService
* @see NimbusJwkSetEndpointFilter
* @see OAuth2AuthorizationServerMetadataEndpointFilter
*/
public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBuilder<B>>
extends AbstractHttpConfigurer<OAuth2AuthorizationServerConfigurer<B>, B> {
public final class OAuth2AuthorizationServerConfigurer
extends AbstractHttpConfigurer<OAuth2AuthorizationServerConfigurer, HttpSecurity> {
private final Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = createConfigurers();
private RequestMatcher jwkSetEndpointMatcher;
private RequestMatcher authorizationServerMetadataEndpointMatcher;
private final RequestMatcher endpointsMatcher = (request) ->
getRequestMatcher(OAuth2AuthorizationEndpointConfigurer.class).matches(request) ||
getRequestMatcher(OAuth2TokenEndpointConfigurer.class).matches(request) ||
getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class).matches(request) ||
getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class).matches(request) ||
getRequestMatcher(OidcConfigurer.class).matches(request) ||
this.jwkSetEndpointMatcher.matches(request) ||
this.authorizationServerMetadataEndpointMatcher.matches(request);
private RequestMatcher endpointsMatcher;
/**
* Sets the repository of registered clients.
@@ -87,7 +84,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
* @param registeredClientRepository the repository of registered clients
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
*/
public OAuth2AuthorizationServerConfigurer<B> registeredClientRepository(RegisteredClientRepository registeredClientRepository) {
public OAuth2AuthorizationServerConfigurer registeredClientRepository(RegisteredClientRepository registeredClientRepository) {
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
getBuilder().setSharedObject(RegisteredClientRepository.class, registeredClientRepository);
return this;
@@ -99,7 +96,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
* @param authorizationService the authorization service
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
*/
public OAuth2AuthorizationServerConfigurer<B> authorizationService(OAuth2AuthorizationService authorizationService) {
public OAuth2AuthorizationServerConfigurer authorizationService(OAuth2AuthorizationService authorizationService) {
Assert.notNull(authorizationService, "authorizationService cannot be null");
getBuilder().setSharedObject(OAuth2AuthorizationService.class, authorizationService);
return this;
@@ -111,21 +108,21 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
* @param authorizationConsentService the authorization consent service
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
*/
public OAuth2AuthorizationServerConfigurer<B> authorizationConsentService(OAuth2AuthorizationConsentService authorizationConsentService) {
public OAuth2AuthorizationServerConfigurer authorizationConsentService(OAuth2AuthorizationConsentService authorizationConsentService) {
Assert.notNull(authorizationConsentService, "authorizationConsentService cannot be null");
getBuilder().setSharedObject(OAuth2AuthorizationConsentService.class, authorizationConsentService);
return this;
}
/**
* Sets the provider settings.
* Sets the authorization server settings.
*
* @param providerSettings the provider settings
* @param authorizationServerSettings the authorization server settings
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
*/
public OAuth2AuthorizationServerConfigurer<B> providerSettings(ProviderSettings providerSettings) {
Assert.notNull(providerSettings, "providerSettings cannot be null");
getBuilder().setSharedObject(ProviderSettings.class, providerSettings);
public OAuth2AuthorizationServerConfigurer authorizationServerSettings(AuthorizationServerSettings authorizationServerSettings) {
Assert.notNull(authorizationServerSettings, "authorizationServerSettings cannot be null");
getBuilder().setSharedObject(AuthorizationServerSettings.class, authorizationServerSettings);
return this;
}
@@ -136,7 +133,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
* @since 0.2.3
*/
public OAuth2AuthorizationServerConfigurer<B> tokenGenerator(OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
public OAuth2AuthorizationServerConfigurer tokenGenerator(OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
getBuilder().setSharedObject(OAuth2TokenGenerator.class, tokenGenerator);
return this;
@@ -148,18 +145,30 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
* @param clientAuthenticationCustomizer the {@link Customizer} providing access to the {@link OAuth2ClientAuthenticationConfigurer}
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
*/
public OAuth2AuthorizationServerConfigurer<B> clientAuthentication(Customizer<OAuth2ClientAuthenticationConfigurer> clientAuthenticationCustomizer) {
public OAuth2AuthorizationServerConfigurer clientAuthentication(Customizer<OAuth2ClientAuthenticationConfigurer> clientAuthenticationCustomizer) {
clientAuthenticationCustomizer.customize(getConfigurer(OAuth2ClientAuthenticationConfigurer.class));
return this;
}
/**
* Configures the OAuth 2.0 Authorization Server Metadata Endpoint.
*
* @param authorizationServerMetadataEndpointCustomizer the {@link Customizer} providing access to the {@link OAuth2AuthorizationServerMetadataEndpointConfigurer}
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
* @since 0.4.0
*/
public OAuth2AuthorizationServerConfigurer authorizationServerMetadataEndpoint(Customizer<OAuth2AuthorizationServerMetadataEndpointConfigurer> authorizationServerMetadataEndpointCustomizer) {
authorizationServerMetadataEndpointCustomizer.customize(getConfigurer(OAuth2AuthorizationServerMetadataEndpointConfigurer.class));
return this;
}
/**
* Configures the OAuth 2.0 Authorization Endpoint.
*
* @param authorizationEndpointCustomizer the {@link Customizer} providing access to the {@link OAuth2AuthorizationEndpointConfigurer}
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
*/
public OAuth2AuthorizationServerConfigurer<B> authorizationEndpoint(Customizer<OAuth2AuthorizationEndpointConfigurer> authorizationEndpointCustomizer) {
public OAuth2AuthorizationServerConfigurer authorizationEndpoint(Customizer<OAuth2AuthorizationEndpointConfigurer> authorizationEndpointCustomizer) {
authorizationEndpointCustomizer.customize(getConfigurer(OAuth2AuthorizationEndpointConfigurer.class));
return this;
}
@@ -170,7 +179,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
* @param tokenEndpointCustomizer the {@link Customizer} providing access to the {@link OAuth2TokenEndpointConfigurer}
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
*/
public OAuth2AuthorizationServerConfigurer<B> tokenEndpoint(Customizer<OAuth2TokenEndpointConfigurer> tokenEndpointCustomizer) {
public OAuth2AuthorizationServerConfigurer tokenEndpoint(Customizer<OAuth2TokenEndpointConfigurer> tokenEndpointCustomizer) {
tokenEndpointCustomizer.customize(getConfigurer(OAuth2TokenEndpointConfigurer.class));
return this;
}
@@ -182,7 +191,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
* @since 0.2.3
*/
public OAuth2AuthorizationServerConfigurer<B> tokenIntrospectionEndpoint(Customizer<OAuth2TokenIntrospectionEndpointConfigurer> tokenIntrospectionEndpointCustomizer) {
public OAuth2AuthorizationServerConfigurer tokenIntrospectionEndpoint(Customizer<OAuth2TokenIntrospectionEndpointConfigurer> tokenIntrospectionEndpointCustomizer) {
tokenIntrospectionEndpointCustomizer.customize(getConfigurer(OAuth2TokenIntrospectionEndpointConfigurer.class));
return this;
}
@@ -194,19 +203,24 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
* @since 0.2.2
*/
public OAuth2AuthorizationServerConfigurer<B> tokenRevocationEndpoint(Customizer<OAuth2TokenRevocationEndpointConfigurer> tokenRevocationEndpointCustomizer) {
public OAuth2AuthorizationServerConfigurer tokenRevocationEndpoint(Customizer<OAuth2TokenRevocationEndpointConfigurer> tokenRevocationEndpointCustomizer) {
tokenRevocationEndpointCustomizer.customize(getConfigurer(OAuth2TokenRevocationEndpointConfigurer.class));
return this;
}
/**
* Configures OpenID Connect 1.0 support.
* Configures OpenID Connect 1.0 support (disabled by default).
*
* @param oidcCustomizer the {@link Customizer} providing access to the {@link OidcConfigurer}
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
*/
public OAuth2AuthorizationServerConfigurer<B> oidc(Customizer<OidcConfigurer> oidcCustomizer) {
oidcCustomizer.customize(getConfigurer(OidcConfigurer.class));
public OAuth2AuthorizationServerConfigurer oidc(Customizer<OidcConfigurer> oidcCustomizer) {
OidcConfigurer oidcConfigurer = getConfigurer(OidcConfigurer.class);
if (oidcConfigurer == null) {
addConfigurer(OidcConfigurer.class, new OidcConfigurer(this::postProcess));
oidcConfigurer = getConfigurer(OidcConfigurer.class);
}
oidcCustomizer.customize(oidcConfigurer);
return this;
}
@@ -216,18 +230,45 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
* @return a {@link RequestMatcher} for the authorization server endpoints
*/
public RequestMatcher getEndpointsMatcher() {
return this.endpointsMatcher;
// Return a deferred RequestMatcher
// since endpointsMatcher is constructed in init(HttpSecurity).
return (request) -> this.endpointsMatcher.matches(request);
}
@Override
public void init(B builder) {
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
validateProviderSettings(providerSettings);
initEndpointMatchers(providerSettings);
public void init(HttpSecurity httpSecurity) {
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
validateAuthorizationServerSettings(authorizationServerSettings);
this.configurers.values().forEach(configurer -> configurer.init(builder));
OidcConfigurer oidcConfigurer = getConfigurer(OidcConfigurer.class);
if (oidcConfigurer == null) {
// OpenID Connect is disabled.
// Add an authentication validator that rejects authentication requests.
OAuth2AuthorizationEndpointConfigurer authorizationEndpointConfigurer =
getConfigurer(OAuth2AuthorizationEndpointConfigurer.class);
authorizationEndpointConfigurer.addAuthorizationCodeRequestAuthenticationValidator((authenticationContext) -> {
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
authenticationContext.getAuthentication();
if (authorizationCodeRequestAuthentication.getScopes().contains(OidcScopes.OPENID)) {
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_SCOPE,
"OpenID Connect 1.0 authentication requests are restricted.",
"https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1");
throw new OAuth2AuthorizationCodeRequestAuthenticationException(
error, authorizationCodeRequestAuthentication);
}
});
}
ExceptionHandlingConfigurer<B> exceptionHandling = builder.getConfigurer(ExceptionHandlingConfigurer.class);
List<RequestMatcher> requestMatchers = new ArrayList<>();
this.configurers.values().forEach(configurer -> {
configurer.init(httpSecurity);
requestMatchers.add(configurer.getRequestMatcher());
});
requestMatchers.add(new AntPathRequestMatcher(
authorizationServerSettings.getJwkSetEndpoint(), HttpMethod.GET.name()));
this.endpointsMatcher = new OrRequestMatcher(requestMatchers);
ExceptionHandlingConfigurer<HttpSecurity> exceptionHandling = httpSecurity.getConfigurer(ExceptionHandlingConfigurer.class);
if (exceptionHandling != null) {
exceptionHandling.defaultAuthenticationEntryPointFor(
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
@@ -240,34 +281,30 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
}
@Override
public void configure(B builder) {
this.configurers.values().forEach(configurer -> configurer.configure(builder));
public void configure(HttpSecurity httpSecurity) {
this.configurers.values().forEach(configurer -> configurer.configure(httpSecurity));
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
ProviderContextFilter providerContextFilter = new ProviderContextFilter(providerSettings);
builder.addFilterAfter(postProcess(providerContextFilter), SecurityContextPersistenceFilter.class);
AuthorizationServerContextFilter authorizationServerContextFilter = new AuthorizationServerContextFilter(authorizationServerSettings);
httpSecurity.addFilterAfter(postProcess(authorizationServerContextFilter), SecurityContextHolderFilter.class);
JWKSource<com.nimbusds.jose.proc.SecurityContext> jwkSource = OAuth2ConfigurerUtils.getJwkSource(builder);
JWKSource<com.nimbusds.jose.proc.SecurityContext> jwkSource = OAuth2ConfigurerUtils.getJwkSource(httpSecurity);
if (jwkSource != null) {
NimbusJwkSetEndpointFilter jwkSetEndpointFilter = new NimbusJwkSetEndpointFilter(
jwkSource, providerSettings.getJwkSetEndpoint());
builder.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
jwkSource, authorizationServerSettings.getJwkSetEndpoint());
httpSecurity.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
}
OAuth2AuthorizationServerMetadataEndpointFilter authorizationServerMetadataEndpointFilter =
new OAuth2AuthorizationServerMetadataEndpointFilter(providerSettings);
builder.addFilterBefore(postProcess(authorizationServerMetadataEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
}
private Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> createConfigurers() {
Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = new LinkedHashMap<>();
configurers.put(OAuth2ClientAuthenticationConfigurer.class, new OAuth2ClientAuthenticationConfigurer(this::postProcess));
configurers.put(OAuth2AuthorizationServerMetadataEndpointConfigurer.class, new OAuth2AuthorizationServerMetadataEndpointConfigurer(this::postProcess));
configurers.put(OAuth2AuthorizationEndpointConfigurer.class, new OAuth2AuthorizationEndpointConfigurer(this::postProcess));
configurers.put(OAuth2TokenEndpointConfigurer.class, new OAuth2TokenEndpointConfigurer(this::postProcess));
configurers.put(OAuth2TokenIntrospectionEndpointConfigurer.class, new OAuth2TokenIntrospectionEndpointConfigurer(this::postProcess));
configurers.put(OAuth2TokenRevocationEndpointConfigurer.class, new OAuth2TokenRevocationEndpointConfigurer(this::postProcess));
configurers.put(OidcConfigurer.class, new OidcConfigurer(this::postProcess));
return configurers;
}
@@ -276,22 +313,20 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
return (T) this.configurers.get(type);
}
private <T extends AbstractOAuth2Configurer> void addConfigurer(Class<T> configurerType, T configurer) {
this.configurers.put(configurerType, configurer);
}
private <T extends AbstractOAuth2Configurer> RequestMatcher getRequestMatcher(Class<T> configurerType) {
return getConfigurer(configurerType).getRequestMatcher();
T configurer = getConfigurer(configurerType);
return configurer != null ? configurer.getRequestMatcher() : null;
}
private void initEndpointMatchers(ProviderSettings providerSettings) {
this.jwkSetEndpointMatcher = new AntPathRequestMatcher(
providerSettings.getJwkSetEndpoint(), HttpMethod.GET.name());
this.authorizationServerMetadataEndpointMatcher = new AntPathRequestMatcher(
"/.well-known/oauth-authorization-server", HttpMethod.GET.name());
}
private static void validateProviderSettings(ProviderSettings providerSettings) {
if (providerSettings.getIssuer() != null) {
private static void validateAuthorizationServerSettings(AuthorizationServerSettings authorizationServerSettings) {
if (authorizationServerSettings.getIssuer() != null) {
URI issuerUri;
try {
issuerUri = new URI(providerSettings.getIssuer());
issuerUri = new URI(authorizationServerSettings.getIssuer());
issuerUri.toURL();
} catch (Exception ex) {
throw new IllegalArgumentException("issuer must be a valid URL", ex);

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
import java.util.function.Consumer;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
/**
* Configurer for the OAuth 2.0 Authorization Server Metadata Endpoint.
*
* @author Joe Grandja
* @since 0.4.0
* @see OAuth2AuthorizationServerConfigurer#authorizationServerMetadataEndpoint
* @see OAuth2AuthorizationServerMetadataEndpointFilter
*/
public final class OAuth2AuthorizationServerMetadataEndpointConfigurer extends AbstractOAuth2Configurer {
private RequestMatcher requestMatcher;
private Consumer<OAuth2AuthorizationServerMetadata.Builder> authorizationServerMetadataCustomizer;
private Consumer<OAuth2AuthorizationServerMetadata.Builder> defaultAuthorizationServerMetadataCustomizer;
/**
* Restrict for internal use only.
*/
OAuth2AuthorizationServerMetadataEndpointConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor);
}
/**
* Sets the {@code Consumer} providing access to the {@link OAuth2AuthorizationServerMetadata.Builder}
* allowing the ability to customize the claims of the Authorization Server's configuration.
*
* @param authorizationServerMetadataCustomizer the {@code Consumer} providing access to the {@link OAuth2AuthorizationServerMetadata.Builder}
* @return the {@link OAuth2AuthorizationServerMetadataEndpointConfigurer} for further configuration
*/
public OAuth2AuthorizationServerMetadataEndpointConfigurer authorizationServerMetadataCustomizer(
Consumer<OAuth2AuthorizationServerMetadata.Builder> authorizationServerMetadataCustomizer) {
this.authorizationServerMetadataCustomizer = authorizationServerMetadataCustomizer;
return this;
}
void addDefaultAuthorizationServerMetadataCustomizer(
Consumer<OAuth2AuthorizationServerMetadata.Builder> defaultAuthorizationServerMetadataCustomizer) {
this.defaultAuthorizationServerMetadataCustomizer =
this.defaultAuthorizationServerMetadataCustomizer == null ?
defaultAuthorizationServerMetadataCustomizer :
this.defaultAuthorizationServerMetadataCustomizer.andThen(defaultAuthorizationServerMetadataCustomizer);
}
@Override
void init(HttpSecurity httpSecurity) {
this.requestMatcher = new AntPathRequestMatcher(
"/.well-known/oauth-authorization-server", HttpMethod.GET.name());
}
@Override
void configure(HttpSecurity httpSecurity) {
OAuth2AuthorizationServerMetadataEndpointFilter authorizationServerMetadataEndpointFilter =
new OAuth2AuthorizationServerMetadataEndpointFilter();
Consumer<OAuth2AuthorizationServerMetadata.Builder> authorizationServerMetadataCustomizer = getAuthorizationServerMetadataCustomizer();
if (authorizationServerMetadataCustomizer != null) {
authorizationServerMetadataEndpointFilter.setAuthorizationServerMetadataCustomizer(authorizationServerMetadataCustomizer);
}
httpSecurity.addFilterBefore(postProcess(authorizationServerMetadataEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
}
private Consumer<OAuth2AuthorizationServerMetadata.Builder> getAuthorizationServerMetadataCustomizer() {
Consumer<OAuth2AuthorizationServerMetadata.Builder> authorizationServerMetadataCustomizer = null;
if (this.defaultAuthorizationServerMetadataCustomizer != null || this.authorizationServerMetadataCustomizer != null) {
if (this.defaultAuthorizationServerMetadataCustomizer != null) {
authorizationServerMetadataCustomizer = this.defaultAuthorizationServerMetadataCustomizer;
}
if (this.authorizationServerMetadataCustomizer != null) {
authorizationServerMetadataCustomizer =
authorizationServerMetadataCustomizer == null ?
this.authorizationServerMetadataCustomizer :
authorizationServerMetadataCustomizer.andThen(this.authorizationServerMetadataCustomizer);
}
}
return authorizationServerMetadataCustomizer;
}
@Override
RequestMatcher getRequestMatcher() {
return this.requestMatcher;
}
}

View File

@@ -13,10 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import javax.servlet.http.HttpServletRequest;
@@ -24,7 +25,7 @@ import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.OAuth2Error;
@@ -34,8 +35,13 @@ import org.springframework.security.oauth2.server.authorization.authentication.J
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.PublicClientAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter;
import org.springframework.security.oauth2.server.authorization.web.authentication.ClientSecretBasicAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.ClientSecretPostAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.DelegatingAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.JwtClientAssertionAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.PublicClientAuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
@@ -55,8 +61,10 @@ import org.springframework.util.Assert;
*/
public final class OAuth2ClientAuthenticationConfigurer extends AbstractOAuth2Configurer {
private RequestMatcher requestMatcher;
private AuthenticationConverter authenticationConverter;
private final List<AuthenticationConverter> authenticationConverters = new ArrayList<>();
private Consumer<List<AuthenticationConverter>> authenticationConvertersConsumer = (authenticationConverters) -> {};
private final List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
private Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer = (authenticationProviders) -> {};
private AuthenticationSuccessHandler authenticationSuccessHandler;
private AuthenticationFailureHandler errorResponseHandler;
@@ -68,14 +76,31 @@ public final class OAuth2ClientAuthenticationConfigurer extends AbstractOAuth2Co
}
/**
* Sets the {@link AuthenticationConverter} used when attempting to extract client credentials from {@link HttpServletRequest}
* Adds an {@link AuthenticationConverter} used when attempting to extract client credentials from {@link HttpServletRequest}
* to an instance of {@link OAuth2ClientAuthenticationToken} used for authenticating the client.
*
* @param authenticationConverter the {@link AuthenticationConverter} used when attempting to extract client credentials from {@link HttpServletRequest}
* @param authenticationConverter an {@link AuthenticationConverter} used when attempting to extract client credentials from {@link HttpServletRequest}
* @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration
*/
public OAuth2ClientAuthenticationConfigurer authenticationConverter(AuthenticationConverter authenticationConverter) {
this.authenticationConverter = authenticationConverter;
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
this.authenticationConverters.add(authenticationConverter);
return this;
}
/**
* Sets the {@code Consumer} providing access to the {@code List} of default
* and (optionally) added {@link #authenticationConverter(AuthenticationConverter) AuthenticationConverter}'s
* allowing the ability to add, remove, or customize a specific {@link AuthenticationConverter}.
*
* @param authenticationConvertersConsumer the {@code Consumer} providing access to the {@code List} of default and (optionally) added {@link AuthenticationConverter}'s
* @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration
* @since 0.4.0
*/
public OAuth2ClientAuthenticationConfigurer authenticationConverters(
Consumer<List<AuthenticationConverter>> authenticationConvertersConsumer) {
Assert.notNull(authenticationConvertersConsumer, "authenticationConvertersConsumer cannot be null");
this.authenticationConvertersConsumer = authenticationConvertersConsumer;
return this;
}
@@ -91,6 +116,22 @@ public final class OAuth2ClientAuthenticationConfigurer extends AbstractOAuth2Co
return this;
}
/**
* Sets the {@code Consumer} providing access to the {@code List} of default
* and (optionally) added {@link #authenticationProvider(AuthenticationProvider) AuthenticationProvider}'s
* allowing the ability to add, remove, or customize a specific {@link AuthenticationProvider}.
*
* @param authenticationProvidersConsumer the {@code Consumer} providing access to the {@code List} of default and (optionally) added {@link AuthenticationProvider}'s
* @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration
* @since 0.4.0
*/
public OAuth2ClientAuthenticationConfigurer authenticationProviders(
Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer) {
Assert.notNull(authenticationProvidersConsumer, "authenticationProvidersConsumer cannot be null");
this.authenticationProvidersConsumer = authenticationProvidersConsumer;
return this;
}
/**
* Sets the {@link AuthenticationSuccessHandler} used for handling a successful client authentication
* and associating the {@link OAuth2ClientAuthenticationToken} to the {@link SecurityContext}.
@@ -116,42 +157,47 @@ public final class OAuth2ClientAuthenticationConfigurer extends AbstractOAuth2Co
}
@Override
<B extends HttpSecurityBuilder<B>> void init(B builder) {
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
void init(HttpSecurity httpSecurity) {
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
this.requestMatcher = new OrRequestMatcher(
new AntPathRequestMatcher(
providerSettings.getTokenEndpoint(),
authorizationServerSettings.getTokenEndpoint(),
HttpMethod.POST.name()),
new AntPathRequestMatcher(
providerSettings.getTokenIntrospectionEndpoint(),
authorizationServerSettings.getTokenIntrospectionEndpoint(),
HttpMethod.POST.name()),
new AntPathRequestMatcher(
providerSettings.getTokenRevocationEndpoint(),
authorizationServerSettings.getTokenRevocationEndpoint(),
HttpMethod.POST.name()));
List<AuthenticationProvider> authenticationProviders =
!this.authenticationProviders.isEmpty() ?
this.authenticationProviders :
createDefaultAuthenticationProviders(builder);
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
if (!this.authenticationProviders.isEmpty()) {
authenticationProviders.addAll(0, this.authenticationProviders);
}
this.authenticationProvidersConsumer.accept(authenticationProviders);
authenticationProviders.forEach(authenticationProvider ->
builder.authenticationProvider(postProcess(authenticationProvider)));
httpSecurity.authenticationProvider(postProcess(authenticationProvider)));
}
@Override
<B extends HttpSecurityBuilder<B>> void configure(B builder) {
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
void configure(HttpSecurity httpSecurity) {
AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class);
OAuth2ClientAuthenticationFilter clientAuthenticationFilter = new OAuth2ClientAuthenticationFilter(
authenticationManager, this.requestMatcher);
if (this.authenticationConverter != null) {
clientAuthenticationFilter.setAuthenticationConverter(this.authenticationConverter);
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
if (!this.authenticationConverters.isEmpty()) {
authenticationConverters.addAll(0, this.authenticationConverters);
}
this.authenticationConvertersConsumer.accept(authenticationConverters);
clientAuthenticationFilter.setAuthenticationConverter(
new DelegatingAuthenticationConverter(authenticationConverters));
if (this.authenticationSuccessHandler != null) {
clientAuthenticationFilter.setAuthenticationSuccessHandler(this.authenticationSuccessHandler);
}
if (this.errorResponseHandler != null) {
clientAuthenticationFilter.setAuthenticationFailureHandler(this.errorResponseHandler);
}
builder.addFilterAfter(postProcess(clientAuthenticationFilter), AbstractPreAuthenticatedProcessingFilter.class);
httpSecurity.addFilterAfter(postProcess(clientAuthenticationFilter), AbstractPreAuthenticatedProcessingFilter.class);
}
@Override
@@ -159,11 +205,22 @@ public final class OAuth2ClientAuthenticationConfigurer extends AbstractOAuth2Co
return this.requestMatcher;
}
private <B extends HttpSecurityBuilder<B>> List<AuthenticationProvider> createDefaultAuthenticationProviders(B builder) {
private static List<AuthenticationConverter> createDefaultAuthenticationConverters() {
List<AuthenticationConverter> authenticationConverters = new ArrayList<>();
authenticationConverters.add(new JwtClientAssertionAuthenticationConverter());
authenticationConverters.add(new ClientSecretBasicAuthenticationConverter());
authenticationConverters.add(new ClientSecretPostAuthenticationConverter());
authenticationConverters.add(new PublicClientAuthenticationConverter());
return authenticationConverters;
}
private static List<AuthenticationProvider> createDefaultAuthenticationProviders(HttpSecurity httpSecurity) {
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
RegisteredClientRepository registeredClientRepository = OAuth2ConfigurerUtils.getRegisteredClientRepository(builder);
OAuth2AuthorizationService authorizationService = OAuth2ConfigurerUtils.getAuthorizationService(builder);
RegisteredClientRepository registeredClientRepository = OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity);
OAuth2AuthorizationService authorizationService = OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity);
JwtClientAssertionAuthenticationProvider jwtClientAssertionAuthenticationProvider =
new JwtClientAssertionAuthenticationProvider(registeredClientRepository, authorizationService);
@@ -171,7 +228,7 @@ public final class OAuth2ClientAuthenticationConfigurer extends AbstractOAuth2Co
ClientSecretAuthenticationProvider clientSecretAuthenticationProvider =
new ClientSecretAuthenticationProvider(registeredClientRepository, authorizationService);
PasswordEncoder passwordEncoder = OAuth2ConfigurerUtils.getOptionalBean(builder, PasswordEncoder.class);
PasswordEncoder passwordEncoder = OAuth2ConfigurerUtils.getOptionalBean(httpSecurity, PasswordEncoder.class);
if (passwordEncoder != null) {
clientSecretAuthenticationProvider.setPasswordEncoder(passwordEncoder);
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
import java.util.Map;
@@ -25,7 +25,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
@@ -34,7 +34,7 @@ import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2Au
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
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.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.JwtGenerator;
@@ -56,48 +56,48 @@ final class OAuth2ConfigurerUtils {
private OAuth2ConfigurerUtils() {
}
static <B extends HttpSecurityBuilder<B>> RegisteredClientRepository getRegisteredClientRepository(B builder) {
RegisteredClientRepository registeredClientRepository = builder.getSharedObject(RegisteredClientRepository.class);
static RegisteredClientRepository getRegisteredClientRepository(HttpSecurity httpSecurity) {
RegisteredClientRepository registeredClientRepository = httpSecurity.getSharedObject(RegisteredClientRepository.class);
if (registeredClientRepository == null) {
registeredClientRepository = getBean(builder, RegisteredClientRepository.class);
builder.setSharedObject(RegisteredClientRepository.class, registeredClientRepository);
registeredClientRepository = getBean(httpSecurity, RegisteredClientRepository.class);
httpSecurity.setSharedObject(RegisteredClientRepository.class, registeredClientRepository);
}
return registeredClientRepository;
}
static <B extends HttpSecurityBuilder<B>> OAuth2AuthorizationService getAuthorizationService(B builder) {
OAuth2AuthorizationService authorizationService = builder.getSharedObject(OAuth2AuthorizationService.class);
static OAuth2AuthorizationService getAuthorizationService(HttpSecurity httpSecurity) {
OAuth2AuthorizationService authorizationService = httpSecurity.getSharedObject(OAuth2AuthorizationService.class);
if (authorizationService == null) {
authorizationService = getOptionalBean(builder, OAuth2AuthorizationService.class);
authorizationService = getOptionalBean(httpSecurity, OAuth2AuthorizationService.class);
if (authorizationService == null) {
authorizationService = new InMemoryOAuth2AuthorizationService();
}
builder.setSharedObject(OAuth2AuthorizationService.class, authorizationService);
httpSecurity.setSharedObject(OAuth2AuthorizationService.class, authorizationService);
}
return authorizationService;
}
static <B extends HttpSecurityBuilder<B>> OAuth2AuthorizationConsentService getAuthorizationConsentService(B builder) {
OAuth2AuthorizationConsentService authorizationConsentService = builder.getSharedObject(OAuth2AuthorizationConsentService.class);
static OAuth2AuthorizationConsentService getAuthorizationConsentService(HttpSecurity httpSecurity) {
OAuth2AuthorizationConsentService authorizationConsentService = httpSecurity.getSharedObject(OAuth2AuthorizationConsentService.class);
if (authorizationConsentService == null) {
authorizationConsentService = getOptionalBean(builder, OAuth2AuthorizationConsentService.class);
authorizationConsentService = getOptionalBean(httpSecurity, OAuth2AuthorizationConsentService.class);
if (authorizationConsentService == null) {
authorizationConsentService = new InMemoryOAuth2AuthorizationConsentService();
}
builder.setSharedObject(OAuth2AuthorizationConsentService.class, authorizationConsentService);
httpSecurity.setSharedObject(OAuth2AuthorizationConsentService.class, authorizationConsentService);
}
return authorizationConsentService;
}
@SuppressWarnings("unchecked")
static <B extends HttpSecurityBuilder<B>> OAuth2TokenGenerator<? extends OAuth2Token> getTokenGenerator(B builder) {
OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator = builder.getSharedObject(OAuth2TokenGenerator.class);
static OAuth2TokenGenerator<? extends OAuth2Token> getTokenGenerator(HttpSecurity httpSecurity) {
OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator = httpSecurity.getSharedObject(OAuth2TokenGenerator.class);
if (tokenGenerator == null) {
tokenGenerator = getOptionalBean(builder, OAuth2TokenGenerator.class);
tokenGenerator = getOptionalBean(httpSecurity, OAuth2TokenGenerator.class);
if (tokenGenerator == null) {
JwtGenerator jwtGenerator = getJwtGenerator(builder);
JwtGenerator jwtGenerator = getJwtGenerator(httpSecurity);
OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator();
OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer = getAccessTokenCustomizer(builder);
OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer = getAccessTokenCustomizer(httpSecurity);
if (accessTokenCustomizer != null) {
accessTokenGenerator.setAccessTokenCustomizer(accessTokenCustomizer);
}
@@ -110,83 +110,83 @@ final class OAuth2ConfigurerUtils {
accessTokenGenerator, refreshTokenGenerator);
}
}
builder.setSharedObject(OAuth2TokenGenerator.class, tokenGenerator);
httpSecurity.setSharedObject(OAuth2TokenGenerator.class, tokenGenerator);
}
return tokenGenerator;
}
private static <B extends HttpSecurityBuilder<B>> JwtGenerator getJwtGenerator(B builder) {
JwtGenerator jwtGenerator = builder.getSharedObject(JwtGenerator.class);
private static JwtGenerator getJwtGenerator(HttpSecurity httpSecurity) {
JwtGenerator jwtGenerator = httpSecurity.getSharedObject(JwtGenerator.class);
if (jwtGenerator == null) {
JwtEncoder jwtEncoder = getJwtEncoder(builder);
JwtEncoder jwtEncoder = getJwtEncoder(httpSecurity);
if (jwtEncoder != null) {
jwtGenerator = new JwtGenerator(jwtEncoder);
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = getJwtCustomizer(builder);
OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = getJwtCustomizer(httpSecurity);
if (jwtCustomizer != null) {
jwtGenerator.setJwtCustomizer(jwtCustomizer);
}
builder.setSharedObject(JwtGenerator.class, jwtGenerator);
httpSecurity.setSharedObject(JwtGenerator.class, jwtGenerator);
}
}
return jwtGenerator;
}
private static <B extends HttpSecurityBuilder<B>> JwtEncoder getJwtEncoder(B builder) {
JwtEncoder jwtEncoder = builder.getSharedObject(JwtEncoder.class);
private static JwtEncoder getJwtEncoder(HttpSecurity httpSecurity) {
JwtEncoder jwtEncoder = httpSecurity.getSharedObject(JwtEncoder.class);
if (jwtEncoder == null) {
jwtEncoder = getOptionalBean(builder, JwtEncoder.class);
jwtEncoder = getOptionalBean(httpSecurity, JwtEncoder.class);
if (jwtEncoder == null) {
JWKSource<SecurityContext> jwkSource = getJwkSource(builder);
JWKSource<SecurityContext> jwkSource = getJwkSource(httpSecurity);
if (jwkSource != null) {
jwtEncoder = new NimbusJwtEncoder(jwkSource);
}
}
if (jwtEncoder != null) {
builder.setSharedObject(JwtEncoder.class, jwtEncoder);
httpSecurity.setSharedObject(JwtEncoder.class, jwtEncoder);
}
}
return jwtEncoder;
}
@SuppressWarnings("unchecked")
static <B extends HttpSecurityBuilder<B>> JWKSource<SecurityContext> getJwkSource(B builder) {
JWKSource<SecurityContext> jwkSource = builder.getSharedObject(JWKSource.class);
static JWKSource<SecurityContext> getJwkSource(HttpSecurity httpSecurity) {
JWKSource<SecurityContext> jwkSource = httpSecurity.getSharedObject(JWKSource.class);
if (jwkSource == null) {
ResolvableType type = ResolvableType.forClassWithGenerics(JWKSource.class, SecurityContext.class);
jwkSource = getOptionalBean(builder, type);
jwkSource = getOptionalBean(httpSecurity, type);
if (jwkSource != null) {
builder.setSharedObject(JWKSource.class, jwkSource);
httpSecurity.setSharedObject(JWKSource.class, jwkSource);
}
}
return jwkSource;
}
private static <B extends HttpSecurityBuilder<B>> OAuth2TokenCustomizer<JwtEncodingContext> getJwtCustomizer(B builder) {
private static OAuth2TokenCustomizer<JwtEncodingContext> getJwtCustomizer(HttpSecurity httpSecurity) {
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, JwtEncodingContext.class);
return getOptionalBean(builder, type);
return getOptionalBean(httpSecurity, type);
}
private static <B extends HttpSecurityBuilder<B>> OAuth2TokenCustomizer<OAuth2TokenClaimsContext> getAccessTokenCustomizer(B builder) {
private static OAuth2TokenCustomizer<OAuth2TokenClaimsContext> getAccessTokenCustomizer(HttpSecurity httpSecurity) {
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, OAuth2TokenClaimsContext.class);
return getOptionalBean(builder, type);
return getOptionalBean(httpSecurity, type);
}
static <B extends HttpSecurityBuilder<B>> ProviderSettings getProviderSettings(B builder) {
ProviderSettings providerSettings = builder.getSharedObject(ProviderSettings.class);
if (providerSettings == null) {
providerSettings = getBean(builder, ProviderSettings.class);
builder.setSharedObject(ProviderSettings.class, providerSettings);
static AuthorizationServerSettings getAuthorizationServerSettings(HttpSecurity httpSecurity) {
AuthorizationServerSettings authorizationServerSettings = httpSecurity.getSharedObject(AuthorizationServerSettings.class);
if (authorizationServerSettings == null) {
authorizationServerSettings = getBean(httpSecurity, AuthorizationServerSettings.class);
httpSecurity.setSharedObject(AuthorizationServerSettings.class, authorizationServerSettings);
}
return providerSettings;
return authorizationServerSettings;
}
static <B extends HttpSecurityBuilder<B>, T> T getBean(B builder, Class<T> type) {
return builder.getSharedObject(ApplicationContext.class).getBean(type);
static <T> T getBean(HttpSecurity httpSecurity, Class<T> type) {
return httpSecurity.getSharedObject(ApplicationContext.class).getBean(type);
}
@SuppressWarnings("unchecked")
static <B extends HttpSecurityBuilder<B>, T> T getBean(B builder, ResolvableType type) {
ApplicationContext context = builder.getSharedObject(ApplicationContext.class);
static <T> T getBean(HttpSecurity httpSecurity, ResolvableType type) {
ApplicationContext context = httpSecurity.getSharedObject(ApplicationContext.class);
String[] names = context.getBeanNamesForType(type);
if (names.length == 1) {
return (T) context.getBean(names[0]);
@@ -197,9 +197,9 @@ final class OAuth2ConfigurerUtils {
throw new NoSuchBeanDefinitionException(type);
}
static <B extends HttpSecurityBuilder<B>, T> T getOptionalBean(B builder, Class<T> type) {
static <T> T getOptionalBean(HttpSecurity httpSecurity, Class<T> type) {
Map<String, T> beansMap = BeanFactoryUtils.beansOfTypeIncludingAncestors(
builder.getSharedObject(ApplicationContext.class), type);
httpSecurity.getSharedObject(ApplicationContext.class), type);
if (beansMap.size() > 1) {
throw new NoUniqueBeanDefinitionException(type, beansMap.size(),
"Expected single matching bean of type '" + type.getName() + "' but found " +
@@ -209,8 +209,8 @@ final class OAuth2ConfigurerUtils {
}
@SuppressWarnings("unchecked")
static <B extends HttpSecurityBuilder<B>, T> T getOptionalBean(B builder, ResolvableType type) {
ApplicationContext context = builder.getSharedObject(ApplicationContext.class);
static <T> T getOptionalBean(HttpSecurity httpSecurity, ResolvableType type) {
ApplicationContext context = httpSecurity.getSharedObject(ApplicationContext.class);
String[] names = context.getBeanNamesForType(type);
if (names.length > 1) {
throw new NoUniqueBeanDefinitionException(type, names);

View File

@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import javax.servlet.http.HttpServletRequest;
@@ -25,20 +25,24 @@ import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.authentication.DelegatingAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ClientCredentialsAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2RefreshTokenAuthenticationConverter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
@@ -57,8 +61,10 @@ import org.springframework.util.Assert;
*/
public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configurer {
private RequestMatcher requestMatcher;
private AuthenticationConverter accessTokenRequestConverter;
private final List<AuthenticationProvider> authenticationProviders = new LinkedList<>();
private final List<AuthenticationConverter> accessTokenRequestConverters = new ArrayList<>();
private Consumer<List<AuthenticationConverter>> accessTokenRequestConvertersConsumer = (accessTokenRequestConverters) -> {};
private final List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
private Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer = (authenticationProviders) -> {};
private AuthenticationSuccessHandler accessTokenResponseHandler;
private AuthenticationFailureHandler errorResponseHandler;
@@ -70,14 +76,31 @@ public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configure
}
/**
* Sets the {@link AuthenticationConverter} used when attempting to extract an Access Token Request from {@link HttpServletRequest}
* Adds an {@link AuthenticationConverter} used when attempting to extract an Access Token Request from {@link HttpServletRequest}
* to an instance of {@link OAuth2AuthorizationGrantAuthenticationToken} used for authenticating the authorization grant.
*
* @param accessTokenRequestConverter the {@link AuthenticationConverter} used when attempting to extract an Access Token Request from {@link HttpServletRequest}
* @param accessTokenRequestConverter an {@link AuthenticationConverter} used when attempting to extract an Access Token Request from {@link HttpServletRequest}
* @return the {@link OAuth2TokenEndpointConfigurer} for further configuration
*/
public OAuth2TokenEndpointConfigurer accessTokenRequestConverter(AuthenticationConverter accessTokenRequestConverter) {
this.accessTokenRequestConverter = accessTokenRequestConverter;
Assert.notNull(accessTokenRequestConverter, "accessTokenRequestConverter cannot be null");
this.accessTokenRequestConverters.add(accessTokenRequestConverter);
return this;
}
/**
* Sets the {@code Consumer} providing access to the {@code List} of default
* and (optionally) added {@link #accessTokenRequestConverter(AuthenticationConverter) AuthenticationConverter}'s
* allowing the ability to add, remove, or customize a specific {@link AuthenticationConverter}.
*
* @param accessTokenRequestConvertersConsumer the {@code Consumer} providing access to the {@code List} of default and (optionally) added {@link AuthenticationConverter}'s
* @return the {@link OAuth2TokenEndpointConfigurer} for further configuration
* @since 0.4.0
*/
public OAuth2TokenEndpointConfigurer accessTokenRequestConverters(
Consumer<List<AuthenticationConverter>> accessTokenRequestConvertersConsumer) {
Assert.notNull(accessTokenRequestConvertersConsumer, "accessTokenRequestConvertersConsumer cannot be null");
this.accessTokenRequestConvertersConsumer = accessTokenRequestConvertersConsumer;
return this;
}
@@ -93,6 +116,22 @@ public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configure
return this;
}
/**
* Sets the {@code Consumer} providing access to the {@code List} of default
* and (optionally) added {@link #authenticationProvider(AuthenticationProvider) AuthenticationProvider}'s
* allowing the ability to add, remove, or customize a specific {@link AuthenticationProvider}.
*
* @param authenticationProvidersConsumer the {@code Consumer} providing access to the {@code List} of default and (optionally) added {@link AuthenticationProvider}'s
* @return the {@link OAuth2TokenEndpointConfigurer} for further configuration
* @since 0.4.0
*/
public OAuth2TokenEndpointConfigurer authenticationProviders(
Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer) {
Assert.notNull(authenticationProvidersConsumer, "authenticationProvidersConsumer cannot be null");
this.authenticationProvidersConsumer = authenticationProvidersConsumer;
return this;
}
/**
* Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2AccessTokenAuthenticationToken}
* and returning the {@link OAuth2AccessTokenResponse Access Token Response}.
@@ -118,38 +157,43 @@ public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configure
}
@Override
<B extends HttpSecurityBuilder<B>> void init(B builder) {
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
void init(HttpSecurity httpSecurity) {
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
this.requestMatcher = new AntPathRequestMatcher(
providerSettings.getTokenEndpoint(), HttpMethod.POST.name());
authorizationServerSettings.getTokenEndpoint(), HttpMethod.POST.name());
List<AuthenticationProvider> authenticationProviders =
!this.authenticationProviders.isEmpty() ?
this.authenticationProviders :
createDefaultAuthenticationProviders(builder);
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
if (!this.authenticationProviders.isEmpty()) {
authenticationProviders.addAll(0, this.authenticationProviders);
}
this.authenticationProvidersConsumer.accept(authenticationProviders);
authenticationProviders.forEach(authenticationProvider ->
builder.authenticationProvider(postProcess(authenticationProvider)));
httpSecurity.authenticationProvider(postProcess(authenticationProvider)));
}
@Override
<B extends HttpSecurityBuilder<B>> void configure(B builder) {
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
void configure(HttpSecurity httpSecurity) {
AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class);
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
OAuth2TokenEndpointFilter tokenEndpointFilter =
new OAuth2TokenEndpointFilter(
authenticationManager,
providerSettings.getTokenEndpoint());
if (this.accessTokenRequestConverter != null) {
tokenEndpointFilter.setAuthenticationConverter(this.accessTokenRequestConverter);
authorizationServerSettings.getTokenEndpoint());
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
if (!this.accessTokenRequestConverters.isEmpty()) {
authenticationConverters.addAll(0, this.accessTokenRequestConverters);
}
this.accessTokenRequestConvertersConsumer.accept(authenticationConverters);
tokenEndpointFilter.setAuthenticationConverter(
new DelegatingAuthenticationConverter(authenticationConverters));
if (this.accessTokenResponseHandler != null) {
tokenEndpointFilter.setAuthenticationSuccessHandler(this.accessTokenResponseHandler);
}
if (this.errorResponseHandler != null) {
tokenEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler);
}
builder.addFilterAfter(postProcess(tokenEndpointFilter), FilterSecurityInterceptor.class);
httpSecurity.addFilterAfter(postProcess(tokenEndpointFilter), FilterSecurityInterceptor.class);
}
@Override
@@ -157,11 +201,21 @@ public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configure
return this.requestMatcher;
}
private <B extends HttpSecurityBuilder<B>> List<AuthenticationProvider> createDefaultAuthenticationProviders(B builder) {
private static List<AuthenticationConverter> createDefaultAuthenticationConverters() {
List<AuthenticationConverter> authenticationConverters = new ArrayList<>();
authenticationConverters.add(new OAuth2AuthorizationCodeAuthenticationConverter());
authenticationConverters.add(new OAuth2RefreshTokenAuthenticationConverter());
authenticationConverters.add(new OAuth2ClientCredentialsAuthenticationConverter());
return authenticationConverters;
}
private static List<AuthenticationProvider> createDefaultAuthenticationProviders(HttpSecurity httpSecurity) {
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
OAuth2AuthorizationService authorizationService = OAuth2ConfigurerUtils.getAuthorizationService(builder);
OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator = OAuth2ConfigurerUtils.getTokenGenerator(builder);
OAuth2AuthorizationService authorizationService = OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity);
OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator = OAuth2ConfigurerUtils.getTokenGenerator(httpSecurity);
OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider =
new OAuth2AuthorizationCodeAuthenticationProvider(authorizationService, tokenGenerator);

View File

@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import javax.servlet.http.HttpServletRequest;
@@ -26,13 +26,15 @@ import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.authentication.DelegatingAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2TokenIntrospectionAuthenticationConverter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
@@ -45,14 +47,17 @@ import org.springframework.util.Assert;
* Configurer for the OAuth 2.0 Token Introspection Endpoint.
*
* @author Gaurav Tiwari
* @author Joe Grandja
* @since 0.2.3
* @see OAuth2AuthorizationServerConfigurer#tokenIntrospectionEndpoint(Customizer)
* @see OAuth2TokenIntrospectionEndpointFilter
*/
public final class OAuth2TokenIntrospectionEndpointConfigurer extends AbstractOAuth2Configurer {
private RequestMatcher requestMatcher;
private AuthenticationConverter introspectionRequestConverter;
private final List<AuthenticationProvider> authenticationProviders = new LinkedList<>();
private final List<AuthenticationConverter> introspectionRequestConverters = new ArrayList<>();
private Consumer<List<AuthenticationConverter>> introspectionRequestConvertersConsumer = (introspectionRequestConverters) -> {};
private final List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
private Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer = (authenticationProviders) -> {};
private AuthenticationSuccessHandler introspectionResponseHandler;
private AuthenticationFailureHandler errorResponseHandler;
@@ -64,14 +69,31 @@ public final class OAuth2TokenIntrospectionEndpointConfigurer extends AbstractOA
}
/**
* Sets the {@link AuthenticationConverter} used when attempting to extract an Introspection Request from {@link HttpServletRequest}
* Adds an {@link AuthenticationConverter} used when attempting to extract an Introspection Request from {@link HttpServletRequest}
* to an instance of {@link OAuth2TokenIntrospectionAuthenticationToken} used for authenticating the request.
*
* @param introspectionRequestConverter the {@link AuthenticationConverter} used when attempting to extract an Introspection Request from {@link HttpServletRequest}
* @param introspectionRequestConverter an {@link AuthenticationConverter} used when attempting to extract an Introspection Request from {@link HttpServletRequest}
* @return the {@link OAuth2TokenIntrospectionEndpointConfigurer} for further configuration
*/
public OAuth2TokenIntrospectionEndpointConfigurer introspectionRequestConverter(AuthenticationConverter introspectionRequestConverter) {
this.introspectionRequestConverter = introspectionRequestConverter;
Assert.notNull(introspectionRequestConverter, "introspectionRequestConverter cannot be null");
this.introspectionRequestConverters.add(introspectionRequestConverter);
return this;
}
/**
* Sets the {@code Consumer} providing access to the {@code List} of default
* and (optionally) added {@link #introspectionRequestConverter(AuthenticationConverter) AuthenticationConverter}'s
* allowing the ability to add, remove, or customize a specific {@link AuthenticationConverter}.
*
* @param introspectionRequestConvertersConsumer the {@code Consumer} providing access to the {@code List} of default and (optionally) added {@link AuthenticationConverter}'s
* @return the {@link OAuth2TokenIntrospectionEndpointConfigurer} for further configuration
* @since 0.4.0
*/
public OAuth2TokenIntrospectionEndpointConfigurer introspectionRequestConverters(
Consumer<List<AuthenticationConverter>> introspectionRequestConvertersConsumer) {
Assert.notNull(introspectionRequestConvertersConsumer, "introspectionRequestConvertersConsumer cannot be null");
this.introspectionRequestConvertersConsumer = introspectionRequestConvertersConsumer;
return this;
}
@@ -87,6 +109,22 @@ public final class OAuth2TokenIntrospectionEndpointConfigurer extends AbstractOA
return this;
}
/**
* Sets the {@code Consumer} providing access to the {@code List} of default
* and (optionally) added {@link #authenticationProvider(AuthenticationProvider) AuthenticationProvider}'s
* allowing the ability to add, remove, or customize a specific {@link AuthenticationProvider}.
*
* @param authenticationProvidersConsumer the {@code Consumer} providing access to the {@code List} of default and (optionally) added {@link AuthenticationProvider}'s
* @return the {@link OAuth2TokenIntrospectionEndpointConfigurer} for further configuration
* @since 0.4.0
*/
public OAuth2TokenIntrospectionEndpointConfigurer authenticationProviders(
Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer) {
Assert.notNull(authenticationProvidersConsumer, "authenticationProvidersConsumer cannot be null");
this.authenticationProvidersConsumer = authenticationProvidersConsumer;
return this;
}
/**
* Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2TokenIntrospectionAuthenticationToken}.
*
@@ -111,51 +149,64 @@ public final class OAuth2TokenIntrospectionEndpointConfigurer extends AbstractOA
}
@Override
<B extends HttpSecurityBuilder<B>> void init(B builder) {
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
void init(HttpSecurity httpSecurity) {
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
this.requestMatcher = new AntPathRequestMatcher(
providerSettings.getTokenIntrospectionEndpoint(), HttpMethod.POST.name());
authorizationServerSettings.getTokenIntrospectionEndpoint(), HttpMethod.POST.name());
List<AuthenticationProvider> authenticationProviders =
!this.authenticationProviders.isEmpty() ?
this.authenticationProviders :
createDefaultAuthenticationProviders(builder);
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
if (!this.authenticationProviders.isEmpty()) {
authenticationProviders.addAll(0, this.authenticationProviders);
}
this.authenticationProvidersConsumer.accept(authenticationProviders);
authenticationProviders.forEach(authenticationProvider ->
builder.authenticationProvider(postProcess(authenticationProvider)));
httpSecurity.authenticationProvider(postProcess(authenticationProvider)));
}
@Override
<B extends HttpSecurityBuilder<B>> void configure(B builder) {
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
void configure(HttpSecurity httpSecurity) {
AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class);
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
OAuth2TokenIntrospectionEndpointFilter introspectionEndpointFilter =
new OAuth2TokenIntrospectionEndpointFilter(
authenticationManager, providerSettings.getTokenIntrospectionEndpoint());
if (this.introspectionRequestConverter != null) {
introspectionEndpointFilter.setAuthenticationConverter(this.introspectionRequestConverter);
authenticationManager, authorizationServerSettings.getTokenIntrospectionEndpoint());
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
if (!this.introspectionRequestConverters.isEmpty()) {
authenticationConverters.addAll(0, this.introspectionRequestConverters);
}
this.introspectionRequestConvertersConsumer.accept(authenticationConverters);
introspectionEndpointFilter.setAuthenticationConverter(
new DelegatingAuthenticationConverter(authenticationConverters));
if (this.introspectionResponseHandler != null) {
introspectionEndpointFilter.setAuthenticationSuccessHandler(this.introspectionResponseHandler);
}
if (this.errorResponseHandler != null) {
introspectionEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler);
}
builder.addFilterAfter(postProcess(introspectionEndpointFilter), FilterSecurityInterceptor.class);
httpSecurity.addFilterAfter(postProcess(introspectionEndpointFilter), FilterSecurityInterceptor.class);
}
@Override
public RequestMatcher getRequestMatcher() {
RequestMatcher getRequestMatcher() {
return this.requestMatcher;
}
private <B extends HttpSecurityBuilder<B>> List<AuthenticationProvider> createDefaultAuthenticationProviders(B builder) {
private static List<AuthenticationConverter> createDefaultAuthenticationConverters() {
List<AuthenticationConverter> authenticationConverters = new ArrayList<>();
authenticationConverters.add(new OAuth2TokenIntrospectionAuthenticationConverter());
return authenticationConverters;
}
private static List<AuthenticationProvider> createDefaultAuthenticationProviders(HttpSecurity httpSecurity) {
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
OAuth2TokenIntrospectionAuthenticationProvider tokenIntrospectionAuthenticationProvider =
new OAuth2TokenIntrospectionAuthenticationProvider(
OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
OAuth2ConfigurerUtils.getAuthorizationService(builder));
OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity),
OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity));
authenticationProviders.add(tokenIntrospectionAuthenticationProvider);
return authenticationProviders;

View File

@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import javax.servlet.http.HttpServletRequest;
@@ -25,13 +25,15 @@ import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.authentication.DelegatingAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2TokenRevocationAuthenticationConverter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
@@ -44,14 +46,17 @@ import org.springframework.util.Assert;
* Configurer for the OAuth 2.0 Token Revocation Endpoint.
*
* @author Arfat Chaus
* @author Joe Grandja
* @since 0.2.2
* @see OAuth2AuthorizationServerConfigurer#tokenRevocationEndpoint
* @see OAuth2TokenRevocationEndpointFilter
*/
public final class OAuth2TokenRevocationEndpointConfigurer extends AbstractOAuth2Configurer {
private RequestMatcher requestMatcher;
private AuthenticationConverter revocationRequestConverter;
private final List<AuthenticationProvider> authenticationProviders = new LinkedList<>();
private final List<AuthenticationConverter> revocationRequestConverters = new ArrayList<>();
private Consumer<List<AuthenticationConverter>> revocationRequestConvertersConsumer = (revocationRequestConverters) -> {};
private final List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
private Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer = (authenticationProviders) -> {};
private AuthenticationSuccessHandler revocationResponseHandler;
private AuthenticationFailureHandler errorResponseHandler;
@@ -63,14 +68,31 @@ public final class OAuth2TokenRevocationEndpointConfigurer extends AbstractOAuth
}
/**
* Sets the {@link AuthenticationConverter} used when attempting to extract a Revoke Token Request from {@link HttpServletRequest}
* to an instance of {@link OAuth2TokenRevocationAuthenticationToken} used for authenticating the client.
* Adds an {@link AuthenticationConverter} used when attempting to extract a Revoke Token Request from {@link HttpServletRequest}
* to an instance of {@link OAuth2TokenRevocationAuthenticationToken} used for authenticating the request.
*
* @param revocationRequestConverter the {@link AuthenticationConverter} used when attempting to extract client credentials from {@link HttpServletRequest}
* @param revocationRequestConverter an {@link AuthenticationConverter} used when attempting to extract a Revoke Token Request from {@link HttpServletRequest}
* @return the {@link OAuth2TokenRevocationEndpointConfigurer} for further configuration
*/
public OAuth2TokenRevocationEndpointConfigurer revocationRequestConverter(AuthenticationConverter revocationRequestConverter) {
this.revocationRequestConverter = revocationRequestConverter;
Assert.notNull(revocationRequestConverter, "revocationRequestConverter cannot be null");
this.revocationRequestConverters.add(revocationRequestConverter);
return this;
}
/**
* Sets the {@code Consumer} providing access to the {@code List} of default
* and (optionally) added {@link #revocationRequestConverter(AuthenticationConverter) AuthenticationConverter}'s
* allowing the ability to add, remove, or customize a specific {@link AuthenticationConverter}.
*
* @param revocationRequestConvertersConsumer the {@code Consumer} providing access to the {@code List} of default and (optionally) added {@link AuthenticationConverter}'s
* @return the {@link OAuth2TokenRevocationEndpointConfigurer} for further configuration
* @since 0.4.0
*/
public OAuth2TokenRevocationEndpointConfigurer revocationRequestConverters(
Consumer<List<AuthenticationConverter>> revocationRequestConvertersConsumer) {
Assert.notNull(revocationRequestConvertersConsumer, "revocationRequestConvertersConsumer cannot be null");
this.revocationRequestConvertersConsumer = revocationRequestConvertersConsumer;
return this;
}
@@ -86,6 +108,22 @@ public final class OAuth2TokenRevocationEndpointConfigurer extends AbstractOAuth
return this;
}
/**
* Sets the {@code Consumer} providing access to the {@code List} of default
* and (optionally) added {@link #authenticationProvider(AuthenticationProvider) AuthenticationProvider}'s
* allowing the ability to add, remove, or customize a specific {@link AuthenticationProvider}.
*
* @param authenticationProvidersConsumer the {@code Consumer} providing access to the {@code List} of default and (optionally) added {@link AuthenticationProvider}'s
* @return the {@link OAuth2TokenRevocationEndpointConfigurer} for further configuration
* @since 0.4.0
*/
public OAuth2TokenRevocationEndpointConfigurer authenticationProviders(
Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer) {
Assert.notNull(authenticationProvidersConsumer, "authenticationProvidersConsumer cannot be null");
this.authenticationProvidersConsumer = authenticationProvidersConsumer;
return this;
}
/**
* Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2TokenRevocationAuthenticationToken}.
*
@@ -110,37 +148,42 @@ public final class OAuth2TokenRevocationEndpointConfigurer extends AbstractOAuth
}
@Override
<B extends HttpSecurityBuilder<B>> void init(B builder) {
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
void init(HttpSecurity httpSecurity) {
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
this.requestMatcher = new AntPathRequestMatcher(
providerSettings.getTokenRevocationEndpoint(), HttpMethod.POST.name());
authorizationServerSettings.getTokenRevocationEndpoint(), HttpMethod.POST.name());
List<AuthenticationProvider> authenticationProviders =
!this.authenticationProviders.isEmpty() ?
this.authenticationProviders :
createDefaultAuthenticationProviders(builder);
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
if (!this.authenticationProviders.isEmpty()) {
authenticationProviders.addAll(0, this.authenticationProviders);
}
this.authenticationProvidersConsumer.accept(authenticationProviders);
authenticationProviders.forEach(authenticationProvider ->
builder.authenticationProvider(postProcess(authenticationProvider)));
httpSecurity.authenticationProvider(postProcess(authenticationProvider)));
}
@Override
<B extends HttpSecurityBuilder<B>> void configure(B builder) {
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
void configure(HttpSecurity httpSecurity) {
AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class);
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
OAuth2TokenRevocationEndpointFilter revocationEndpointFilter =
new OAuth2TokenRevocationEndpointFilter(
authenticationManager, providerSettings.getTokenRevocationEndpoint());
if (this.revocationRequestConverter != null) {
revocationEndpointFilter.setAuthenticationConverter(this.revocationRequestConverter);
authenticationManager, authorizationServerSettings.getTokenRevocationEndpoint());
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
if (!this.revocationRequestConverters.isEmpty()) {
authenticationConverters.addAll(0, this.revocationRequestConverters);
}
this.revocationRequestConvertersConsumer.accept(authenticationConverters);
revocationEndpointFilter.setAuthenticationConverter(
new DelegatingAuthenticationConverter(authenticationConverters));
if (this.revocationResponseHandler != null) {
revocationEndpointFilter.setAuthenticationSuccessHandler(this.revocationResponseHandler);
}
if (this.errorResponseHandler != null) {
revocationEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler);
}
builder.addFilterAfter(postProcess(revocationEndpointFilter), FilterSecurityInterceptor.class);
httpSecurity.addFilterAfter(postProcess(revocationEndpointFilter), FilterSecurityInterceptor.class);
}
@Override
@@ -148,11 +191,19 @@ public final class OAuth2TokenRevocationEndpointConfigurer extends AbstractOAuth
return this.requestMatcher;
}
private <B extends HttpSecurityBuilder<B>> List<AuthenticationProvider> createDefaultAuthenticationProviders(B builder) {
private static List<AuthenticationConverter> createDefaultAuthenticationConverters() {
List<AuthenticationConverter> authenticationConverters = new ArrayList<>();
authenticationConverters.add(new OAuth2TokenRevocationAuthenticationConverter());
return authenticationConverters;
}
private static List<AuthenticationProvider> createDefaultAuthenticationProviders(HttpSecurity httpSecurity) {
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
OAuth2TokenRevocationAuthenticationProvider tokenRevocationAuthenticationProvider =
new OAuth2TokenRevocationAuthenticationProvider(OAuth2ConfigurerUtils.getAuthorizationService(builder));
new OAuth2TokenRevocationAuthenticationProvider(OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity));
authenticationProviders.add(tokenRevocationAuthenticationProvider);
return authenticationProviders;

View File

@@ -0,0 +1,235 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientConfigurationAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.oidc.web.authentication.OidcClientRegistrationAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.web.authentication.DelegatingAuthenticationConverter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
/**
* Configurer for OpenID Connect 1.0 Dynamic Client Registration Endpoint.
*
* @author Joe Grandja
* @author Daniel Garnier-Moiroux
* @since 0.2.0
* @see OidcConfigurer#clientRegistrationEndpoint
* @see OidcClientRegistrationEndpointFilter
*/
public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAuth2Configurer {
private RequestMatcher requestMatcher;
private final List<AuthenticationConverter> clientRegistrationRequestConverters = new ArrayList<>();
private Consumer<List<AuthenticationConverter>> clientRegistrationRequestConvertersConsumer = (clientRegistrationRequestConverters) -> {};
private final List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
private Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer = (authenticationProviders) -> {};
private AuthenticationSuccessHandler clientRegistrationResponseHandler;
private AuthenticationFailureHandler errorResponseHandler;
/**
* Restrict for internal use only.
*/
OidcClientRegistrationEndpointConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor);
}
/**
* Adds an {@link AuthenticationConverter} used when attempting to extract a Client Registration Request from {@link HttpServletRequest}
* to an instance of {@link OidcClientRegistrationAuthenticationToken} used for authenticating the request.
*
* @param clientRegistrationRequestConverter an {@link AuthenticationConverter} used when attempting to extract a Client Registration Request from {@link HttpServletRequest}
* @return the {@link OidcClientRegistrationEndpointConfigurer} for further configuration
* @since 0.4.0
*/
public OidcClientRegistrationEndpointConfigurer clientRegistrationRequestConverter(
AuthenticationConverter clientRegistrationRequestConverter) {
Assert.notNull(clientRegistrationRequestConverter, "clientRegistrationRequestConverter cannot be null");
this.clientRegistrationRequestConverters.add(clientRegistrationRequestConverter);
return this;
}
/**
* Sets the {@code Consumer} providing access to the {@code List} of default
* and (optionally) added {@link #clientRegistrationRequestConverter(AuthenticationConverter) AuthenticationConverter}'s
* allowing the ability to add, remove, or customize a specific {@link AuthenticationConverter}.
*
* @param clientRegistrationRequestConvertersConsumer the {@code Consumer} providing access to the {@code List} of default and (optionally) added {@link AuthenticationConverter}'s
* @return the {@link OidcUserInfoEndpointConfigurer} for further configuration
* @since 0.4.0
*/
public OidcClientRegistrationEndpointConfigurer clientRegistrationRequestConverters(
Consumer<List<AuthenticationConverter>> clientRegistrationRequestConvertersConsumer) {
Assert.notNull(clientRegistrationRequestConvertersConsumer, "clientRegistrationRequestConvertersConsumer cannot be null");
this.clientRegistrationRequestConvertersConsumer = clientRegistrationRequestConvertersConsumer;
return this;
}
/**
* Adds an {@link AuthenticationProvider} used for authenticating an {@link OidcClientRegistrationAuthenticationToken}.
*
* @param authenticationProvider an {@link AuthenticationProvider} used for authenticating an {@link OidcClientRegistrationAuthenticationToken}
* @return the {@link OidcClientRegistrationEndpointConfigurer} for further configuration
* @since 0.4.0
*/
public OidcClientRegistrationEndpointConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) {
Assert.notNull(authenticationProvider, "authenticationProvider cannot be null");
this.authenticationProviders.add(authenticationProvider);
return this;
}
/**
* Sets the {@code Consumer} providing access to the {@code List} of default
* and (optionally) added {@link #authenticationProvider(AuthenticationProvider) AuthenticationProvider}'s
* allowing the ability to add, remove, or customize a specific {@link AuthenticationProvider}.
*
* @param authenticationProvidersConsumer the {@code Consumer} providing access to the {@code List} of default and (optionally) added {@link AuthenticationProvider}'s
* @return the {@link OidcClientRegistrationEndpointConfigurer} for further configuration
* @since 0.4.0
*/
public OidcClientRegistrationEndpointConfigurer authenticationProviders(
Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer) {
Assert.notNull(authenticationProvidersConsumer, "authenticationProvidersConsumer cannot be null");
this.authenticationProvidersConsumer = authenticationProvidersConsumer;
return this;
}
/**
* Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OidcClientRegistrationAuthenticationToken}
* and returning the {@link OidcClientRegistration Client Registration Response}.
*
* @param clientRegistrationResponseHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OidcClientRegistrationAuthenticationToken}
* @return the {@link OidcClientRegistrationEndpointConfigurer} for further configuration
* @since 0.4.0
*/
public OidcClientRegistrationEndpointConfigurer clientRegistrationResponseHandler(AuthenticationSuccessHandler clientRegistrationResponseHandler) {
this.clientRegistrationResponseHandler = clientRegistrationResponseHandler;
return this;
}
/**
* Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
* and returning the {@link OAuth2Error Error Response}.
*
* @param errorResponseHandler the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
* @return the {@link OidcClientRegistrationEndpointConfigurer} for further configuration
* @since 0.4.0
*/
public OidcClientRegistrationEndpointConfigurer errorResponseHandler(AuthenticationFailureHandler errorResponseHandler) {
this.errorResponseHandler = errorResponseHandler;
return this;
}
@Override
void init(HttpSecurity httpSecurity) {
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
String clientRegistrationEndpointUri = authorizationServerSettings.getOidcClientRegistrationEndpoint();
this.requestMatcher = new OrRequestMatcher(
new AntPathRequestMatcher(clientRegistrationEndpointUri, HttpMethod.POST.name()),
new AntPathRequestMatcher(clientRegistrationEndpointUri, HttpMethod.GET.name())
);
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
if (!this.authenticationProviders.isEmpty()) {
authenticationProviders.addAll(0, this.authenticationProviders);
}
this.authenticationProvidersConsumer.accept(authenticationProviders);
authenticationProviders.forEach(authenticationProvider ->
httpSecurity.authenticationProvider(postProcess(authenticationProvider)));
}
@Override
void configure(HttpSecurity httpSecurity) {
AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class);
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
OidcClientRegistrationEndpointFilter oidcClientRegistrationEndpointFilter =
new OidcClientRegistrationEndpointFilter(
authenticationManager,
authorizationServerSettings.getOidcClientRegistrationEndpoint());
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
if (!this.clientRegistrationRequestConverters.isEmpty()) {
authenticationConverters.addAll(0, this.clientRegistrationRequestConverters);
}
this.clientRegistrationRequestConvertersConsumer.accept(authenticationConverters);
oidcClientRegistrationEndpointFilter.setAuthenticationConverter(
new DelegatingAuthenticationConverter(authenticationConverters));
if (this.clientRegistrationResponseHandler != null) {
oidcClientRegistrationEndpointFilter
.setAuthenticationSuccessHandler(this.clientRegistrationResponseHandler);
}
if (this.errorResponseHandler != null) {
oidcClientRegistrationEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler);
}
httpSecurity.addFilterAfter(postProcess(oidcClientRegistrationEndpointFilter), FilterSecurityInterceptor.class);
}
@Override
RequestMatcher getRequestMatcher() {
return this.requestMatcher;
}
private static List<AuthenticationConverter> createDefaultAuthenticationConverters() {
List<AuthenticationConverter> authenticationConverters = new ArrayList<>();
authenticationConverters.add(new OidcClientRegistrationAuthenticationConverter());
return authenticationConverters;
}
private static List<AuthenticationProvider> createDefaultAuthenticationProviders(HttpSecurity httpSecurity) {
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
OidcClientRegistrationAuthenticationProvider oidcClientRegistrationAuthenticationProvider =
new OidcClientRegistrationAuthenticationProvider(
OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity),
OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity),
OAuth2ConfigurerUtils.getTokenGenerator(httpSecurity));
authenticationProviders.add(oidcClientRegistrationAuthenticationProvider);
OidcClientConfigurationAuthenticationProvider oidcClientConfigurationAuthenticationProvider =
new OidcClientConfigurationAuthenticationProvider(
OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity),
OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity));
authenticationProviders.add(oidcClientConfigurationAuthenticationProvider);
return authenticationProviders;
}
}

View File

@@ -13,23 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.util.UriComponentsBuilder;
/**
* Configurer for OpenID Connect 1.0 support.
@@ -37,9 +36,9 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
* @author Joe Grandja
* @since 0.2.0
* @see OAuth2AuthorizationServerConfigurer#oidc
* @see OidcProviderConfigurationEndpointConfigurer
* @see OidcClientRegistrationEndpointConfigurer
* @see OidcUserInfoEndpointConfigurer
* @see OidcProviderConfigurationEndpointFilter
*/
public final class OidcConfigurer extends AbstractOAuth2Configurer {
private final Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = new LinkedHashMap<>();
@@ -50,9 +49,22 @@ public final class OidcConfigurer extends AbstractOAuth2Configurer {
*/
OidcConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor);
addConfigurer(OidcProviderConfigurationEndpointConfigurer.class, new OidcProviderConfigurationEndpointConfigurer(objectPostProcessor));
addConfigurer(OidcUserInfoEndpointConfigurer.class, new OidcUserInfoEndpointConfigurer(objectPostProcessor));
}
/**
* Configures the OpenID Connect 1.0 Provider Configuration Endpoint.
*
* @param providerConfigurationEndpointCustomizer the {@link Customizer} providing access to the {@link OidcProviderConfigurationEndpointConfigurer}
* @return the {@link OidcConfigurer} for further configuration
* @since 0.4.0
*/
public OidcConfigurer providerConfigurationEndpoint(Customizer<OidcProviderConfigurationEndpointConfigurer> providerConfigurationEndpointCustomizer) {
providerConfigurationEndpointCustomizer.customize(getConfigurer(OidcProviderConfigurationEndpointConfigurer.class));
return this;
}
/**
* Configures the OpenID Connect Dynamic Client Registration 1.0 Endpoint.
*
@@ -83,41 +95,37 @@ public final class OidcConfigurer extends AbstractOAuth2Configurer {
}
@Override
<B extends HttpSecurityBuilder<B>> void init(B builder) {
OidcUserInfoEndpointConfigurer userInfoEndpointConfigurer =
getConfigurer(OidcUserInfoEndpointConfigurer.class);
userInfoEndpointConfigurer.init(builder);
OidcClientRegistrationEndpointConfigurer clientRegistrationEndpointConfigurer =
getConfigurer(OidcClientRegistrationEndpointConfigurer.class);
if (clientRegistrationEndpointConfigurer != null) {
clientRegistrationEndpointConfigurer.init(builder);
}
void init(HttpSecurity httpSecurity) {
List<RequestMatcher> requestMatchers = new ArrayList<>();
requestMatchers.add(new AntPathRequestMatcher(
"/.well-known/openid-configuration", HttpMethod.GET.name()));
requestMatchers.add(userInfoEndpointConfigurer.getRequestMatcher());
if (clientRegistrationEndpointConfigurer != null) {
requestMatchers.add(clientRegistrationEndpointConfigurer.getRequestMatcher());
}
this.configurers.values().forEach(configurer -> {
configurer.init(httpSecurity);
requestMatchers.add(configurer.getRequestMatcher());
});
this.requestMatcher = new OrRequestMatcher(requestMatchers);
}
@Override
<B extends HttpSecurityBuilder<B>> void configure(B builder) {
OidcUserInfoEndpointConfigurer userInfoEndpointConfigurer =
getConfigurer(OidcUserInfoEndpointConfigurer.class);
userInfoEndpointConfigurer.configure(builder);
void configure(HttpSecurity httpSecurity) {
OidcClientRegistrationEndpointConfigurer clientRegistrationEndpointConfigurer =
getConfigurer(OidcClientRegistrationEndpointConfigurer.class);
if (clientRegistrationEndpointConfigurer != null) {
clientRegistrationEndpointConfigurer.configure(builder);
OidcProviderConfigurationEndpointConfigurer providerConfigurationEndpointConfigurer =
getConfigurer(OidcProviderConfigurationEndpointConfigurer.class);
providerConfigurationEndpointConfigurer
.addDefaultProviderConfigurationCustomizer((builder) -> {
AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext();
String issuer = authorizationServerContext.getIssuer();
AuthorizationServerSettings authorizationServerSettings = authorizationServerContext.getAuthorizationServerSettings();
String clientRegistrationEndpoint = UriComponentsBuilder.fromUriString(issuer)
.path(authorizationServerSettings.getOidcClientRegistrationEndpoint()).build().toUriString();
builder.clientRegistrationEndpoint(clientRegistrationEndpoint);
});
}
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
OidcProviderConfigurationEndpointFilter oidcProviderConfigurationEndpointFilter =
new OidcProviderConfigurationEndpointFilter(providerSettings);
builder.addFilterBefore(postProcess(oidcProviderConfigurationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
this.configurers.values().forEach(configurer -> configurer.configure(httpSecurity));
}
@Override

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
import java.util.function.Consumer;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.server.authorization.oidc.OidcProviderConfiguration;
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
/**
* Configurer for the OpenID Connect 1.0 Provider Configuration Endpoint.
*
* @author Joe Grandja
* @since 0.4.0
* @see OidcConfigurer#providerConfigurationEndpoint
* @see OidcProviderConfigurationEndpointFilter
*/
public final class OidcProviderConfigurationEndpointConfigurer extends AbstractOAuth2Configurer {
private RequestMatcher requestMatcher;
private Consumer<OidcProviderConfiguration.Builder> providerConfigurationCustomizer;
private Consumer<OidcProviderConfiguration.Builder> defaultProviderConfigurationCustomizer;
/**
* Restrict for internal use only.
*/
OidcProviderConfigurationEndpointConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor);
}
/**
* Sets the {@code Consumer} providing access to the {@link OidcProviderConfiguration.Builder}
* allowing the ability to customize the claims of the OpenID Provider's configuration.
*
* @param providerConfigurationCustomizer the {@code Consumer} providing access to the {@link OidcProviderConfiguration.Builder}
* @return the {@link OidcProviderConfigurationEndpointConfigurer} for further configuration
*/
public OidcProviderConfigurationEndpointConfigurer providerConfigurationCustomizer(
Consumer<OidcProviderConfiguration.Builder> providerConfigurationCustomizer) {
this.providerConfigurationCustomizer = providerConfigurationCustomizer;
return this;
}
void addDefaultProviderConfigurationCustomizer(
Consumer<OidcProviderConfiguration.Builder> defaultProviderConfigurationCustomizer) {
this.defaultProviderConfigurationCustomizer =
this.defaultProviderConfigurationCustomizer == null ?
defaultProviderConfigurationCustomizer :
this.defaultProviderConfigurationCustomizer.andThen(defaultProviderConfigurationCustomizer);
}
@Override
void init(HttpSecurity httpSecurity) {
this.requestMatcher = new AntPathRequestMatcher(
"/.well-known/openid-configuration", HttpMethod.GET.name());
}
@Override
void configure(HttpSecurity httpSecurity) {
OidcProviderConfigurationEndpointFilter oidcProviderConfigurationEndpointFilter =
new OidcProviderConfigurationEndpointFilter();
Consumer<OidcProviderConfiguration.Builder> providerConfigurationCustomizer = getProviderConfigurationCustomizer();
if (providerConfigurationCustomizer != null) {
oidcProviderConfigurationEndpointFilter.setProviderConfigurationCustomizer(providerConfigurationCustomizer);
}
httpSecurity.addFilterBefore(postProcess(oidcProviderConfigurationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
}
private Consumer<OidcProviderConfiguration.Builder> getProviderConfigurationCustomizer() {
Consumer<OidcProviderConfiguration.Builder> providerConfigurationCustomizer = null;
if (this.defaultProviderConfigurationCustomizer != null || this.providerConfigurationCustomizer != null) {
if (this.defaultProviderConfigurationCustomizer != null) {
providerConfigurationCustomizer = this.defaultProviderConfigurationCustomizer;
}
if (this.providerConfigurationCustomizer != null) {
providerConfigurationCustomizer =
providerConfigurationCustomizer == null ?
this.providerConfigurationCustomizer :
providerConfigurationCustomizer.andThen(this.providerConfigurationCustomizer);
}
}
return providerConfigurationCustomizer;
}
@Override
RequestMatcher getRequestMatcher() {
return this.requestMatcher;
}
}

View File

@@ -0,0 +1,259 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
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.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcUserInfoEndpointFilter;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.web.authentication.DelegatingAuthenticationConverter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
/**
* Configurer for OpenID Connect 1.0 UserInfo Endpoint.
*
* @author Steve Riesenberg
* @author Daniel Garnier-Moiroux
* @since 0.2.1
* @see OidcConfigurer#userInfoEndpoint
* @see OidcUserInfoEndpointFilter
*/
public final class OidcUserInfoEndpointConfigurer extends AbstractOAuth2Configurer {
private RequestMatcher requestMatcher;
private final List<AuthenticationConverter> userInfoRequestConverters = new ArrayList<>();
private Consumer<List<AuthenticationConverter>> userInfoRequestConvertersConsumer = (userInfoRequestConverters) -> {};
private final List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
private Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer = (authenticationProviders) -> {};
private AuthenticationSuccessHandler userInfoResponseHandler;
private AuthenticationFailureHandler errorResponseHandler;
private Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper;
/**
* Restrict for internal use only.
*/
OidcUserInfoEndpointConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor);
}
/**
* Adds an {@link AuthenticationConverter} used when attempting to extract an UserInfo Request from {@link HttpServletRequest}
* to an instance of {@link OidcUserInfoAuthenticationToken} used for authenticating the request.
*
* @param userInfoRequestConverter an {@link AuthenticationConverter} used when attempting to extract an UserInfo Request from {@link HttpServletRequest}
* @return the {@link OidcUserInfoEndpointConfigurer} for further configuration
* @since 0.4.0
*/
public OidcUserInfoEndpointConfigurer userInfoRequestConverter(AuthenticationConverter userInfoRequestConverter) {
Assert.notNull(userInfoRequestConverter, "userInfoRequestConverter cannot be null");
this.userInfoRequestConverters.add(userInfoRequestConverter);
return this;
}
/**
* Sets the {@code Consumer} providing access to the {@code List} of default
* and (optionally) added {@link #userInfoRequestConverter(AuthenticationConverter) AuthenticationConverter}'s
* allowing the ability to add, remove, or customize a specific {@link AuthenticationConverter}.
*
* @param userInfoRequestConvertersConsumer the {@code Consumer} providing access to the {@code List} of default and (optionally) added {@link AuthenticationConverter}'s
* @return the {@link OidcUserInfoEndpointConfigurer} for further configuration
* @since 0.4.0
*/
public OidcUserInfoEndpointConfigurer userInfoRequestConverters(
Consumer<List<AuthenticationConverter>> userInfoRequestConvertersConsumer) {
Assert.notNull(userInfoRequestConvertersConsumer, "userInfoRequestConvertersConsumer cannot be null");
this.userInfoRequestConvertersConsumer = userInfoRequestConvertersConsumer;
return this;
}
/**
* Adds an {@link AuthenticationProvider} used for authenticating an {@link OidcUserInfoAuthenticationToken}.
*
* @param authenticationProvider an {@link AuthenticationProvider} used for authenticating an {@link OidcUserInfoAuthenticationToken}
* @return the {@link OidcUserInfoEndpointConfigurer} for further configuration
* @since 0.4.0
*/
public OidcUserInfoEndpointConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) {
Assert.notNull(authenticationProvider, "authenticationProvider cannot be null");
this.authenticationProviders.add(authenticationProvider);
return this;
}
/**
* Sets the {@code Consumer} providing access to the {@code List} of default
* and (optionally) added {@link #authenticationProvider(AuthenticationProvider) AuthenticationProvider}'s
* allowing the ability to add, remove, or customize a specific {@link AuthenticationProvider}.
*
* @param authenticationProvidersConsumer the {@code Consumer} providing access to the {@code List} of default and (optionally) added {@link AuthenticationProvider}'s
* @return the {@link OidcUserInfoEndpointConfigurer} for further configuration
* @since 0.4.0
*/
public OidcUserInfoEndpointConfigurer authenticationProviders(
Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer) {
Assert.notNull(authenticationProvidersConsumer, "authenticationProvidersConsumer cannot be null");
this.authenticationProvidersConsumer = authenticationProvidersConsumer;
return this;
}
/**
* Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OidcUserInfoAuthenticationToken}
* and returning the {@link OidcUserInfo UserInfo Response}.
*
* @param userInfoResponseHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OidcUserInfoAuthenticationToken}
* @return the {@link OidcUserInfoEndpointConfigurer} for further configuration
* @since 0.4.0
*/
public OidcUserInfoEndpointConfigurer userInfoResponseHandler(AuthenticationSuccessHandler userInfoResponseHandler) {
this.userInfoResponseHandler = userInfoResponseHandler;
return this;
}
/**
* Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
* and returning the {@link OAuth2Error Error Response}.
*
* @param errorResponseHandler the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
* @return the {@link OidcUserInfoEndpointConfigurer} for further configuration
* @since 0.4.0
*/
public OidcUserInfoEndpointConfigurer errorResponseHandler(AuthenticationFailureHandler errorResponseHandler) {
this.errorResponseHandler = errorResponseHandler;
return this;
}
/**
* Sets the {@link Function} used to extract claims from {@link OidcUserInfoAuthenticationContext}
* to an instance of {@link OidcUserInfo} for the UserInfo response.
*
* <p>
* The {@link OidcUserInfoAuthenticationContext} gives the mapper access to the {@link OidcUserInfoAuthenticationToken},
* as well as, the following context attributes:
* <ul>
* <li>{@link OidcUserInfoAuthenticationContext#getAccessToken()} containing the bearer token used to make the request.</li>
* <li>{@link OidcUserInfoAuthenticationContext#getAuthorization()} containing the {@link OidcIdToken} and
* {@link OAuth2AccessToken} associated with the bearer token used to make the request.</li>
* </ul>
*
* @param userInfoMapper the {@link Function} used to extract claims from {@link OidcUserInfoAuthenticationContext} to an instance of {@link OidcUserInfo}
* @return the {@link OidcUserInfoEndpointConfigurer} for further configuration
*/
public OidcUserInfoEndpointConfigurer userInfoMapper(
Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper) {
this.userInfoMapper = userInfoMapper;
return this;
}
@Override
void init(HttpSecurity httpSecurity) {
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
String userInfoEndpointUri = authorizationServerSettings.getOidcUserInfoEndpoint();
this.requestMatcher = new OrRequestMatcher(
new AntPathRequestMatcher(userInfoEndpointUri, HttpMethod.GET.name()),
new AntPathRequestMatcher(userInfoEndpointUri, HttpMethod.POST.name()));
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
if (!this.authenticationProviders.isEmpty()) {
authenticationProviders.addAll(0, this.authenticationProviders);
}
this.authenticationProvidersConsumer.accept(authenticationProviders);
authenticationProviders.forEach(authenticationProvider ->
httpSecurity.authenticationProvider(postProcess(authenticationProvider)));
}
@Override
void configure(HttpSecurity httpSecurity) {
AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class);
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
OidcUserInfoEndpointFilter oidcUserInfoEndpointFilter =
new OidcUserInfoEndpointFilter(
authenticationManager,
authorizationServerSettings.getOidcUserInfoEndpoint());
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
if (!this.userInfoRequestConverters.isEmpty()) {
authenticationConverters.addAll(0, this.userInfoRequestConverters);
}
this.userInfoRequestConvertersConsumer.accept(authenticationConverters);
oidcUserInfoEndpointFilter.setAuthenticationConverter(
new DelegatingAuthenticationConverter(authenticationConverters));
if (this.userInfoResponseHandler != null) {
oidcUserInfoEndpointFilter.setAuthenticationSuccessHandler(this.userInfoResponseHandler);
}
if (this.errorResponseHandler != null) {
oidcUserInfoEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler);
}
httpSecurity.addFilterAfter(postProcess(oidcUserInfoEndpointFilter), FilterSecurityInterceptor.class);
}
@Override
RequestMatcher getRequestMatcher() {
return this.requestMatcher;
}
private static List<AuthenticationConverter> createDefaultAuthenticationConverters() {
List<AuthenticationConverter> authenticationConverters = new ArrayList<>();
authenticationConverters.add(
(request) -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new OidcUserInfoAuthenticationToken(authentication);
}
);
return authenticationConverters;
}
private List<AuthenticationProvider> createDefaultAuthenticationProviders(HttpSecurity httpSecurity) {
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
OidcUserInfoAuthenticationProvider oidcUserInfoAuthenticationProvider =
new OidcUserInfoAuthenticationProvider(
OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity));
if (this.userInfoMapper != null) {
oidcUserInfoAuthenticationProvider.setUserInfoMapper(this.userInfoMapper);
}
authenticationProviders.add(oidcUserInfoAuthenticationProvider);
return authenticationProviders;
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.context;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
/**
* A context that holds information of the Authorization Server runtime environment.
*
* @author Joe Grandja
* @since 0.2.2
* @see AuthorizationServerSettings
* @see AuthorizationServerContextHolder
*/
public interface AuthorizationServerContext {
/**
* Returns the {@code URL} of the Authorization Server's issuer identifier.
*
* @return the {@code URL} of the Authorization Server's issuer identifier
*/
String getIssuer();
/**
* Returns the {@link AuthorizationServerSettings}.
*
* @return the {@link AuthorizationServerSettings}
*/
AuthorizationServerSettings getAuthorizationServerSettings();
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.context;
/**
* A holder of the {@link AuthorizationServerContext} that associates it with the current thread using a {@code ThreadLocal}.
*
* @author Joe Grandja
* @since 0.2.2
* @see AuthorizationServerContext
*/
public final class AuthorizationServerContextHolder {
private static final ThreadLocal<AuthorizationServerContext> holder = new ThreadLocal<>();
private AuthorizationServerContextHolder() {
}
/**
* Returns the {@link AuthorizationServerContext} bound to the current thread.
*
* @return the {@link AuthorizationServerContext}
*/
public static AuthorizationServerContext getContext() {
return holder.get();
}
/**
* Bind the given {@link AuthorizationServerContext} to the current thread.
*
* @param authorizationServerContext the {@link AuthorizationServerContext}
*/
public static void setContext(AuthorizationServerContext authorizationServerContext) {
if (authorizationServerContext == null) {
resetContext();
} else {
holder.set(authorizationServerContext);
}
}
/**
* Reset the {@link AuthorizationServerContext} bound to the current thread.
*/
public static void resetContext() {
holder.remove();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.context;
package org.springframework.security.oauth2.server.authorization.context;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

View File

@@ -1,70 +0,0 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.context;
import java.util.function.Supplier;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.util.Assert;
/**
* A context that holds information of the Provider.
*
* @author Joe Grandja
* @since 0.2.2
* @see ProviderSettings
* @see ProviderContextHolder
*/
public final class ProviderContext {
private final ProviderSettings providerSettings;
private final Supplier<String> issuerSupplier;
/**
* Constructs a {@code ProviderContext} using the provided parameters.
*
* @param providerSettings the provider settings
* @param issuerSupplier a {@code Supplier} for the {@code URL} of the Provider's issuer identifier
*/
public ProviderContext(ProviderSettings providerSettings, @Nullable Supplier<String> issuerSupplier) {
Assert.notNull(providerSettings, "providerSettings cannot be null");
this.providerSettings = providerSettings;
this.issuerSupplier = issuerSupplier;
}
/**
* Returns the {@link ProviderSettings}.
*
* @return the {@link ProviderSettings}
*/
public ProviderSettings getProviderSettings() {
return this.providerSettings;
}
/**
* Returns the {@code URL} of the Provider's issuer identifier.
* The issuer identifier is resolved from the constructor parameter {@code Supplier<String>}
* or if not provided then defaults to {@link ProviderSettings#getIssuer()}.
*
* @return the {@code URL} of the Provider's issuer identifier
*/
public String getIssuer() {
return this.issuerSupplier != null ?
this.issuerSupplier.get() :
getProviderSettings().getIssuer();
}
}

View File

@@ -1,63 +0,0 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.context;
import org.springframework.security.oauth2.server.authorization.web.ProviderContextFilter;
/**
* A holder of {@link ProviderContext} that associates it with the current thread using a {@code ThreadLocal}.
*
* @author Joe Grandja
* @since 0.2.2
* @see ProviderContext
* @see ProviderContextFilter
*/
public final class ProviderContextHolder {
private static final ThreadLocal<ProviderContext> holder = new ThreadLocal<>();
private ProviderContextHolder() {
}
/**
* Returns the {@link ProviderContext} bound to the current thread.
*
* @return the {@link ProviderContext}
*/
public static ProviderContext getProviderContext() {
return holder.get();
}
/**
* Bind the given {@link ProviderContext} to the current thread.
*
* @param providerContext the {@link ProviderContext}
*/
public static void setProviderContext(ProviderContext providerContext) {
if (providerContext == null) {
resetProviderContext();
} else {
holder.set(providerContext);
}
}
/**
* Reset the {@link ProviderContext} bound to the current thread.
*/
public static void resetProviderContext() {
holder.remove();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.oidc.http.converter;
package org.springframework.security.oauth2.server.authorization.http.converter;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
@@ -23,14 +23,10 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert
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
* @since 0.1.1
*/
final class HttpMessageConverters {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.http.converter;
package org.springframework.security.oauth2.server.authorization.http.converter;
import java.net.URL;
import java.util.Collection;
@@ -31,10 +31,10 @@ 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.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadataClaimNames;
import org.springframework.util.Assert;
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.security.oauth2.core.http.converter;
package org.springframework.security.oauth2.server.authorization.http.converter;
import java.net.URL;
import java.time.Instant;
@@ -37,10 +37,10 @@ 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.security.oauth2.server.authorization.OAuth2TokenIntrospection;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.jackson2;
import java.util.Map;
@@ -24,14 +23,10 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* TODO
* This class is a straight copy from Spring Security.
* It should be consolidated when merging this codebase into Spring Security.
*
* Utility class for {@code JsonNode}.
*
* @author Joe Grandja
* @since 5.3
* @since 0.1.2
*/
abstract class JsonNodeUtils {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.jackson2;
import java.io.IOException;
@@ -24,28 +23,21 @@ import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.util.StdConverter;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest.Builder;
/**
* TODO
* This class is a straight copy from Spring Security.
* It should be consolidated when merging this codebase into Spring Security.
*
* A {@code JsonDeserializer} for {@link OAuth2AuthorizationRequest}.
*
* @author Joe Grandja
* @since 5.3
* @since 0.1.2
* @see OAuth2AuthorizationRequest
* @see OAuth2AuthorizationRequestMixin
*/
final class OAuth2AuthorizationRequestDeserializer extends JsonDeserializer<OAuth2AuthorizationRequest> {
private static final StdConverter<JsonNode, AuthorizationGrantType> AUTHORIZATION_GRANT_TYPE_CONVERTER = new StdConverters.AuthorizationGrantTypeConverter();
@Override
public OAuth2AuthorizationRequest deserialize(JsonParser parser, DeserializationContext context)
throws IOException {
@@ -56,8 +48,8 @@ final class OAuth2AuthorizationRequestDeserializer extends JsonDeserializer<OAut
private OAuth2AuthorizationRequest deserialize(JsonParser parser, ObjectMapper mapper, JsonNode root)
throws JsonParseException {
AuthorizationGrantType authorizationGrantType = AUTHORIZATION_GRANT_TYPE_CONVERTER
.convert(JsonNodeUtils.findObjectNode(root, "authorizationGrantType"));
AuthorizationGrantType authorizationGrantType = convertAuthorizationGrantType(
JsonNodeUtils.findObjectNode(root, "authorizationGrantType"));
Builder builder = getBuilder(parser, authorizationGrantType);
builder.authorizationUri(JsonNodeUtils.findStringValue(root, "authorizationUri"));
builder.clientId(JsonNodeUtils.findStringValue(root, "clientId"));
@@ -76,10 +68,15 @@ final class OAuth2AuthorizationRequestDeserializer extends JsonDeserializer<OAut
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationGrantType)) {
return OAuth2AuthorizationRequest.authorizationCode();
}
if (AuthorizationGrantType.IMPLICIT.equals(authorizationGrantType)) {
return OAuth2AuthorizationRequest.implicit();
}
throw new JsonParseException(parser, "Invalid authorizationGrantType");
}
private static AuthorizationGrantType convertAuthorizationGrantType(JsonNode jsonNode) {
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equalsIgnoreCase(value)) {
return AuthorizationGrantType.AUTHORIZATION_CODE;
}
return null;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.jackson2;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
@@ -24,15 +23,11 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
/**
* TODO
* This class is a straight copy from Spring Security.
* It should be consolidated when merging this codebase into Spring Security.
*
* This mixin class is used to serialize/deserialize {@link OAuth2AuthorizationRequest}.
* It also registers a custom deserializer {@link OAuth2AuthorizationRequestDeserializer}.
*
* @author Joe Grandja
* @since 5.3
* @since 0.1.2
* @see OAuth2AuthorizationRequest
* @see OAuth2AuthorizationRequestDeserializer
*/

View File

@@ -24,10 +24,10 @@ import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.oauth2.core.OAuth2TokenFormat;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
/**
* Jackson {@code Module} for {@code spring-authorization-server}, that registers the

View File

@@ -20,7 +20,7 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.oauth2.core.OAuth2TokenFormat;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
/**
* This mixin class is used to serialize/deserialize {@link OAuth2TokenFormat}.

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