22 Commits

Author SHA1 Message Date
Joe Grandja
a1dd99a332 Release 1.0.0-M1 2022-08-16 16:15:34 -04:00
Joe Grandja
d04a85d05c Fix test in OidcTests 2022-08-16 16:07:56 -04:00
Joe Grandja
4b04c705c2 Merge remote-tracking branch 'upstream/0.4.x' into main 2022-08-16 12:02:08 -04:00
Joe Grandja
a5e6b032de Update github workflows to build with Java 17 2022-08-02 09:56:52 -04:00
Joe Grandja
2b47a16956 Update Support Policy 2022-08-02 09:44:29 -04:00
Joe Grandja
be80b2e74d Update to org.hsqldb:hsqldb:2.6.1
Closes gh-843
2022-08-02 09:44:16 -04:00
Joe Grandja
338e6f703d Update to com.squareup.okhttp3:okhttp:4.10.0
Closes gh-842
2022-08-02 09:44:01 -04:00
Joe Grandja
589043df3b Update to mockito-core:4.6.1
Closes gh-841
2022-08-02 09:43:47 -04:00
Joe Grandja
3942d886b5 Update to assertj-core:3.23.1
Closes gh-840
2022-08-02 09:43:34 -04:00
Joe Grandja
5b622ced9a Update to nimbus-jose-jwt:9.23
Closes gh-839
2022-08-02 09:43:19 -04:00
Joe Grandja
0f3f8485db Fix OAuth2AuthorizationCodeGrantTests
Issue gh-482
2022-08-02 09:43:19 -04:00
Joe Grandja
ea1d649ed1 Fix references to OidcUserInfo.phoneNumberVerified() 2022-08-02 09:43:19 -04:00
Joe Grandja
9b52e918af Update packages from javax to jakarta
Issue gh-838
2022-08-02 09:42:56 -04:00
Joe Grandja
a3019b775f Update to jakarta.servlet-api:5.0.0
Closes gh-838
2022-08-02 09:42:34 -04:00
Joe Grandja
395cdbd620 Update to thymeleaf-extras-springsecurity6
Closes gh-837
2022-08-02 09:42:13 -04:00
Joe Grandja
d021bad8c5 Update to Spring Security 6.0.0-M6
Closes gh-836
2022-08-02 09:41:59 -04:00
Joe Grandja
737a1ea4d2 Update to Spring Framework 6.0.0-M5
Closes gh-835
2022-08-02 09:41:46 -04:00
Joe Grandja
0bd153c63d Update to Spring Boot 3.0.0-M4
Closes gh-834
2022-08-02 09:41:28 -04:00
Joe Grandja
67d11d5fc7 Add https://repo.spring.io/milestone 2022-08-02 09:41:28 -04:00
Joe Grandja
d0fa352afe Upgrade to Gradle 7.4.2
Closes gh-833
2022-08-02 09:41:14 -04:00
Joe Grandja
a2e6bd4974 Upgrade to Java 17
Closes gh-832
2022-08-02 09:40:14 -04:00
Joe Grandja
0cf2c236f2 Bump to initial major version 2022-08-02 09:13:29 -04:00
145 changed files with 2857 additions and 4394 deletions

View File

@@ -34,7 +34,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
jdk: [8,11,17]
jdk: [17]
fail-fast: false
runs-on: ${{ matrix.os }}
if: needs.prerequisites.outputs.runjobs
@@ -70,7 +70,7 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: 8
java-version: 17
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Snapshot Tests
@@ -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.8.+' -PlocksDisabled --stacktrace
run: ./gradlew test --refresh-dependencies -Duser.name=spring-builds+github -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PforceMavenRepositories=snapshot -PspringFrameworkVersion='6.0.+' -PspringSecurityVersion='6.0.+' -PlocksDisabled --stacktrace
deploy_artifacts:
name: Deploy Artifacts
needs: [build, snapshot_tests]
@@ -90,7 +90,7 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: 8
java-version: 17
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Deploy Artifacts
@@ -114,7 +114,7 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v1
with:
java-version: 8
java-version: 17
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Deploy Docs

View File

@@ -12,7 +12,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
jdk: [8]
jdk: [17]
fail-fast: false
steps:
- uses: actions/checkout@v2

View File

@@ -16,7 +16,8 @@ It is recommended to install the ZenHub https://www.zenhub.com/extension[browser
The feature list can be viewed in the https://docs.spring.io/spring-authorization-server/docs/current/reference/html/overview.html#feature-list[reference documentation].
== Support Policy
The Spring Authorization Server project provides software support and is documented in its link:SUPPORT_POLICY.adoc[support policy].
The Spring Authorization Server project provides software support through the https://tanzu.vmware.com/support/oss[VMware Tanzu OSS support policy].
https://tanzu.vmware.com/spring-runtime[Commercial support], which offers an extended support period, is also available from VMware.
== 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.
@@ -35,9 +36,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-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.
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.
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].
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].
== Code of Conduct
This project adheres to the Contributor Covenant link:CODE_OF_CONDUCT.adoc[code of conduct].
@@ -52,9 +53,9 @@ In the instructions below, https://vimeo.com/34436402[`./gradlew`] is invoked fr
a cross-platform, self-contained bootstrap mechanism for the build.
=== Prerequisites
https://help.github.com/set-up-git-redirect[Git] and the https://www.oracle.com/technetwork/java/javase/downloads[JDK8 build].
https://help.github.com/set-up-git-redirect[Git] and the https://www.oracle.com/technetwork/java/javase/downloads[JDK17 build].
Be sure that your `JAVA_HOME` environment variable points to the `jdk1.8.0` folder extracted from the JDK download.
Be sure that your `JAVA_HOME` environment variable points to the `jdk17` folder extracted from the JDK download.
=== Check out sources
[indent=0]

View File

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

View File

@@ -4,7 +4,7 @@ plugins {
id "groovy"
}
sourceCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_17
repositories {
gradlePluginPortal()
@@ -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.23"
implementation "org.springframework:spring-core:6.0.0-M5"
}

View File

@@ -73,7 +73,7 @@ public class SpringJavaPlugin implements Plugin<Project> {
// Apply Java source compatibility version
JavaPluginExtension java = project.getExtensions().getByType(JavaPluginExtension.class);
java.setTargetCompatibility(JavaVersion.VERSION_1_8);
java.setTargetCompatibility(JavaVersion.VERSION_17);
// Configure Java tasks
project.getTasks().withType(JavaCompile.class, (javaCompile) -> {
@@ -81,7 +81,7 @@ public class SpringJavaPlugin implements Plugin<Project> {
options.setEncoding("UTF-8");
options.getCompilerArgs().add("-parameters");
if (JavaVersion.current().isJava11Compatible()) {
options.getRelease().set(8);
options.getRelease().set(17);
}
});
project.getTasks().withType(Jar.class, (jar) -> jar.manifest((manifest) -> {

View File

@@ -78,10 +78,9 @@ public class CheckClasspathForProhibitedDependencies extends DefaultTask {
if (group.equals("javax.money")) {
return false;
}
// TODO: Uncomment the following lines when upgrading to Spring Framework 6
// if (group.startsWith("javax")) {
// return true;
// }
if (group.startsWith("javax")) {
return true;
}
if (group.equals("commons-logging")) {
return true;
}

View File

@@ -54,7 +54,7 @@ public class SpringJavadocApiPlugin implements Plugin<Project> {
api.doLast(new Action<Task>() {
@Override
public void execute(Task task) {
if (JavaVersion.current().isJava8Compatible()) {
if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_17)) {
project.copy((copy) -> copy.from(api.getDestinationDir())
.into(api.getDestinationDir())
.include("element-list")

View File

@@ -9,16 +9,16 @@ 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.4")
api platform("com.fasterxml.jackson:jackson-bom:2.13.3")
constraints {
api "com.nimbusds:nimbus-jose-jwt:9.24.4"
api "javax.servlet:javax.servlet-api:4.0.1"
api "com.nimbusds:nimbus-jose-jwt:9.23"
api "jakarta.servlet:jakarta.servlet-api:5.0.0"
api "junit:junit:4.13.2"
api "org.assertj:assertj-core:3.23.1"
api "org.mockito:mockito-core:4.8.0"
api "org.mockito:mockito-core:4.6.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"
api "org.hsqldb:hsqldb:2.6.1"
}
}

View File

@@ -91,7 +91,7 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
.registeredClientRepository(registeredClientRepository) <1>
.authorizationService(authorizationService) <2>
.authorizationConsentService(authorizationConsentService) <3>
.authorizationServerSettings(authorizationServerSettings) <4>
.providerSettings(providerSettings) <4>
.tokenGenerator(tokenGenerator) <5>
.clientAuthentication(clientAuthentication -> { }) <6>
.authorizationEndpoint(authorizationEndpoint -> { }) <7>
@@ -109,7 +109,7 @@ 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> `authorizationServerSettings()`: The <<configuring-authorization-server-settings, `AuthorizationServerSettings`>> (*REQUIRED*) for customizing configuration settings for the OAuth2 authorization server.
<4> `providerSettings()`: The <<configuring-provider-settings, `ProviderSettings`>> (*REQUIRED*) for customizing configuration settings for the OAuth2 authorization server.
<5> `tokenGenerator()`: The xref:core-model-components.adoc#oauth2-token-generator[`OAuth2TokenGenerator`] for generating tokens supported by the OAuth2 authorization server.
<6> `clientAuthentication()`: The configurer for <<configuring-client-authentication, OAuth2 Client Authentication>>.
<7> `authorizationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-authorization-endpoint[OAuth2 Authorization endpoint].
@@ -119,16 +119,16 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
<11> `userInfoEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-user-info-endpoint[OpenID Connect 1.0 UserInfo endpoint].
<12> `clientRegistrationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration endpoint].
[[configuring-authorization-server-settings]]
== Configuring Authorization Server Settings
[[configuring-provider-settings]]
== Configuring Provider Settings
`AuthorizationServerSettings` contains the configuration settings for the OAuth2 authorization server.
`ProviderSettings` contains the configuration settings for the OAuth2 authorization server (provider).
It specifies the `URI` for the protocol endpoints as well as the https://datatracker.ietf.org/doc/html/rfc8414#section-2[issuer identifier].
The default `URI` for the protocol endpoints are as follows:
[source,java]
----
public final class AuthorizationServerSettings extends AbstractSettings {
public final class ProviderSettings extends AbstractSettings {
...
@@ -149,18 +149,18 @@ public final class AuthorizationServerSettings extends AbstractSettings {
----
[NOTE]
`AuthorizationServerSettings` is a *REQUIRED* component.
`ProviderSettings` is a *REQUIRED* component.
[TIP]
<<default-configuration, `@Import(OAuth2AuthorizationServerConfiguration.class)`>> automatically registers an `AuthorizationServerSettings` `@Bean`, if not already provided.
<<default-configuration, `@Import(OAuth2AuthorizationServerConfiguration.class)`>> automatically registers a `ProviderSettings` `@Bean`, if not already provided.
The following example shows how to customize the configuration settings and register an `AuthorizationServerSettings` `@Bean`:
The following example shows how to customize the configuration settings and register a `ProviderSettings` `@Bean`:
[source,java]
----
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder()
public ProviderSettings providerSettings() {
return ProviderSettings.builder()
.issuer("https://example.com")
.authorizationEndpoint("/oauth2/v1/authorize")
.tokenEndpoint("/oauth2/v1/token")
@@ -173,14 +173,17 @@ public AuthorizationServerSettings authorizationServerSettings() {
}
----
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.
The `ProviderContext` is a context object that holds information about the provider.
It provides access to the `ProviderSettings` and the "`current`" issuer identifier.
[NOTE]
If the issuer identifier is not configured in `AuthorizationServerSettings.builder().issuer(String)`, it is resolved from the current request.
If the issuer identifier is not configured in `ProviderSettings.builder().issuer(String)`, it is resolved from the current request.
[NOTE]
The `AuthorizationServerContext` is accessible through the `AuthorizationServerContextHolder`, which associates it with the current request thread by using a `ThreadLocal`.
The `ProviderContext` is accessible through the `ProviderContextHolder`, which associates it with the current request thread by using a `ThreadLocal`.
[NOTE]
The `ProviderContextFilter` associates the `ProviderContext` with the `ProviderContextHolder`.
[[configuring-client-authentication]]
== Configuring Client Authentication
@@ -202,22 +205,18 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
.clientAuthentication(clientAuthentication ->
clientAuthentication
.authenticationConverter(authenticationConverter) <1>
.authenticationConverters(authenticationConvertersConsumer) <2>
.authenticationProvider(authenticationProvider) <3>
.authenticationProviders(authenticationProvidersConsumer) <4>
.authenticationSuccessHandler(authenticationSuccessHandler) <5>
.errorResponseHandler(errorResponseHandler) <6>
.authenticationProvider(authenticationProvider) <2>
.authenticationSuccessHandler(authenticationSuccessHandler) <3>
.errorResponseHandler(errorResponseHandler) <4>
);
return http.build();
}
----
<1> `authenticationConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract client credentials from `HttpServletRequest` to an instance of `OAuth2ClientAuthenticationToken`.
<2> `authenticationConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2ClientAuthenticationToken`.
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
<5> `authenticationSuccessHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling a successful client authentication and associating the `OAuth2ClientAuthenticationToken` to the `SecurityContext`.
<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling a failed client authentication and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-5.2[`OAuth2Error` response].
<1> `authenticationConverter()`: The `AuthenticationConverter` (_pre-processor_) used when attempting to extract client credentials from `HttpServletRequest` to an instance of `OAuth2ClientAuthenticationToken`.
<2> `authenticationProvider()`: The `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2ClientAuthenticationToken`. (One or more may be added to replace the defaults.)
<3> `authenticationSuccessHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling a successful client authentication and associating the `OAuth2ClientAuthenticationToken` to the `SecurityContext`.
<4> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling a failed client authentication and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-5.2[`OAuth2Error` response].
`OAuth2ClientAuthenticationConfigurer` configures the `OAuth2ClientAuthenticationFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OAuth2ClientAuthenticationFilter` is the `Filter` that processes client authentication requests.

View File

@@ -163,9 +163,8 @@ public class OAuth2Authorization implements Serializable {
private String registeredClientId; <2>
private String principalName; <3>
private AuthorizationGrantType authorizationGrantType; <4>
private Set<String> authorizedScopes; <5>
private Map<Class<? extends OAuth2Token>, Token<?>> tokens; <6>
private Map<String, Object> attributes; <7>
private Map<Class<? extends OAuth2Token>, Token<?>> tokens; <5>
private Map<String, Object> attributes; <6>
...
@@ -175,9 +174,8 @@ 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> `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.
<5> `tokens`: The `OAuth2Token` instances (and associated metadata) specific to the executed authorization grant type.
<6> `attributes`: The additional attributes specific to the executed authorization grant type for example, the authenticated `Principal`, `OAuth2AuthorizationRequest`, authorized scope(s), and others.
`OAuth2Authorization` and its associated `OAuth2Token` instances have a set lifespan.
A newly issued `OAuth2Token` is active and becomes inactive when it either expires or is invalidated (revoked).
@@ -318,7 +316,7 @@ public interface OAuth2TokenContext extends Context {
default <T extends Authentication> T getPrincipal() ... <2>
default AuthorizationServerContext getAuthorizationServerContext() ... <3>
default ProviderContext getProviderContext() ... <3>
@Nullable
default OAuth2Authorization getAuthorization() ... <4>
@@ -337,7 +335,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> `getAuthorizationServerContext()`: The xref:configuration-model.adoc#configuring-authorization-server-settings[`AuthorizationServerContext`] object that holds information of the Authorization Server runtime environment.
<3> `getProviderContext()`: The xref:configuration-model.adoc#configuring-provider-settings[`ProviderContext`] object that holds information related to the provider.
<4> `getAuthorization()`: The <<oauth2-authorization, OAuth2Authorization>> associated with the authorization grant.
<5> `getAuthorizedScopes()`: The scope(s) authorized for the client.
<6> `getTokenType()`: The `OAuth2TokenType` to generate. The supported values are `code`, `access_token`, `refresh_token`, and `id_token`.

View File

@@ -4,21 +4,22 @@ plugins {
group = project.rootProject.group
version = project.rootProject.version
sourceCompatibility = "1.8"
sourceCompatibility = "17"
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
}
dependencies {
implementation platform("org.springframework.boot:spring-boot-dependencies:2.7.0")
implementation platform("org.springframework.boot:spring-boot-dependencies:3.0.0-M4")
implementation "org.springframework.boot:spring-boot-starter-web"
implementation "org.springframework.boot:spring-boot-starter-thymeleaf"
implementation "org.springframework.boot:spring-boot-starter-security"
implementation "org.springframework.boot:spring-boot-starter-oauth2-client"
implementation "org.springframework.boot:spring-boot-starter-oauth2-resource-server"
implementation "org.springframework.boot:spring-boot-starter-data-jpa"
implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity5"
implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity6"
implementation project(":spring-security-oauth2-authorization-server")
runtimeOnly "com.h2database:h2"
testImplementation "org.springframework.boot:spring-boot-starter-test"

View File

@@ -42,8 +42,8 @@ import org.springframework.security.oauth2.server.authorization.client.InMemoryR
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.ProviderSettings;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
@@ -150,8 +150,8 @@ public class SecurityConfig {
}
@Bean // <7>
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
public ProviderSettings providerSettings() {
return ProviderSettings.builder().build();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2022 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.
@@ -17,10 +17,10 @@ package sample.jpa.entity.authorization;
import java.time.Instant;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "`authorization`")

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2022 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.
@@ -18,11 +18,11 @@ package sample.jpa.entity.authorizationConsent;
import java.io.Serializable;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import jakarta.persistence.Table;
@Entity
@Table(name = "`authorizationConsent`")

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2022 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.
@@ -17,10 +17,10 @@ package sample.jpa.entity.client;
import java.time.Instant;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "`client`")

View File

@@ -44,8 +44,8 @@ import org.springframework.security.oauth2.server.authorization.client.InMemoryR
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.ProviderSettings;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
@@ -158,8 +158,8 @@ public class EnableUserInfoSecurityConfig {
}
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
public ProviderSettings providerSettings() {
return ProviderSettings.builder().build();
}
// @fold:off

View File

@@ -66,7 +66,7 @@ public class OidcUserInfoService {
.zoneinfo("Europe/Paris")
.locale("en-US")
.phoneNumber("+1 (604) 555-1234;ext=5678")
.phoneNumberVerified("false")
.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

@@ -49,8 +49,8 @@ import org.springframework.security.oauth2.server.authorization.config.annotatio
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.authorization.settings.ProviderSettings;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@@ -182,8 +182,8 @@ public class JwtUserInfoMapperSecurityConfig {
}
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
public ProviderSettings providerSettings() {
return ProviderSettings.builder().build();
}
// @fold:off

View File

@@ -19,10 +19,10 @@ import java.io.Closeable;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.mock.web.MockServletConfig;

View File

@@ -6,7 +6,7 @@ If you are just getting started with Spring Authorization Server, the following
[[system-requirements]]
== System Requirements
Spring Authorization Server requires a Java 8 or higher Runtime Environment.
Spring Authorization Server requires a Java 17 or higher Runtime Environment.
[[installing-spring-authorization-server]]
== Installing Spring Authorization Server
@@ -55,4 +55,4 @@ 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-authorization-server-settings[`AuthorizationServerSettings`] to configure Spring Authorization Server.
<7> An instance of xref:configuration-model#configuring-provider-settings[`ProviderSettings`] to configure Spring Authorization Server.

View File

@@ -21,32 +21,28 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
.authorizationEndpoint(authorizationEndpoint ->
authorizationEndpoint
.authorizationRequestConverter(authorizationRequestConverter) <1>
.authorizationRequestConverters(authorizationRequestConvertersConsumer) <2>
.authenticationProvider(authenticationProvider) <3>
.authenticationProviders(authenticationProvidersConsumer) <4>
.authorizationResponseHandler(authorizationResponseHandler) <5>
.errorResponseHandler(errorResponseHandler) <6>
.consentPage("/oauth2/v1/authorize") <7>
.authenticationProvider(authenticationProvider) <2>
.authorizationResponseHandler(authorizationResponseHandler) <3>
.errorResponseHandler(errorResponseHandler) <4>
.consentPage("/oauth2/v1/authorize") <5>
);
return http.build();
}
----
<1> `authorizationRequestConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1[OAuth2 authorization request] (or consent) from `HttpServletRequest` to an instance of `OAuth2AuthorizationCodeRequestAuthenticationToken` or `OAuth2AuthorizationConsentAuthenticationToken`.
<2> `authorizationRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2AuthorizationCodeRequestAuthenticationToken` or `OAuth2AuthorizationConsentAuthenticationToken`.
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
<5> `authorizationResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OAuth2AuthorizationCodeRequestAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2[OAuth2AuthorizationResponse].
<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthorizationCodeRequestAuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1[OAuth2Error response].
<7> `consentPage()`: The `URI` of the custom consent page to redirect resource owners to if consent is required during the authorization request flow.
<1> `authorizationRequestConverter()`: The `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1[OAuth2 authorization request] (or consent) from `HttpServletRequest` to an instance of `OAuth2AuthorizationCodeRequestAuthenticationToken`.
<2> `authenticationProvider()`: The `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2AuthorizationCodeRequestAuthenticationToken`. (One or more may be added to replace the defaults.)
<3> `authorizationResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OAuth2AuthorizationCodeRequestAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2[OAuth2AuthorizationResponse].
<4> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthorizationCodeRequestAuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1[OAuth2Error response].
<5> `consentPage()`: The `URI` of the custom consent page to redirect resource owners to if consent is required during the authorization request flow.
`OAuth2AuthorizationEndpointConfigurer` configures the `OAuth2AuthorizationEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OAuth2AuthorizationEndpointFilter` is the `Filter` that processes OAuth2 authorization requests (and consents).
`OAuth2AuthorizationEndpointFilter` is configured with the following defaults:
* `*AuthenticationConverter*` -- A `DelegatingAuthenticationConverter` composed of `OAuth2AuthorizationCodeRequestAuthenticationConverter` and `OAuth2AuthorizationConsentAuthenticationConverter`.
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OAuth2AuthorizationCodeRequestAuthenticationProvider` and `OAuth2AuthorizationConsentAuthenticationProvider`.
* `*AuthenticationConverter*` -- An `OAuth2AuthorizationCodeRequestAuthenticationConverter`.
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OAuth2AuthorizationCodeRequestAuthenticationProvider`.
* `*AuthenticationSuccessHandler*` -- An internal implementation that handles an "`authenticated`" `OAuth2AuthorizationCodeRequestAuthenticationToken` and returns the `OAuth2AuthorizationResponse`.
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthorizationCodeRequestAuthenticationException` and returns the `OAuth2Error` response.
@@ -69,22 +65,18 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
.tokenEndpoint(tokenEndpoint ->
tokenEndpoint
.accessTokenRequestConverter(accessTokenRequestConverter) <1>
.accessTokenRequestConverters(accessTokenRequestConvertersConsumer) <2>
.authenticationProvider(authenticationProvider) <3>
.authenticationProviders(authenticationProvidersConsumer) <4>
.accessTokenResponseHandler(accessTokenResponseHandler) <5>
.errorResponseHandler(errorResponseHandler) <6>
.authenticationProvider(authenticationProvider) <2>
.accessTokenResponseHandler(accessTokenResponseHandler) <3>
.errorResponseHandler(errorResponseHandler) <4>
);
return http.build();
}
----
<1> `accessTokenRequestConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3[OAuth2 access token request] from `HttpServletRequest` to an instance of `OAuth2AuthorizationGrantAuthenticationToken`.
<2> `accessTokenRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2AuthorizationGrantAuthenticationToken`.
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
<5> `accessTokenResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an `OAuth2AccessTokenAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-5.1[`OAuth2AccessTokenResponse`].
<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-5.2[OAuth2Error response].
<1> `accessTokenRequestConverter()`: The `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3[OAuth2 access token request] from `HttpServletRequest` to an instance of `OAuth2AuthorizationGrantAuthenticationToken`.
<2> `authenticationProvider()`: The `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2AuthorizationGrantAuthenticationToken`. (One or more may be added to replace the defaults.)
<3> `accessTokenResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an `OAuth2AccessTokenAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-5.1[`OAuth2AccessTokenResponse`].
<4> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-5.2[OAuth2Error response].
`OAuth2TokenEndpointConfigurer` configures the `OAuth2TokenEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OAuth2TokenEndpointFilter` is the `Filter` that processes OAuth2 access token requests.
@@ -118,29 +110,25 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
.tokenIntrospectionEndpoint(tokenIntrospectionEndpoint ->
tokenIntrospectionEndpoint
.introspectionRequestConverter(introspectionRequestConverter) <1>
.introspectionRequestConverters(introspectionRequestConvertersConsumer) <2>
.authenticationProvider(authenticationProvider) <3>
.authenticationProviders(authenticationProvidersConsumer) <4>
.introspectionResponseHandler(introspectionResponseHandler) <5>
.errorResponseHandler(errorResponseHandler) <6>
.authenticationProvider(authenticationProvider) <2>
.introspectionResponseHandler(introspectionResponseHandler) <3>
.errorResponseHandler(errorResponseHandler) <4>
);
return http.build();
}
----
<1> `introspectionRequestConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc7662#section-2.1[OAuth2 introspection request] from `HttpServletRequest` to an instance of `OAuth2TokenIntrospectionAuthenticationToken`.
<2> `introspectionRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2TokenIntrospectionAuthenticationToken`.
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
<5> `introspectionResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OAuth2TokenIntrospectionAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc7662#section-2.2[OAuth2TokenIntrospection response].
<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc7662#section-2.3[OAuth2Error response].
<1> `introspectionRequestConverter()`: The `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc7662#section-2.1[OAuth2 introspection request] from `HttpServletRequest` to an instance of `OAuth2TokenIntrospectionAuthenticationToken`.
<2> `authenticationProvider()`: The `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2TokenIntrospectionAuthenticationToken`. (One or more may be added to replace the defaults.)
<3> `introspectionResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OAuth2TokenIntrospectionAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc7662#section-2.2[OAuth2TokenIntrospection response].
<4> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc7662#section-2.3[OAuth2Error response].
`OAuth2TokenIntrospectionEndpointConfigurer` configures the `OAuth2TokenIntrospectionEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OAuth2TokenIntrospectionEndpointFilter` is the `Filter` that processes OAuth2 introspection requests.
`OAuth2TokenIntrospectionEndpointFilter` is configured with the following defaults:
* `*AuthenticationConverter*` -- An `OAuth2TokenIntrospectionAuthenticationConverter`.
* `*AuthenticationConverter*` -- An internal implementation that returns the `OAuth2TokenIntrospectionAuthenticationToken`.
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OAuth2TokenIntrospectionAuthenticationProvider`.
* `*AuthenticationSuccessHandler*` -- An internal implementation that handles an "`authenticated`" `OAuth2TokenIntrospectionAuthenticationToken` and returns the `OAuth2TokenIntrospection` response.
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthenticationException` and returns the `OAuth2Error` response.
@@ -164,30 +152,26 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
authorizationServerConfigurer
.tokenRevocationEndpoint(tokenRevocationEndpoint ->
tokenRevocationEndpoint
.revocationRequestConverter(revocationRequestConverter) <1>
.revocationRequestConverters(revocationRequestConvertersConsumer) <2>
.authenticationProvider(authenticationProvider) <3>
.authenticationProviders(authenticationProvidersConsumer) <4>
.revocationResponseHandler(revocationResponseHandler) <5>
.errorResponseHandler(errorResponseHandler) <6>
.revocationRequestConverter(revocationRequestConverter) <1>
.authenticationProvider(authenticationProvider) <2>
.revocationResponseHandler(revocationResponseHandler) <3>
.errorResponseHandler(errorResponseHandler) <4>
);
return http.build();
}
----
<1> `revocationRequestConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc7009#section-2.1[OAuth2 revocation request] from `HttpServletRequest` to an instance of `OAuth2TokenRevocationAuthenticationToken`.
<2> `revocationRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2TokenRevocationAuthenticationToken`.
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
<5> `revocationResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OAuth2TokenRevocationAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc7009#section-2.2[OAuth2 revocation response].
<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc7009#section-2.2.1[OAuth2Error response].
<1> `revocationRequestConverter()`: The `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc7009#section-2.1[OAuth2 revocation request] from `HttpServletRequest` to an instance of `OAuth2TokenRevocationAuthenticationToken`.
<2> `authenticationProvider()`: The `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2TokenRevocationAuthenticationToken`. (One or more may be added to replace the defaults.)
<3> `revocationResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OAuth2TokenRevocationAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc7009#section-2.2[OAuth2 revocation response].
<4> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc7009#section-2.2.1[OAuth2Error response].
`OAuth2TokenRevocationEndpointConfigurer` configures the `OAuth2TokenRevocationEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OAuth2TokenRevocationEndpointFilter` is the `Filter` that processes OAuth2 revocation requests.
`OAuth2TokenRevocationEndpointFilter` is configured with the following defaults:
* `*AuthenticationConverter*` -- An `OAuth2TokenRevocationAuthenticationConverter`.
* `*AuthenticationConverter*` -- An internal implementation that returns the `OAuth2TokenRevocationAuthenticationToken`.
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OAuth2TokenRevocationAuthenticationProvider`.
* `*AuthenticationSuccessHandler*` -- An internal implementation that handles an "`authenticated`" `OAuth2TokenRevocationAuthenticationToken` and returns the OAuth2 revocation response.
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthenticationException` and returns the `OAuth2Error` response.
@@ -195,31 +179,10 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
[[oauth2-authorization-server-metadata-endpoint]]
== OAuth2 Authorization Server Metadata Endpoint
`OAuth2AuthorizationServerMetadataEndpointConfigurer` provides the ability to customize the https://datatracker.ietf.org/doc/html/rfc8414#section-3[OAuth2 Authorization Server Metadata endpoint].
It defines an extension point that lets you customize the https://datatracker.ietf.org/doc/html/rfc8414#section-3.2[OAuth2 Authorization Server Metadata response].
`OAuth2AuthorizationServerConfigurer` provides support for the https://datatracker.ietf.org/doc/html/rfc8414#section-3[OAuth2 Authorization Server Metadata endpoint].
`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].
`OAuth2AuthorizationServerConfigurer` configures the `OAuth2AuthorizationServerMetadataEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`OAuth2AuthorizationServerMetadataEndpointFilter` is the `Filter` that processes https://datatracker.ietf.org/doc/html/rfc8414#section-3.1[OAuth2 authorization server metadata requests] and returns the https://datatracker.ietf.org/doc/html/rfc8414#section-3.2[OAuth2AuthorizationServerMetadata response].
[[jwk-set-endpoint]]
== JWK Set Endpoint
@@ -235,34 +198,9 @@ The JWK Set endpoint is configured *only* if a `JWKSource<SecurityContext>` `@Be
[[oidc-provider-configuration-endpoint]]
== OpenID Connect 1.0 Provider Configuration Endpoint
`OidcProviderConfigurationEndpointConfigurer` provides the ability to customize the https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[OpenID Connect 1.0 Provider Configuration endpoint].
It defines an extension point that lets you customize the https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse[OpenID Provider Configuration response].
`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 following configuration option:
[source,java]
----
@Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.oidc(oidc ->
oidc
.providerConfigurationEndpoint(providerConfigurationEndpoint ->
providerConfigurationEndpoint
.providerConfigurationCustomizer(providerConfigurationCustomizer) <1>
)
);
return http.build();
}
----
<1> `providerConfigurationCustomizer()`: The `Consumer` providing access to the `OidcProviderConfiguration.Builder` allowing the ability to customize the claims of the OpenID Provider's configuration.
`OidcProviderConfigurationEndpointConfigurer` configures the `OidcProviderConfigurationEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
`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]]

View File

@@ -1,9 +1,9 @@
version=0.4.0-M2
version=1.0.0-M1
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
org.gradle.caching=true
springFrameworkVersion=5.3.23
springSecurityVersion=5.8.0-M3
springFrameworkVersion=6.0.0-M5
springSecurityVersion=6.0.0-M6
springJavaformatVersion=0.0.31
springJavaformatExcludePackages=org/springframework/security/config org/springframework/security/oauth2
checkstyleToolVersion=8.34

View File

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

View File

@@ -29,5 +29,5 @@ dependencies {
testRuntimeOnly "org.hsqldb:hsqldb"
provided "javax.servlet:javax.servlet-api"
provided "jakarta.servlet:jakarta.servlet-api"
}

View File

@@ -274,17 +274,6 @@ 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.
@@ -380,9 +369,6 @@ 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

@@ -141,16 +141,6 @@ 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

@@ -86,12 +86,6 @@ 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

@@ -15,8 +15,6 @@
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import java.time.Instant;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
@@ -109,11 +107,6 @@ public final class ClientSecretAuthenticationProvider implements AuthenticationP
throwInvalidClient(OAuth2ParameterNames.CLIENT_SECRET);
}
if (registeredClient.getClientSecretExpiresAt() != null &&
Instant.now().isAfter(registeredClient.getClientSecretExpiresAt())) {
throwInvalidClient("client_secret_expires_at");
}
// Validate the "code_verifier" parameter for the confidential client, if available
this.codeVerifierAuthenticator.authenticateIfAvailable(clientAuthentication, registeredClient);

View File

@@ -51,9 +51,9 @@ 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.context.AuthorizationServerContext;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.context.ProviderContext;
import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder;
import org.springframework.security.oauth2.server.authorization.settings.ProviderSettings;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@@ -221,20 +221,20 @@ public final class JwtClientAssertionAuthenticationProvider implements Authentic
return new DelegatingOAuth2TokenValidator<>(
new JwtClaimValidator<>(JwtClaimNames.ISS, clientId::equals),
new JwtClaimValidator<>(JwtClaimNames.SUB, clientId::equals),
new JwtClaimValidator<>(JwtClaimNames.AUD, containsAudience()),
new JwtClaimValidator<>(JwtClaimNames.AUD, containsProviderAudience()),
new JwtClaimValidator<>(JwtClaimNames.EXP, Objects::nonNull),
new JwtTimestampValidator()
);
}
private static Predicate<List<String>> containsAudience() {
private static Predicate<List<String>> containsProviderAudience() {
return (audienceClaim) -> {
if (CollectionUtils.isEmpty(audienceClaim)) {
return false;
}
List<String> audienceList = getAudience();
List<String> providerAudience = getProviderAudience();
for (String audience : audienceClaim) {
if (audienceList.contains(audience)) {
if (providerAudience.contains(audience)) {
return true;
}
}
@@ -242,19 +242,19 @@ public final class JwtClientAssertionAuthenticationProvider implements Authentic
};
}
private static List<String> getAudience() {
AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext();
if (!StringUtils.hasText(authorizationServerContext.getIssuer())) {
private static List<String> getProviderAudience() {
ProviderContext providerContext = ProviderContextHolder.getProviderContext();
if (!StringUtils.hasText(providerContext.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;
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) {

View File

@@ -15,24 +15,54 @@
*/
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.authentication.AuthenticationProvider;
import org.springframework.lang.Nullable;
import org.springframework.security.core.Authentication;
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
* and is used in an {@link AuthenticationProvider}.
* A context that holds an {@link Authentication} and (optionally) additional information.
*
* @author Joe Grandja
* @since 0.2.0
* @see Context
*/
public interface OAuth2AuthenticationContext extends Context {
public class OAuth2AuthenticationContext implements Context {
private final Map<Object, Object> context;
/**
* Constructs an {@code OAuth2AuthenticationContext} using the provided parameters.
*
* @param authentication the {@code Authentication}
* @param context a {@code Map} of additional context information
*/
public OAuth2AuthenticationContext(Authentication authentication, @Nullable Map<Object, Object> context) {
Assert.notNull(authentication, "authentication cannot be null");
Map<Object, Object> ctx = new HashMap<>();
if (!CollectionUtils.isEmpty(context)) {
ctx.putAll(context);
}
ctx.put(Authentication.class, authentication);
this.context = Collections.unmodifiableMap(ctx);
}
/**
* Constructs an {@code OAuth2AuthenticationContext} using the provided parameters.
*
* @param context a {@code Map} of context information, must contain the {@code Authentication}
* @since 0.2.1
*/
public OAuth2AuthenticationContext(Map<Object, Object> context) {
Assert.notEmpty(context, "context cannot be empty");
Assert.notNull(context.get(Authentication.class), "authentication cannot be null");
this.context = Collections.unmodifiableMap(new HashMap<>(context));
}
/**
* Returns the {@link Authentication} associated to the context.
@@ -41,10 +71,23 @@ public interface OAuth2AuthenticationContext extends Context {
* @return the {@link Authentication}
*/
@SuppressWarnings("unchecked")
default <T extends Authentication> T getAuthentication() {
public <T extends Authentication> T getAuthentication() {
return (T) get(Authentication.class);
}
@SuppressWarnings("unchecked")
@Nullable
@Override
public <V> V get(Object key) {
return hasKey(key) ? (V) this.context.get(key) : null;
}
@Override
public boolean hasKey(Object key) {
Assert.notNull(key, "key cannot be null");
return this.context.containsKey(key);
}
/**
* A builder for subclasses of {@link OAuth2AuthenticationContext}.
*
@@ -52,7 +95,7 @@ public interface OAuth2AuthenticationContext extends Context {
* @param <B> the type of the builder
* @since 0.2.1
*/
abstract class AbstractBuilder<T extends OAuth2AuthenticationContext, B extends AbstractBuilder<T, B>> {
protected static abstract class AbstractBuilder<T extends OAuth2AuthenticationContext, B extends AbstractBuilder<T, B>> {
private final Map<Object, Object> context = new HashMap<>();
protected AbstractBuilder(Authentication authentication) {

View File

@@ -0,0 +1,40 @@
/*
* 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 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

@@ -43,7 +43,7 @@ import org.springframework.security.oauth2.server.authorization.OAuth2Authorizat
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.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder;
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;
@@ -132,7 +132,7 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
.registeredClient(registeredClient)
.principal(authorization.getAttribute(Principal.class.getName()))
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
.providerContext(ProviderContextHolder.getProviderContext())
.authorization(authorization)
.authorizedScopes(authorization.getAuthorizedScopes())
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)

View File

@@ -1,55 +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.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

@@ -1,107 +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.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,14 +16,22 @@
package org.springframework.security.oauth2.server.authorization.authentication;
import java.security.Principal;
import java.time.Instant;
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.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;
@@ -41,24 +49,24 @@ import org.springframework.security.oauth2.server.authorization.OAuth2Authorizat
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.context.ProviderContextHolder;
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
* An {@link AuthenticationProvider} implementation for the OAuth 2.0 Authorization Request (and Consent)
* 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
@@ -67,14 +75,17 @@ import org.springframework.util.StringUtils;
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 RegisteredClientRepository registeredClientRepository;
private final OAuth2AuthorizationService authorizationService;
private final OAuth2AuthorizationConsentService authorizationConsentService;
private OAuth2TokenGenerator<OAuth2AuthorizationCode> authorizationCodeGenerator = new OAuth2AuthorizationCodeGenerator();
private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator =
new OAuth2AuthorizationCodeRequestAuthenticationValidator();
private Function<String, OAuth2AuthenticationValidator> authenticationValidatorResolver = DEFAULT_AUTHENTICATION_VALIDATOR_RESOLVER;
private Consumer<OAuth2AuthorizationConsentAuthenticationContext> authorizationConsentCustomizer;
/**
* Constructs an {@code OAuth2AuthorizationCodeRequestAuthenticationProvider} using the provided parameters.
@@ -98,6 +109,76 @@ 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) {
@@ -105,17 +186,22 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
authorizationCodeRequestAuthentication, null);
}
OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext =
OAuth2AuthorizationCodeRequestAuthenticationContext.with(authorizationCodeRequestAuthentication)
.registeredClient(registeredClient)
.build();
this.authenticationValidator.accept(authenticationContext);
Map<Object, Object> context = new HashMap<>();
context.put(RegisteredClient.class, registeredClient);
OAuth2AuthenticationContext authenticationContext = new OAuth2AuthenticationContext(
authorizationCodeRequestAuthentication, context);
OAuth2AuthenticationValidator redirectUriValidator = resolveAuthenticationValidator(OAuth2ParameterNames.REDIRECT_URI);
redirectUriValidator.validate(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)) {
@@ -161,8 +247,12 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
Set<String> currentAuthorizedScopes = currentAuthorizationConsent != null ?
currentAuthorizationConsent.getScopes() : null;
return new OAuth2AuthorizationConsentAuthenticationToken(authorizationRequest.getAuthorizationUri(),
registeredClient.getClientId(), principal, state, currentAuthorizedScopes, null);
return OAuth2AuthorizationCodeRequestAuthenticationToken.with(registeredClient.getClientId(), principal)
.authorizationUri(authorizationRequest.getAuthorizationUri())
.scopes(currentAuthorizedScopes)
.state(state)
.consentRequired(true)
.build();
}
OAuth2TokenContext tokenContext = createAuthorizationCodeTokenContext(
@@ -185,42 +275,150 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
redirectUri = registeredClient.getRedirectUris().iterator().next();
}
return new OAuth2AuthorizationCodeRequestAuthenticationToken(authorizationRequest.getAuthorizationUri(),
registeredClient.getClientId(), principal, authorizationCode, redirectUri,
authorizationRequest.getState(), authorizationRequest.getScopes());
return OAuth2AuthorizationCodeRequestAuthenticationToken.with(registeredClient.getClientId(), principal)
.authorizationUri(authorizationRequest.getAuthorizationUri())
.redirectUri(redirectUri)
.scopes(authorizationRequest.getScopes())
.state(authorizationRequest.getState())
.authorizationCode(authorizationCode)
.build();
}
@Override
public boolean supports(Class<?> authentication) {
return OAuth2AuthorizationCodeRequestAuthenticationToken.class.isAssignableFrom(authentication);
private OAuth2AuthenticationValidator resolveAuthenticationValidator(String parameterName) {
OAuth2AuthenticationValidator authenticationValidator = this.authenticationValidatorResolver.apply(parameterName);
return authenticationValidator != null ?
authenticationValidator :
DEFAULT_AUTHENTICATION_VALIDATOR_RESOLVER.apply(parameterName);
}
/**
* 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;
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)
.authorizedScopes(authorizedScopes)
.token(authorizationCode)
.attributes(attrs -> {
attrs.remove(OAuth2ParameterNames.STATE);
})
.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();
}
/**
* 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 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;
}
private static OAuth2Authorization.Builder authorizationBuilder(RegisteredClient registeredClient, Authentication principal,
@@ -240,7 +438,7 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
.registeredClient(registeredClient)
.principal((Authentication) authorizationCodeRequestAuthentication.getPrincipal())
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
.providerContext(ProviderContextHolder.getProviderContext())
.tokenType(new OAuth2TokenType(OAuth2ParameterNames.CODE))
.authorizedScopes(authorizedScopes)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
@@ -283,7 +481,14 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
private static void throwError(String errorCode, String parameterName,
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication,
RegisteredClient registeredClient) {
throwError(errorCode, parameterName, ERROR_URI, authorizationCodeRequestAuthentication, registeredClient, null);
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);
}
private static void throwError(String errorCode, String parameterName, String errorUri,
@@ -297,19 +502,31 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication,
RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest) {
String redirectUri = resolveRedirectUri(authorizationRequest, registeredClient);
boolean redirectOnError = true;
if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_REQUEST) &&
(parameterName.equals(OAuth2ParameterNames.CLIENT_ID) ||
parameterName.equals(OAuth2ParameterNames.REDIRECT_URI) ||
parameterName.equals(OAuth2ParameterNames.STATE))) {
redirectUri = null; // Prevent redirects
redirectOnError = false;
}
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
authorizationCodeRequestAuthentication.getAuthorizationUri(), authorizationCodeRequestAuthentication.getClientId(),
(Authentication) authorizationCodeRequestAuthentication.getPrincipal(), redirectUri,
authorizationCodeRequestAuthentication.getState(), authorizationCodeRequestAuthentication.getScopes(),
authorizationCodeRequestAuthentication.getAdditionalParameters());
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());
}
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, authorizationCodeRequestAuthenticationResult);
}
@@ -324,4 +541,152 @@ 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(context.getRegisteredClient().getTokenSettings().getAuthorizationCodeTimeToLive());
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

@@ -15,104 +15,45 @@
*/
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.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
* An {@link Authentication} implementation for the OAuth 2.0 Authorization Request (and Consent)
* used in the Authorization Code Grant.
*
* @author Joe Grandja
* @since 0.1.2
* @see OAuth2AuthorizationCodeRequestAuthenticationProvider
* @see OAuth2AuthorizationConsentAuthenticationProvider
*/
public class OAuth2AuthorizationCodeRequestAuthenticationToken extends AbstractAuthenticationToken {
public final 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 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;
/**
* 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) {
private OAuth2AuthorizationCodeRequestAuthenticationToken() {
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
@@ -153,6 +94,15 @@ public class OAuth2AuthorizationCodeRequestAuthenticationToken extends AbstractA
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.
*
@@ -163,24 +113,34 @@ public class OAuth2AuthorizationCodeRequestAuthenticationToken extends AbstractA
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
* @return the additional parameters
*/
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}.
*
@@ -191,4 +151,170 @@ public class OAuth2AuthorizationCodeRequestAuthenticationToken extends AbstractA
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 = SpringAuthorizationServerVersion.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

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

@@ -15,12 +15,8 @@
*/
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.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
@@ -36,26 +32,11 @@ import org.springframework.util.Assert;
* @since 0.2.1
* @see OAuth2AuthenticationContext
* @see OAuth2AuthorizationConsent
* @see OAuth2AuthorizationConsentAuthenticationProvider#setAuthorizationConsentCustomizer(Consumer)
*/
public final class OAuth2AuthorizationConsentAuthenticationContext implements OAuth2AuthenticationContext {
private final Map<Object, Object> context;
public final class OAuth2AuthorizationConsentAuthenticationContext extends OAuth2AuthenticationContext {
private OAuth2AuthorizationConsentAuthenticationContext(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);
super(context);
}
/**
@@ -95,12 +76,12 @@ public final class OAuth2AuthorizationConsentAuthenticationContext implements OA
}
/**
* Constructs a new {@link Builder} with the provided {@link OAuth2AuthorizationConsentAuthenticationToken}.
* Constructs a new {@link Builder} with the provided {@link OAuth2AuthorizationCodeRequestAuthenticationToken}.
*
* @param authentication the {@link OAuth2AuthorizationConsentAuthenticationToken}
* @param authentication the {@link OAuth2AuthorizationCodeRequestAuthenticationToken}
* @return the {@link Builder}
*/
public static Builder with(OAuth2AuthorizationConsentAuthenticationToken authentication) {
public static Builder with(OAuth2AuthorizationCodeRequestAuthenticationToken authentication) {
return new Builder(authentication);
}
@@ -109,7 +90,7 @@ public final class OAuth2AuthorizationConsentAuthenticationContext implements OA
*/
public static final class Builder extends AbstractBuilder<OAuth2AuthorizationConsentAuthenticationContext, Builder> {
private Builder(OAuth2AuthorizationConsentAuthenticationToken authentication) {
private Builder(OAuth2AuthorizationCodeRequestAuthenticationToken authentication) {
super(authentication);
}

View File

@@ -1,316 +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.authentication;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
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 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);
}
// 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);
}
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);
}
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(authorizationConsentAuthentication)
.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,
authorizationConsentAuthentication, registeredClient, authorizationRequest);
}
OAuth2AuthorizationConsent authorizationConsent = authorizationConsentBuilder.build();
if (!authorizationConsent.equals(currentAuthorizationConsent)) {
this.authorizationConsentService.save(authorizationConsent);
}
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);
}
OAuth2Authorization updatedAuthorization = OAuth2Authorization.from(authorization)
.authorizedScopes(authorizedScopes)
.token(authorizationCode)
.attributes(attrs -> {
attrs.remove(OAuth2ParameterNames.STATE);
})
.build();
this.authorizationService.save(updatedAuthorization);
String redirectUri = authorizationRequest.getRedirectUri();
if (!StringUtils.hasText(redirectUri)) {
redirectUri = registeredClient.getRedirectUris().iterator().next();
}
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

@@ -1,135 +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.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

@@ -33,7 +33,7 @@ import org.springframework.security.oauth2.server.authorization.OAuth2Authorizat
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.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder;
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;
@@ -102,7 +102,7 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth
OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder()
.registeredClient(registeredClient)
.principal(clientPrincipal)
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
.providerContext(ProviderContextHolder.getProviderContext())
.authorizedScopes(authorizedScopes)
.tokenType(OAuth2TokenType.ACCESS_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)

View File

@@ -40,7 +40,7 @@ import org.springframework.security.oauth2.server.authorization.OAuth2Authorizat
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.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder;
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;
@@ -130,7 +130,7 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
.registeredClient(registeredClient)
.principal(authorization.getAttribute(Principal.class.getName()))
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
.providerContext(ProviderContextHolder.getProviderContext())
.authorization(authorization)
.authorizedScopes(scopes)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)

View File

@@ -34,7 +34,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
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.ProviderSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -90,7 +90,7 @@ public class OAuth2AuthorizationServerConfiguration {
@Bean
RegisterMissingBeanPostProcessor registerMissingBeanPostProcessor() {
RegisterMissingBeanPostProcessor postProcessor = new RegisterMissingBeanPostProcessor();
postProcessor.addBeanDefinition(AuthorizationServerSettings.class, () -> AuthorizationServerSettings.builder().build());
postProcessor.addBeanDefinition(ProviderSettings.class, () -> ProviderSettings.builder().build());
return postProcessor;
}

View File

@@ -1,105 +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.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

@@ -17,9 +17,8 @@ package org.springframework.security.oauth2.server.authorization.config.annotati
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
@@ -31,13 +30,8 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResp
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.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.settings.ProviderSettings;
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;
@@ -58,10 +52,8 @@ import org.springframework.util.StringUtils;
*/
public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2Configurer {
private RequestMatcher requestMatcher;
private final List<AuthenticationConverter> authorizationRequestConverters = new ArrayList<>();
private Consumer<List<AuthenticationConverter>> authorizationRequestConvertersConsumer = (authorizationRequestConverters) -> {};
private AuthenticationConverter authorizationRequestConverter;
private final List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
private Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer = (authenticationProviders) -> {};
private AuthenticationSuccessHandler authorizationResponseHandler;
private AuthenticationFailureHandler errorResponseHandler;
private String consentPage;
@@ -74,32 +66,14 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
}
/**
* 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.
* Sets the {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
* to an instance of {@link OAuth2AuthorizationCodeRequestAuthenticationToken} used for authenticating the request.
*
* @param authorizationRequestConverter an {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
* @param authorizationRequestConverter the {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
* @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration
*/
public OAuth2AuthorizationEndpointConfigurer authorizationRequestConverter(AuthenticationConverter authorizationRequestConverter) {
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;
this.authorizationRequestConverter = authorizationRequestConverter;
return this;
}
@@ -115,22 +89,6 @@ 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}.
@@ -174,7 +132,7 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
*
* <ul>
* <li>It must be an HTTP POST</li>
* <li>It must be submitted to {@link AuthorizationServerSettings#getAuthorizationEndpoint()}</li>
* <li>It must be submitted to {@link ProviderSettings#getAuthorizationEndpoint()} ()}</li>
* <li>It must include the received {@code client_id} as an HTTP parameter</li>
* <li>It must include the received {@code state} as an HTTP parameter</li>
* <li>It must include the list of {@code scope}s the {@code Resource Owner}
@@ -191,20 +149,19 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
@Override
void init(HttpSecurity httpSecurity) {
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(httpSecurity);
this.requestMatcher = new OrRequestMatcher(
new AntPathRequestMatcher(
authorizationServerSettings.getAuthorizationEndpoint(),
providerSettings.getAuthorizationEndpoint(),
HttpMethod.GET.name()),
new AntPathRequestMatcher(
authorizationServerSettings.getAuthorizationEndpoint(),
providerSettings.getAuthorizationEndpoint(),
HttpMethod.POST.name()));
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
if (!this.authenticationProviders.isEmpty()) {
authenticationProviders.addAll(0, this.authenticationProviders);
}
this.authenticationProvidersConsumer.accept(authenticationProviders);
List<AuthenticationProvider> authenticationProviders =
!this.authenticationProviders.isEmpty() ?
this.authenticationProviders :
createDefaultAuthenticationProviders(httpSecurity);
authenticationProviders.forEach(authenticationProvider ->
httpSecurity.authenticationProvider(postProcess(authenticationProvider)));
}
@@ -212,19 +169,15 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
@Override
void configure(HttpSecurity httpSecurity) {
AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class);
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(httpSecurity);
OAuth2AuthorizationEndpointFilter authorizationEndpointFilter =
new OAuth2AuthorizationEndpointFilter(
authenticationManager,
authorizationServerSettings.getAuthorizationEndpoint());
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
if (!this.authorizationRequestConverters.isEmpty()) {
authenticationConverters.addAll(0, this.authorizationRequestConverters);
providerSettings.getAuthorizationEndpoint());
if (this.authorizationRequestConverter != null) {
authorizationEndpointFilter.setAuthenticationConverter(this.authorizationRequestConverter);
}
this.authorizationRequestConvertersConsumer.accept(authenticationConverters);
authorizationEndpointFilter.setAuthenticationConverter(
new DelegatingAuthenticationConverter(authenticationConverters));
if (this.authorizationResponseHandler != null) {
authorizationEndpointFilter.setAuthenticationSuccessHandler(this.authorizationResponseHandler);
}
@@ -242,16 +195,7 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
return this.requestMatcher;
}
private static List<AuthenticationConverter> createDefaultAuthenticationConverters() {
List<AuthenticationConverter> authenticationConverters = new ArrayList<>();
authenticationConverters.add(new OAuth2AuthorizationCodeRequestAuthenticationConverter());
authenticationConverters.add(new OAuth2AuthorizationConsentAuthenticationConverter());
return authenticationConverters;
}
private static List<AuthenticationProvider> createDefaultAuthenticationProviders(HttpSecurity httpSecurity) {
private List<AuthenticationProvider> createDefaultAuthenticationProviders(HttpSecurity httpSecurity) {
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
OAuth2AuthorizationCodeRequestAuthenticationProvider authorizationCodeRequestAuthenticationProvider =
@@ -261,13 +205,6 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
OAuth2ConfigurerUtils.getAuthorizationConsentService(httpSecurity));
authenticationProviders.add(authorizationCodeRequestAuthenticationProvider);
OAuth2AuthorizationConsentAuthenticationProvider authorizationConsentAuthenticationProvider =
new OAuth2AuthorizationConsentAuthenticationProvider(
OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity),
OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity),
OAuth2ConfigurerUtils.getAuthorizationConsentService(httpSecurity));
authenticationProviders.add(authorizationConsentAuthenticationProvider);
return authenticationProviders;
}

View File

@@ -31,9 +31,11 @@ import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.ProviderContextFilter;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.context.SecurityContextHolderFilter;
@@ -53,7 +55,6 @@ import org.springframework.util.Assert;
* @since 0.0.1
* @see AbstractHttpConfigurer
* @see OAuth2ClientAuthenticationConfigurer
* @see OAuth2AuthorizationServerMetadataEndpointConfigurer
* @see OAuth2AuthorizationEndpointConfigurer
* @see OAuth2TokenEndpointConfigurer
* @see OAuth2TokenIntrospectionEndpointConfigurer
@@ -63,20 +64,22 @@ import org.springframework.util.Assert;
* @see OAuth2AuthorizationService
* @see OAuth2AuthorizationConsentService
* @see NimbusJwkSetEndpointFilter
* @see OAuth2AuthorizationServerMetadataEndpointFilter
*/
public final class OAuth2AuthorizationServerConfigurer
extends AbstractHttpConfigurer<OAuth2AuthorizationServerConfigurer, HttpSecurity> {
private final Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = createConfigurers();
private final RequestMatcher endpointsMatcher = (request) ->
getRequestMatcher(OAuth2AuthorizationServerMetadataEndpointConfigurer.class).matches(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);
private RequestMatcher jwkSetEndpointMatcher;
private RequestMatcher authorizationServerMetadataEndpointMatcher;
private final RequestMatcher endpointsMatcher = (request) ->
getRequestMatcher(OAuth2AuthorizationEndpointConfigurer.class).matches(request) ||
getRequestMatcher(OAuth2TokenEndpointConfigurer.class).matches(request) ||
getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class).matches(request) ||
getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class).matches(request) ||
getRequestMatcher(OidcConfigurer.class).matches(request) ||
this.jwkSetEndpointMatcher.matches(request) ||
this.authorizationServerMetadataEndpointMatcher.matches(request);
/**
* Sets the repository of registered clients.
@@ -115,14 +118,14 @@ public final class OAuth2AuthorizationServerConfigurer
}
/**
* Sets the authorization server settings.
* Sets the provider settings.
*
* @param authorizationServerSettings the authorization server settings
* @param providerSettings the provider settings
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
*/
public OAuth2AuthorizationServerConfigurer authorizationServerSettings(AuthorizationServerSettings authorizationServerSettings) {
Assert.notNull(authorizationServerSettings, "authorizationServerSettings cannot be null");
getBuilder().setSharedObject(AuthorizationServerSettings.class, authorizationServerSettings);
public OAuth2AuthorizationServerConfigurer providerSettings(ProviderSettings providerSettings) {
Assert.notNull(providerSettings, "providerSettings cannot be null");
getBuilder().setSharedObject(ProviderSettings.class, providerSettings);
return this;
}
@@ -150,18 +153,6 @@ public final class OAuth2AuthorizationServerConfigurer
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.
*
@@ -230,11 +221,9 @@ public final class OAuth2AuthorizationServerConfigurer
@Override
public void init(HttpSecurity httpSecurity) {
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
validateAuthorizationServerSettings(authorizationServerSettings);
this.jwkSetEndpointMatcher = new AntPathRequestMatcher(
authorizationServerSettings.getJwkSetEndpoint(), HttpMethod.GET.name());
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(httpSecurity);
validateProviderSettings(providerSettings);
initEndpointMatchers(providerSettings);
this.configurers.values().forEach(configurer -> configurer.init(httpSecurity));
@@ -254,23 +243,26 @@ public final class OAuth2AuthorizationServerConfigurer
public void configure(HttpSecurity httpSecurity) {
this.configurers.values().forEach(configurer -> configurer.configure(httpSecurity));
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(httpSecurity);
AuthorizationServerContextFilter authorizationServerContextFilter = new AuthorizationServerContextFilter(authorizationServerSettings);
httpSecurity.addFilterAfter(postProcess(authorizationServerContextFilter), SecurityContextHolderFilter.class);
ProviderContextFilter providerContextFilter = new ProviderContextFilter(providerSettings);
httpSecurity.addFilterAfter(postProcess(providerContextFilter), SecurityContextHolderFilter.class);
JWKSource<com.nimbusds.jose.proc.SecurityContext> jwkSource = OAuth2ConfigurerUtils.getJwkSource(httpSecurity);
if (jwkSource != null) {
NimbusJwkSetEndpointFilter jwkSetEndpointFilter = new NimbusJwkSetEndpointFilter(
jwkSource, authorizationServerSettings.getJwkSetEndpoint());
jwkSource, providerSettings.getJwkSetEndpoint());
httpSecurity.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
}
OAuth2AuthorizationServerMetadataEndpointFilter authorizationServerMetadataEndpointFilter =
new OAuth2AuthorizationServerMetadataEndpointFilter(providerSettings);
httpSecurity.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));
@@ -288,11 +280,18 @@ public final class OAuth2AuthorizationServerConfigurer
return getConfigurer(configurerType).getRequestMatcher();
}
private static void validateAuthorizationServerSettings(AuthorizationServerSettings authorizationServerSettings) {
if (authorizationServerSettings.getIssuer() != 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) {
URI issuerUri;
try {
issuerUri = new URI(authorizationServerSettings.getIssuer());
issuerUri = new URI(providerSettings.getIssuer());
issuerUri.toURL();
} catch (Exception ex) {
throw new IllegalArgumentException("issuer must be a valid URL", ex);

View File

@@ -1,108 +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.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

@@ -17,9 +17,8 @@ package org.springframework.security.oauth2.server.authorization.config.annotati
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
@@ -35,13 +34,8 @@ 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.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ProviderSettings;
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;
@@ -61,10 +55,8 @@ import org.springframework.util.Assert;
*/
public final class OAuth2ClientAuthenticationConfigurer extends AbstractOAuth2Configurer {
private RequestMatcher requestMatcher;
private final List<AuthenticationConverter> authenticationConverters = new ArrayList<>();
private Consumer<List<AuthenticationConverter>> authenticationConvertersConsumer = (authenticationConverters) -> {};
private AuthenticationConverter authenticationConverter;
private final List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
private Consumer<List<AuthenticationProvider>> authenticationProvidersConsumer = (authenticationProviders) -> {};
private AuthenticationSuccessHandler authenticationSuccessHandler;
private AuthenticationFailureHandler errorResponseHandler;
@@ -76,31 +68,14 @@ public final class OAuth2ClientAuthenticationConfigurer extends AbstractOAuth2Co
}
/**
* Adds an {@link AuthenticationConverter} used when attempting to extract client credentials from {@link HttpServletRequest}
* Sets the {@link AuthenticationConverter} used when attempting to extract client credentials from {@link HttpServletRequest}
* to an instance of {@link OAuth2ClientAuthenticationToken} used for authenticating the client.
*
* @param authenticationConverter an {@link AuthenticationConverter} used when attempting to extract client credentials from {@link HttpServletRequest}
* @param authenticationConverter the {@link AuthenticationConverter} used when attempting to extract client credentials from {@link HttpServletRequest}
* @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration
*/
public OAuth2ClientAuthenticationConfigurer authenticationConverter(AuthenticationConverter authenticationConverter) {
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;
this.authenticationConverter = authenticationConverter;
return this;
}
@@ -116,22 +91,6 @@ 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}.
@@ -158,23 +117,22 @@ public final class OAuth2ClientAuthenticationConfigurer extends AbstractOAuth2Co
@Override
void init(HttpSecurity httpSecurity) {
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(httpSecurity);
this.requestMatcher = new OrRequestMatcher(
new AntPathRequestMatcher(
authorizationServerSettings.getTokenEndpoint(),
providerSettings.getTokenEndpoint(),
HttpMethod.POST.name()),
new AntPathRequestMatcher(
authorizationServerSettings.getTokenIntrospectionEndpoint(),
providerSettings.getTokenIntrospectionEndpoint(),
HttpMethod.POST.name()),
new AntPathRequestMatcher(
authorizationServerSettings.getTokenRevocationEndpoint(),
providerSettings.getTokenRevocationEndpoint(),
HttpMethod.POST.name()));
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
if (!this.authenticationProviders.isEmpty()) {
authenticationProviders.addAll(0, this.authenticationProviders);
}
this.authenticationProvidersConsumer.accept(authenticationProviders);
List<AuthenticationProvider> authenticationProviders =
!this.authenticationProviders.isEmpty() ?
this.authenticationProviders :
createDefaultAuthenticationProviders(httpSecurity);
authenticationProviders.forEach(authenticationProvider ->
httpSecurity.authenticationProvider(postProcess(authenticationProvider)));
}
@@ -184,13 +142,9 @@ public final class OAuth2ClientAuthenticationConfigurer extends AbstractOAuth2Co
AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class);
OAuth2ClientAuthenticationFilter clientAuthenticationFilter = new OAuth2ClientAuthenticationFilter(
authenticationManager, this.requestMatcher);
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
if (!this.authenticationConverters.isEmpty()) {
authenticationConverters.addAll(0, this.authenticationConverters);
if (this.authenticationConverter != null) {
clientAuthenticationFilter.setAuthenticationConverter(this.authenticationConverter);
}
this.authenticationConvertersConsumer.accept(authenticationConverters);
clientAuthenticationFilter.setAuthenticationConverter(
new DelegatingAuthenticationConverter(authenticationConverters));
if (this.authenticationSuccessHandler != null) {
clientAuthenticationFilter.setAuthenticationSuccessHandler(this.authenticationSuccessHandler);
}
@@ -205,18 +159,7 @@ public final class OAuth2ClientAuthenticationConfigurer extends AbstractOAuth2Co
return this.requestMatcher;
}
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) {
private List<AuthenticationProvider> createDefaultAuthenticationProviders(HttpSecurity httpSecurity) {
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
RegisteredClientRepository registeredClientRepository = OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity);

View File

@@ -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.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.JwtGenerator;
@@ -171,13 +171,13 @@ final class OAuth2ConfigurerUtils {
return getOptionalBean(httpSecurity, type);
}
static AuthorizationServerSettings getAuthorizationServerSettings(HttpSecurity httpSecurity) {
AuthorizationServerSettings authorizationServerSettings = httpSecurity.getSharedObject(AuthorizationServerSettings.class);
if (authorizationServerSettings == null) {
authorizationServerSettings = getBean(httpSecurity, AuthorizationServerSettings.class);
httpSecurity.setSharedObject(AuthorizationServerSettings.class, authorizationServerSettings);
static ProviderSettings getProviderSettings(HttpSecurity httpSecurity) {
ProviderSettings providerSettings = httpSecurity.getSharedObject(ProviderSettings.class);
if (providerSettings == null) {
providerSettings = getBean(httpSecurity, ProviderSettings.class);
httpSecurity.setSharedObject(ProviderSettings.class, providerSettings);
}
return authorizationServerSettings;
return providerSettings;
}
static <T> T getBean(HttpSecurity httpSecurity, Class<T> type) {

View File

@@ -16,10 +16,10 @@
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;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
@@ -36,13 +36,9 @@ import org.springframework.security.oauth2.server.authorization.authentication.O
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.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ProviderSettings;
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;
@@ -61,10 +57,8 @@ import org.springframework.util.Assert;
*/
public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configurer {
private RequestMatcher requestMatcher;
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 AuthenticationConverter accessTokenRequestConverter;
private final List<AuthenticationProvider> authenticationProviders = new LinkedList<>();
private AuthenticationSuccessHandler accessTokenResponseHandler;
private AuthenticationFailureHandler errorResponseHandler;
@@ -76,31 +70,14 @@ public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configure
}
/**
* Adds an {@link AuthenticationConverter} used when attempting to extract an Access Token Request from {@link HttpServletRequest}
* Sets the {@link AuthenticationConverter} used when attempting to extract an Access Token Request from {@link HttpServletRequest}
* to an instance of {@link OAuth2AuthorizationGrantAuthenticationToken} used for authenticating the authorization grant.
*
* @param accessTokenRequestConverter an {@link AuthenticationConverter} used when attempting to extract an Access Token Request from {@link HttpServletRequest}
* @param accessTokenRequestConverter the {@link AuthenticationConverter} used when attempting to extract an Access Token Request from {@link HttpServletRequest}
* @return the {@link OAuth2TokenEndpointConfigurer} for further configuration
*/
public OAuth2TokenEndpointConfigurer accessTokenRequestConverter(AuthenticationConverter accessTokenRequestConverter) {
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;
this.accessTokenRequestConverter = accessTokenRequestConverter;
return this;
}
@@ -116,22 +93,6 @@ 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}.
@@ -158,15 +119,14 @@ public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configure
@Override
void init(HttpSecurity httpSecurity) {
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(httpSecurity);
this.requestMatcher = new AntPathRequestMatcher(
authorizationServerSettings.getTokenEndpoint(), HttpMethod.POST.name());
providerSettings.getTokenEndpoint(), HttpMethod.POST.name());
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
if (!this.authenticationProviders.isEmpty()) {
authenticationProviders.addAll(0, this.authenticationProviders);
}
this.authenticationProvidersConsumer.accept(authenticationProviders);
List<AuthenticationProvider> authenticationProviders =
!this.authenticationProviders.isEmpty() ?
this.authenticationProviders :
createDefaultAuthenticationProviders(httpSecurity);
authenticationProviders.forEach(authenticationProvider ->
httpSecurity.authenticationProvider(postProcess(authenticationProvider)));
}
@@ -174,19 +134,15 @@ public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configure
@Override
void configure(HttpSecurity httpSecurity) {
AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class);
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(httpSecurity);
OAuth2TokenEndpointFilter tokenEndpointFilter =
new OAuth2TokenEndpointFilter(
authenticationManager,
authorizationServerSettings.getTokenEndpoint());
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
if (!this.accessTokenRequestConverters.isEmpty()) {
authenticationConverters.addAll(0, this.accessTokenRequestConverters);
providerSettings.getTokenEndpoint());
if (this.accessTokenRequestConverter != null) {
tokenEndpointFilter.setAuthenticationConverter(this.accessTokenRequestConverter);
}
this.accessTokenRequestConvertersConsumer.accept(authenticationConverters);
tokenEndpointFilter.setAuthenticationConverter(
new DelegatingAuthenticationConverter(authenticationConverters));
if (this.accessTokenResponseHandler != null) {
tokenEndpointFilter.setAuthenticationSuccessHandler(this.accessTokenResponseHandler);
}
@@ -201,17 +157,7 @@ public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configure
return this.requestMatcher;
}
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) {
private List<AuthenticationProvider> createDefaultAuthenticationProviders(HttpSecurity httpSecurity) {
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
OAuth2AuthorizationService authorizationService = OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity);

View File

@@ -16,10 +16,10 @@
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;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
@@ -31,10 +31,8 @@ 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.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ProviderSettings;
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;
@@ -47,17 +45,14 @@ 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 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 AuthenticationConverter introspectionRequestConverter;
private final List<AuthenticationProvider> authenticationProviders = new LinkedList<>();
private AuthenticationSuccessHandler introspectionResponseHandler;
private AuthenticationFailureHandler errorResponseHandler;
@@ -69,31 +64,14 @@ public final class OAuth2TokenIntrospectionEndpointConfigurer extends AbstractOA
}
/**
* Adds an {@link AuthenticationConverter} used when attempting to extract an Introspection Request from {@link HttpServletRequest}
* Sets the {@link AuthenticationConverter} used when attempting to extract an Introspection Request from {@link HttpServletRequest}
* to an instance of {@link OAuth2TokenIntrospectionAuthenticationToken} used for authenticating the request.
*
* @param introspectionRequestConverter an {@link AuthenticationConverter} used when attempting to extract an Introspection Request from {@link HttpServletRequest}
* @param introspectionRequestConverter the {@link AuthenticationConverter} used when attempting to extract an Introspection Request from {@link HttpServletRequest}
* @return the {@link OAuth2TokenIntrospectionEndpointConfigurer} for further configuration
*/
public OAuth2TokenIntrospectionEndpointConfigurer introspectionRequestConverter(AuthenticationConverter introspectionRequestConverter) {
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;
this.introspectionRequestConverter = introspectionRequestConverter;
return this;
}
@@ -109,22 +87,6 @@ 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}.
*
@@ -150,15 +112,14 @@ public final class OAuth2TokenIntrospectionEndpointConfigurer extends AbstractOA
@Override
void init(HttpSecurity httpSecurity) {
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(httpSecurity);
this.requestMatcher = new AntPathRequestMatcher(
authorizationServerSettings.getTokenIntrospectionEndpoint(), HttpMethod.POST.name());
providerSettings.getTokenIntrospectionEndpoint(), HttpMethod.POST.name());
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
if (!this.authenticationProviders.isEmpty()) {
authenticationProviders.addAll(0, this.authenticationProviders);
}
this.authenticationProvidersConsumer.accept(authenticationProviders);
List<AuthenticationProvider> authenticationProviders =
!this.authenticationProviders.isEmpty() ?
this.authenticationProviders :
createDefaultAuthenticationProviders(httpSecurity);
authenticationProviders.forEach(authenticationProvider ->
httpSecurity.authenticationProvider(postProcess(authenticationProvider)));
}
@@ -166,18 +127,14 @@ public final class OAuth2TokenIntrospectionEndpointConfigurer extends AbstractOA
@Override
void configure(HttpSecurity httpSecurity) {
AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class);
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(httpSecurity);
OAuth2TokenIntrospectionEndpointFilter introspectionEndpointFilter =
new OAuth2TokenIntrospectionEndpointFilter(
authenticationManager, authorizationServerSettings.getTokenIntrospectionEndpoint());
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
if (!this.introspectionRequestConverters.isEmpty()) {
authenticationConverters.addAll(0, this.introspectionRequestConverters);
authenticationManager, providerSettings.getTokenIntrospectionEndpoint());
if (this.introspectionRequestConverter != null) {
introspectionEndpointFilter.setAuthenticationConverter(this.introspectionRequestConverter);
}
this.introspectionRequestConvertersConsumer.accept(authenticationConverters);
introspectionEndpointFilter.setAuthenticationConverter(
new DelegatingAuthenticationConverter(authenticationConverters));
if (this.introspectionResponseHandler != null) {
introspectionEndpointFilter.setAuthenticationSuccessHandler(this.introspectionResponseHandler);
}
@@ -192,15 +149,7 @@ public final class OAuth2TokenIntrospectionEndpointConfigurer extends AbstractOA
return this.requestMatcher;
}
private static List<AuthenticationConverter> createDefaultAuthenticationConverters() {
List<AuthenticationConverter> authenticationConverters = new ArrayList<>();
authenticationConverters.add(new OAuth2TokenIntrospectionAuthenticationConverter());
return authenticationConverters;
}
private static List<AuthenticationProvider> createDefaultAuthenticationProviders(HttpSecurity httpSecurity) {
private List<AuthenticationProvider> createDefaultAuthenticationProviders(HttpSecurity httpSecurity) {
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
OAuth2TokenIntrospectionAuthenticationProvider tokenIntrospectionAuthenticationProvider =

View File

@@ -16,10 +16,10 @@
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;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
@@ -30,10 +30,8 @@ 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.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ProviderSettings;
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;
@@ -46,17 +44,14 @@ 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 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 AuthenticationConverter revocationRequestConverter;
private final List<AuthenticationProvider> authenticationProviders = new LinkedList<>();
private AuthenticationSuccessHandler revocationResponseHandler;
private AuthenticationFailureHandler errorResponseHandler;
@@ -68,31 +63,14 @@ public final class OAuth2TokenRevocationEndpointConfigurer extends AbstractOAuth
}
/**
* 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.
* Sets the {@link AuthenticationConverter} used when attempting to extract a Revoke Token Request from {@link HttpServletRequest}
* to an instance of {@link OAuth2TokenRevocationAuthenticationToken} used for authenticating the client.
*
* @param revocationRequestConverter an {@link AuthenticationConverter} used when attempting to extract a Revoke Token Request from {@link HttpServletRequest}
* @param revocationRequestConverter the {@link AuthenticationConverter} used when attempting to extract client credentials from {@link HttpServletRequest}
* @return the {@link OAuth2TokenRevocationEndpointConfigurer} for further configuration
*/
public OAuth2TokenRevocationEndpointConfigurer revocationRequestConverter(AuthenticationConverter revocationRequestConverter) {
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;
this.revocationRequestConverter = revocationRequestConverter;
return this;
}
@@ -108,22 +86,6 @@ 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}.
*
@@ -149,15 +111,14 @@ public final class OAuth2TokenRevocationEndpointConfigurer extends AbstractOAuth
@Override
void init(HttpSecurity httpSecurity) {
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(httpSecurity);
this.requestMatcher = new AntPathRequestMatcher(
authorizationServerSettings.getTokenRevocationEndpoint(), HttpMethod.POST.name());
providerSettings.getTokenRevocationEndpoint(), HttpMethod.POST.name());
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
if (!this.authenticationProviders.isEmpty()) {
authenticationProviders.addAll(0, this.authenticationProviders);
}
this.authenticationProvidersConsumer.accept(authenticationProviders);
List<AuthenticationProvider> authenticationProviders =
!this.authenticationProviders.isEmpty() ?
this.authenticationProviders :
createDefaultAuthenticationProviders(httpSecurity);
authenticationProviders.forEach(authenticationProvider ->
httpSecurity.authenticationProvider(postProcess(authenticationProvider)));
}
@@ -165,18 +126,14 @@ public final class OAuth2TokenRevocationEndpointConfigurer extends AbstractOAuth
@Override
void configure(HttpSecurity httpSecurity) {
AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class);
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(httpSecurity);
OAuth2TokenRevocationEndpointFilter revocationEndpointFilter =
new OAuth2TokenRevocationEndpointFilter(
authenticationManager, authorizationServerSettings.getTokenRevocationEndpoint());
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
if (!this.revocationRequestConverters.isEmpty()) {
authenticationConverters.addAll(0, this.revocationRequestConverters);
authenticationManager, providerSettings.getTokenRevocationEndpoint());
if (this.revocationRequestConverter != null) {
revocationEndpointFilter.setAuthenticationConverter(this.revocationRequestConverter);
}
this.revocationRequestConvertersConsumer.accept(authenticationConverters);
revocationEndpointFilter.setAuthenticationConverter(
new DelegatingAuthenticationConverter(authenticationConverters));
if (this.revocationResponseHandler != null) {
revocationEndpointFilter.setAuthenticationSuccessHandler(this.revocationResponseHandler);
}
@@ -191,15 +148,7 @@ public final class OAuth2TokenRevocationEndpointConfigurer extends AbstractOAuth
return this.requestMatcher;
}
private static List<AuthenticationConverter> createDefaultAuthenticationConverters() {
List<AuthenticationConverter> authenticationConverters = new ArrayList<>();
authenticationConverters.add(new OAuth2TokenRevocationAuthenticationConverter());
return authenticationConverters;
}
private static List<AuthenticationProvider> createDefaultAuthenticationProviders(HttpSecurity httpSecurity) {
private List<AuthenticationProvider> createDefaultAuthenticationProviders(HttpSecurity httpSecurity) {
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
OAuth2TokenRevocationAuthenticationProvider tokenRevocationAuthenticationProvider =

View File

@@ -21,7 +21,7 @@ import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ProviderSettings;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
@@ -47,10 +47,10 @@ public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAut
@Override
void init(HttpSecurity httpSecurity) {
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(httpSecurity);
this.requestMatcher = new OrRequestMatcher(
new AntPathRequestMatcher(authorizationServerSettings.getOidcClientRegistrationEndpoint(), HttpMethod.POST.name()),
new AntPathRequestMatcher(authorizationServerSettings.getOidcClientRegistrationEndpoint(), HttpMethod.GET.name())
new AntPathRequestMatcher(providerSettings.getOidcClientRegistrationEndpoint(), HttpMethod.POST.name()),
new AntPathRequestMatcher(providerSettings.getOidcClientRegistrationEndpoint(), HttpMethod.GET.name())
);
OidcClientRegistrationAuthenticationProvider oidcClientRegistrationAuthenticationProvider =
@@ -64,12 +64,12 @@ public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAut
@Override
void configure(HttpSecurity httpSecurity) {
AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class);
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(httpSecurity);
OidcClientRegistrationEndpointFilter oidcClientRegistrationEndpointFilter =
new OidcClientRegistrationEndpointFilter(
authenticationManager,
authorizationServerSettings.getOidcClientRegistrationEndpoint());
providerSettings.getOidcClientRegistrationEndpoint());
httpSecurity.addFilterAfter(postProcess(oidcClientRegistrationEndpointFilter), FilterSecurityInterceptor.class);
}

View File

@@ -20,15 +20,16 @@ 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.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.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.settings.ProviderSettings;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.util.UriComponentsBuilder;
/**
* Configurer for OpenID Connect 1.0 support.
@@ -36,9 +37,9 @@ import org.springframework.web.util.UriComponentsBuilder;
* @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<>();
@@ -49,22 +50,9 @@ 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.
*
@@ -96,36 +84,40 @@ public final class OidcConfigurer extends AbstractOAuth2Configurer {
@Override
void init(HttpSecurity httpSecurity) {
OidcUserInfoEndpointConfigurer userInfoEndpointConfigurer =
getConfigurer(OidcUserInfoEndpointConfigurer.class);
userInfoEndpointConfigurer.init(httpSecurity);
OidcClientRegistrationEndpointConfigurer clientRegistrationEndpointConfigurer =
getConfigurer(OidcClientRegistrationEndpointConfigurer.class);
if (clientRegistrationEndpointConfigurer != null) {
clientRegistrationEndpointConfigurer.init(httpSecurity);
}
List<RequestMatcher> requestMatchers = new ArrayList<>();
this.configurers.values().forEach(configurer -> {
configurer.init(httpSecurity);
requestMatchers.add(configurer.getRequestMatcher());
});
requestMatchers.add(new AntPathRequestMatcher(
"/.well-known/openid-configuration", HttpMethod.GET.name()));
requestMatchers.add(userInfoEndpointConfigurer.getRequestMatcher());
if (clientRegistrationEndpointConfigurer != null) {
requestMatchers.add(clientRegistrationEndpointConfigurer.getRequestMatcher());
}
this.requestMatcher = new OrRequestMatcher(requestMatchers);
}
@Override
void configure(HttpSecurity httpSecurity) {
OidcUserInfoEndpointConfigurer userInfoEndpointConfigurer =
getConfigurer(OidcUserInfoEndpointConfigurer.class);
userInfoEndpointConfigurer.configure(httpSecurity);
OidcClientRegistrationEndpointConfigurer clientRegistrationEndpointConfigurer =
getConfigurer(OidcClientRegistrationEndpointConfigurer.class);
if (clientRegistrationEndpointConfigurer != null) {
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);
});
clientRegistrationEndpointConfigurer.configure(httpSecurity);
}
this.configurers.values().forEach(configurer -> configurer.configure(httpSecurity));
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(httpSecurity);
OidcProviderConfigurationEndpointFilter oidcProviderConfigurationEndpointFilter =
new OidcProviderConfigurationEndpointFilter(providerSettings);
httpSecurity.addFilterBefore(postProcess(oidcProviderConfigurationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
}
@Override

View File

@@ -1,108 +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.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

@@ -28,7 +28,7 @@ import org.springframework.security.oauth2.server.authorization.oidc.authenticat
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.settings.ProviderSettings;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
@@ -76,8 +76,8 @@ public final class OidcUserInfoEndpointConfigurer extends AbstractOAuth2Configur
@Override
void init(HttpSecurity httpSecurity) {
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
String userInfoEndpointUri = authorizationServerSettings.getOidcUserInfoEndpoint();
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(httpSecurity);
String userInfoEndpointUri = providerSettings.getOidcUserInfoEndpoint();
this.requestMatcher = new OrRequestMatcher(
new AntPathRequestMatcher(userInfoEndpointUri, HttpMethod.GET.name()),
new AntPathRequestMatcher(userInfoEndpointUri, HttpMethod.POST.name()));
@@ -94,12 +94,12 @@ public final class OidcUserInfoEndpointConfigurer extends AbstractOAuth2Configur
@Override
void configure(HttpSecurity httpSecurity) {
AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class);
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(httpSecurity);
OidcUserInfoEndpointFilter oidcUserInfoEndpointFilter =
new OidcUserInfoEndpointFilter(
authenticationManager,
authorizationServerSettings.getOidcUserInfoEndpoint());
providerSettings.getOidcUserInfoEndpoint());
httpSecurity.addFilterAfter(postProcess(oidcUserInfoEndpointFilter), FilterSecurityInterceptor.class);
}

View File

@@ -1,44 +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.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

@@ -1,60 +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;
/**
* 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

@@ -0,0 +1,70 @@
/*
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.context;
import java.util.function.Supplier;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.server.authorization.settings.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

@@ -0,0 +1,63 @@
/*
* 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

@@ -49,8 +49,8 @@ import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
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.AuthorizationServerContext;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.context.ProviderContext;
import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder;
import org.springframework.security.oauth2.server.authorization.oidc.OidcClientMetadataClaimNames;
import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
@@ -212,7 +212,7 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder()
.registeredClient(registeredClient)
.principal(clientPrincipal)
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
.providerContext(ProviderContextHolder.getProviderContext())
.authorizedScopes(authorizedScopes)
.tokenType(OAuth2TokenType.ACCESS_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
@@ -276,9 +276,9 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
scopes.addAll(registeredClient.getScopes()));
}
AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext();
String registrationClientUri = UriComponentsBuilder.fromUriString(authorizationServerContext.getIssuer())
.path(authorizationServerContext.getAuthorizationServerSettings().getOidcClientRegistrationEndpoint())
ProviderContext providerContext = ProviderContextHolder.getProviderContext();
String registrationClientUri = UriComponentsBuilder.fromUriString(providerContext.getIssuer())
.path(providerContext.getProviderSettings().getOidcClientRegistrationEndpoint())
.queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
.toUriString();

View File

@@ -15,12 +15,9 @@
*/
package org.springframework.security.oauth2.server.authorization.oidc.authentication;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
@@ -34,27 +31,12 @@ import org.springframework.util.Assert;
* @author Joe Grandja
* @since 0.2.1
* @see OAuth2AuthenticationContext
* @see OidcUserInfo
* @see OidcUserInfoAuthenticationProvider#setUserInfoMapper(Function)
*/
public final class OidcUserInfoAuthenticationContext implements OAuth2AuthenticationContext {
private final Map<Object, Object> context;
public final class OidcUserInfoAuthenticationContext extends OAuth2AuthenticationContext {
private OidcUserInfoAuthenticationContext(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);
super(context);
}
/**

View File

@@ -17,10 +17,10 @@ package org.springframework.security.oauth2.server.authorization.oidc.web;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;

View File

@@ -19,10 +19,10 @@ import java.io.IOException;
import java.util.List;
import java.util.function.Consumer;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
@@ -32,11 +32,10 @@ import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
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.context.ProviderContextHolder;
import org.springframework.security.oauth2.server.authorization.oidc.OidcProviderConfiguration;
import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcProviderConfigurationHttpMessageConverter;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ProviderSettings;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
@@ -47,10 +46,9 @@ import org.springframework.web.util.UriComponentsBuilder;
* A {@code Filter} that processes OpenID Provider Configuration Requests.
*
* @author Daniel Garnier-Moiroux
* @author Joe Grandja
* @since 0.1.0
* @see OidcProviderConfiguration
* @see AuthorizationServerSettings
* @see ProviderSettings
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest">4.1. OpenID Provider Configuration Request</a>
*/
public final class OidcProviderConfigurationEndpointFilter extends OncePerRequestFilter {
@@ -59,23 +57,18 @@ public final class OidcProviderConfigurationEndpointFilter extends OncePerReques
*/
private static final String DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI = "/.well-known/openid-configuration";
private final RequestMatcher requestMatcher = new AntPathRequestMatcher(
DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI,
HttpMethod.GET.name());
private final ProviderSettings providerSettings;
private final RequestMatcher requestMatcher;
private final OidcProviderConfigurationHttpMessageConverter providerConfigurationHttpMessageConverter =
new OidcProviderConfigurationHttpMessageConverter();
private Consumer<OidcProviderConfiguration.Builder> providerConfigurationCustomizer = (providerConfiguration) -> {};
/**
* 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}
* @since 0.4.0
*/
public void setProviderConfigurationCustomizer(Consumer<OidcProviderConfiguration.Builder> providerConfigurationCustomizer) {
Assert.notNull(providerConfigurationCustomizer, "providerConfigurationCustomizer cannot be null");
this.providerConfigurationCustomizer = providerConfigurationCustomizer;
public OidcProviderConfigurationEndpointFilter(ProviderSettings providerSettings) {
Assert.notNull(providerSettings, "providerSettings cannot be null");
this.providerSettings = providerSettings;
this.requestMatcher = new AntPathRequestMatcher(
DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI,
HttpMethod.GET.name()
);
}
@Override
@@ -87,34 +80,31 @@ public final class OidcProviderConfigurationEndpointFilter extends OncePerReques
return;
}
AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext();
String issuer = authorizationServerContext.getIssuer();
AuthorizationServerSettings authorizationServerSettings = authorizationServerContext.getAuthorizationServerSettings();
String issuer = ProviderContextHolder.getProviderContext().getIssuer();
OidcProviderConfiguration.Builder providerConfiguration = OidcProviderConfiguration.builder()
OidcProviderConfiguration providerConfiguration = OidcProviderConfiguration.builder()
.issuer(issuer)
.authorizationEndpoint(asUrl(issuer, authorizationServerSettings.getAuthorizationEndpoint()))
.tokenEndpoint(asUrl(issuer, authorizationServerSettings.getTokenEndpoint()))
.authorizationEndpoint(asUrl(issuer, this.providerSettings.getAuthorizationEndpoint()))
.tokenEndpoint(asUrl(issuer, this.providerSettings.getTokenEndpoint()))
.tokenEndpointAuthenticationMethods(clientAuthenticationMethods())
.jwkSetUrl(asUrl(issuer, authorizationServerSettings.getJwkSetEndpoint()))
.userInfoEndpoint(asUrl(issuer, authorizationServerSettings.getOidcUserInfoEndpoint()))
.jwkSetUrl(asUrl(issuer, this.providerSettings.getJwkSetEndpoint()))
.userInfoEndpoint(asUrl(issuer, this.providerSettings.getOidcUserInfoEndpoint()))
.responseType(OAuth2AuthorizationResponseType.CODE.getValue())
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.grantType(AuthorizationGrantType.REFRESH_TOKEN.getValue())
.tokenRevocationEndpoint(asUrl(issuer, authorizationServerSettings.getTokenRevocationEndpoint()))
.tokenRevocationEndpoint(asUrl(issuer, this.providerSettings.getTokenRevocationEndpoint()))
.tokenRevocationEndpointAuthenticationMethods(clientAuthenticationMethods())
.tokenIntrospectionEndpoint(asUrl(issuer, authorizationServerSettings.getTokenIntrospectionEndpoint()))
.tokenIntrospectionEndpoint(asUrl(issuer, this.providerSettings.getTokenIntrospectionEndpoint()))
.tokenIntrospectionEndpointAuthenticationMethods(clientAuthenticationMethods())
.subjectType("public")
.idTokenSigningAlgorithm(SignatureAlgorithm.RS256.getName())
.scope(OidcScopes.OPENID);
this.providerConfigurationCustomizer.accept(providerConfiguration);
.scope(OidcScopes.OPENID)
.build();
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
this.providerConfigurationHttpMessageConverter.write(
providerConfiguration.build(), MediaType.APPLICATION_JSON, httpResponse);
providerConfiguration, MediaType.APPLICATION_JSON, httpResponse);
}
private static Consumer<List<String>> clientAuthenticationMethods() {
@@ -129,5 +119,4 @@ public final class OidcProviderConfigurationEndpointFilter extends OncePerReques
private static String asUrl(String issuer, String endpoint) {
return UriComponentsBuilder.fromUriString(issuer).path(endpoint).build().toUriString();
}
}

View File

@@ -17,10 +17,10 @@ package org.springframework.security.oauth2.server.authorization.oidc.web;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;

View File

@@ -71,52 +71,52 @@ public final class ConfigurationSettingNames {
}
/**
* The names for authorization server configuration settings.
* The names for provider configuration settings.
*/
public static final class AuthorizationServer {
private static final String AUTHORIZATION_SERVER_SETTINGS_NAMESPACE = SETTINGS_NAMESPACE.concat("authorization-server.");
public static final class Provider {
private static final String PROVIDER_SETTINGS_NAMESPACE = SETTINGS_NAMESPACE.concat("provider.");
/**
* Set the URL the Authorization Server uses as its Issuer Identifier.
* Set the URL the Provider uses as its Issuer Identifier.
*/
public static final String ISSUER = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE.concat("issuer");
public static final String ISSUER = PROVIDER_SETTINGS_NAMESPACE.concat("issuer");
/**
* Set the OAuth 2.0 Authorization endpoint.
* Set the Provider's OAuth 2.0 Authorization endpoint.
*/
public static final String AUTHORIZATION_ENDPOINT = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE.concat("authorization-endpoint");
public static final String AUTHORIZATION_ENDPOINT = PROVIDER_SETTINGS_NAMESPACE.concat("authorization-endpoint");
/**
* Set the OAuth 2.0 Token endpoint.
* Set the Provider's OAuth 2.0 Token endpoint.
*/
public static final String TOKEN_ENDPOINT = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE.concat("token-endpoint");
public static final String TOKEN_ENDPOINT = PROVIDER_SETTINGS_NAMESPACE.concat("token-endpoint");
/**
* Set the JWK Set endpoint.
* Set the Provider's JWK Set endpoint.
*/
public static final String JWK_SET_ENDPOINT = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE.concat("jwk-set-endpoint");
public static final String JWK_SET_ENDPOINT = PROVIDER_SETTINGS_NAMESPACE.concat("jwk-set-endpoint");
/**
* Set the OAuth 2.0 Token Revocation endpoint.
* Set the Provider's OAuth 2.0 Token Revocation endpoint.
*/
public static final String TOKEN_REVOCATION_ENDPOINT = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE.concat("token-revocation-endpoint");
public static final String TOKEN_REVOCATION_ENDPOINT = PROVIDER_SETTINGS_NAMESPACE.concat("token-revocation-endpoint");
/**
* Set the OAuth 2.0 Token Introspection endpoint.
* Set the Provider's OAuth 2.0 Token Introspection endpoint.
*/
public static final String TOKEN_INTROSPECTION_ENDPOINT = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE.concat("token-introspection-endpoint");
public static final String TOKEN_INTROSPECTION_ENDPOINT = PROVIDER_SETTINGS_NAMESPACE.concat("token-introspection-endpoint");
/**
* Set the OpenID Connect 1.0 Client Registration endpoint.
* Set the Provider's OpenID Connect 1.0 Client Registration endpoint.
*/
public static final String OIDC_CLIENT_REGISTRATION_ENDPOINT = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE.concat("oidc-client-registration-endpoint");
public static final String OIDC_CLIENT_REGISTRATION_ENDPOINT = PROVIDER_SETTINGS_NAMESPACE.concat("oidc-client-registration-endpoint");
/**
* Set the OpenID Connect 1.0 UserInfo endpoint.
* Set the Provider's OpenID Connect 1.0 UserInfo endpoint.
*/
public static final String OIDC_USER_INFO_ENDPOINT = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE.concat("oidc-user-info-endpoint");
public static final String OIDC_USER_INFO_ENDPOINT = PROVIDER_SETTINGS_NAMESPACE.concat("oidc-user-info-endpoint");
private AuthorizationServer() {
private Provider() {
}
}

View File

@@ -20,90 +20,90 @@ import java.util.Map;
import org.springframework.util.Assert;
/**
* A facility for authorization server configuration settings.
* A facility for provider configuration settings.
*
* @author Daniel Garnier-Moiroux
* @author Joe Grandja
* @since 0.1.0
* @see AbstractSettings
* @see ConfigurationSettingNames.AuthorizationServer
* @see ConfigurationSettingNames.Provider
*/
public final class AuthorizationServerSettings extends AbstractSettings {
public final class ProviderSettings extends AbstractSettings {
private AuthorizationServerSettings(Map<String, Object> settings) {
private ProviderSettings(Map<String, Object> settings) {
super(settings);
}
/**
* Returns the URL of the Authorization Server's Issuer Identifier
* Returns the URL of the Provider's Issuer Identifier
*
* @return the URL of the Authorization Server's Issuer Identifier
* @return the URL of the Provider's Issuer Identifier
*/
public String getIssuer() {
return getSetting(ConfigurationSettingNames.AuthorizationServer.ISSUER);
return getSetting(ConfigurationSettingNames.Provider.ISSUER);
}
/**
* Returns the OAuth 2.0 Authorization endpoint. The default is {@code /oauth2/authorize}.
* Returns the Provider's OAuth 2.0 Authorization endpoint. The default is {@code /oauth2/authorize}.
*
* @return the Authorization endpoint
*/
public String getAuthorizationEndpoint() {
return getSetting(ConfigurationSettingNames.AuthorizationServer.AUTHORIZATION_ENDPOINT);
return getSetting(ConfigurationSettingNames.Provider.AUTHORIZATION_ENDPOINT);
}
/**
* Returns the OAuth 2.0 Token endpoint. The default is {@code /oauth2/token}.
* Returns the Provider's OAuth 2.0 Token endpoint. The default is {@code /oauth2/token}.
*
* @return the Token endpoint
*/
public String getTokenEndpoint() {
return getSetting(ConfigurationSettingNames.AuthorizationServer.TOKEN_ENDPOINT);
return getSetting(ConfigurationSettingNames.Provider.TOKEN_ENDPOINT);
}
/**
* Returns the JWK Set endpoint. The default is {@code /oauth2/jwks}.
* Returns the Provider's JWK Set endpoint. The default is {@code /oauth2/jwks}.
*
* @return the JWK Set endpoint
*/
public String getJwkSetEndpoint() {
return getSetting(ConfigurationSettingNames.AuthorizationServer.JWK_SET_ENDPOINT);
return getSetting(ConfigurationSettingNames.Provider.JWK_SET_ENDPOINT);
}
/**
* Returns the OAuth 2.0 Token Revocation endpoint. The default is {@code /oauth2/revoke}.
* Returns the Provider's OAuth 2.0 Token Revocation endpoint. The default is {@code /oauth2/revoke}.
*
* @return the Token Revocation endpoint
*/
public String getTokenRevocationEndpoint() {
return getSetting(ConfigurationSettingNames.AuthorizationServer.TOKEN_REVOCATION_ENDPOINT);
return getSetting(ConfigurationSettingNames.Provider.TOKEN_REVOCATION_ENDPOINT);
}
/**
* Returns the OAuth 2.0 Token Introspection endpoint. The default is {@code /oauth2/introspect}.
* Returns the Provider's OAuth 2.0 Token Introspection endpoint. The default is {@code /oauth2/introspect}.
*
* @return the Token Introspection endpoint
*/
public String getTokenIntrospectionEndpoint() {
return getSetting(ConfigurationSettingNames.AuthorizationServer.TOKEN_INTROSPECTION_ENDPOINT);
return getSetting(ConfigurationSettingNames.Provider.TOKEN_INTROSPECTION_ENDPOINT);
}
/**
* Returns the OpenID Connect 1.0 Client Registration endpoint. The default is {@code /connect/register}.
* Returns the Provider's OpenID Connect 1.0 Client Registration endpoint. The default is {@code /connect/register}.
*
* @return the OpenID Connect 1.0 Client Registration endpoint
*/
public String getOidcClientRegistrationEndpoint() {
return getSetting(ConfigurationSettingNames.AuthorizationServer.OIDC_CLIENT_REGISTRATION_ENDPOINT);
return getSetting(ConfigurationSettingNames.Provider.OIDC_CLIENT_REGISTRATION_ENDPOINT);
}
/**
* Returns the OpenID Connect 1.0 UserInfo endpoint. The default is {@code /userinfo}.
* Returns the Provider's OpenID Connect 1.0 UserInfo endpoint. The default is {@code /userinfo}.
*
* @return the OpenID Connect 1.0 UserInfo endpoint
*/
public String getOidcUserInfoEndpoint() {
return getSetting(ConfigurationSettingNames.AuthorizationServer.OIDC_USER_INFO_ENDPOINT);
return getSetting(ConfigurationSettingNames.Provider.OIDC_USER_INFO_ENDPOINT);
}
/**
@@ -135,101 +135,101 @@ public final class AuthorizationServerSettings extends AbstractSettings {
}
/**
* A builder for {@link AuthorizationServerSettings}.
* A builder for {@link ProviderSettings}.
*/
public final static class Builder extends AbstractBuilder<AuthorizationServerSettings, Builder> {
public final static class Builder extends AbstractBuilder<ProviderSettings, Builder> {
private Builder() {
}
/**
* Sets the URL the Authorization Server uses as its Issuer Identifier.
* Sets the URL the Provider uses as its Issuer Identifier.
*
* @param issuer the URL the Authorization Server uses as its Issuer Identifier.
* @param issuer the URL the Provider uses as its Issuer Identifier.
* @return the {@link Builder} for further configuration
*/
public Builder issuer(String issuer) {
return setting(ConfigurationSettingNames.AuthorizationServer.ISSUER, issuer);
return setting(ConfigurationSettingNames.Provider.ISSUER, issuer);
}
/**
* Sets the OAuth 2.0 Authorization endpoint.
* Sets the Provider's OAuth 2.0 Authorization endpoint.
*
* @param authorizationEndpoint the Authorization endpoint
* @return the {@link Builder} for further configuration
*/
public Builder authorizationEndpoint(String authorizationEndpoint) {
return setting(ConfigurationSettingNames.AuthorizationServer.AUTHORIZATION_ENDPOINT, authorizationEndpoint);
return setting(ConfigurationSettingNames.Provider.AUTHORIZATION_ENDPOINT, authorizationEndpoint);
}
/**
* Sets the OAuth 2.0 Token endpoint.
* Sets the Provider's OAuth 2.0 Token endpoint.
*
* @param tokenEndpoint the Token endpoint
* @return the {@link Builder} for further configuration
*/
public Builder tokenEndpoint(String tokenEndpoint) {
return setting(ConfigurationSettingNames.AuthorizationServer.TOKEN_ENDPOINT, tokenEndpoint);
return setting(ConfigurationSettingNames.Provider.TOKEN_ENDPOINT, tokenEndpoint);
}
/**
* Sets the JWK Set endpoint.
* Sets the Provider's JWK Set endpoint.
*
* @param jwkSetEndpoint the JWK Set endpoint
* @return the {@link Builder} for further configuration
*/
public Builder jwkSetEndpoint(String jwkSetEndpoint) {
return setting(ConfigurationSettingNames.AuthorizationServer.JWK_SET_ENDPOINT, jwkSetEndpoint);
return setting(ConfigurationSettingNames.Provider.JWK_SET_ENDPOINT, jwkSetEndpoint);
}
/**
* Sets the OAuth 2.0 Token Revocation endpoint.
* Sets the Provider's OAuth 2.0 Token Revocation endpoint.
*
* @param tokenRevocationEndpoint the Token Revocation endpoint
* @return the {@link Builder} for further configuration
*/
public Builder tokenRevocationEndpoint(String tokenRevocationEndpoint) {
return setting(ConfigurationSettingNames.AuthorizationServer.TOKEN_REVOCATION_ENDPOINT, tokenRevocationEndpoint);
return setting(ConfigurationSettingNames.Provider.TOKEN_REVOCATION_ENDPOINT, tokenRevocationEndpoint);
}
/**
* Sets the OAuth 2.0 Token Introspection endpoint.
* Sets the Provider's OAuth 2.0 Token Introspection endpoint.
*
* @param tokenIntrospectionEndpoint the Token Introspection endpoint
* @return the {@link Builder} for further configuration
*/
public Builder tokenIntrospectionEndpoint(String tokenIntrospectionEndpoint) {
return setting(ConfigurationSettingNames.AuthorizationServer.TOKEN_INTROSPECTION_ENDPOINT, tokenIntrospectionEndpoint);
return setting(ConfigurationSettingNames.Provider.TOKEN_INTROSPECTION_ENDPOINT, tokenIntrospectionEndpoint);
}
/**
* Sets the OpenID Connect 1.0 Client Registration endpoint.
* Sets the Provider's OpenID Connect 1.0 Client Registration endpoint.
*
* @param oidcClientRegistrationEndpoint the OpenID Connect 1.0 Client Registration endpoint
* @return the {@link Builder} for further configuration
*/
public Builder oidcClientRegistrationEndpoint(String oidcClientRegistrationEndpoint) {
return setting(ConfigurationSettingNames.AuthorizationServer.OIDC_CLIENT_REGISTRATION_ENDPOINT, oidcClientRegistrationEndpoint);
return setting(ConfigurationSettingNames.Provider.OIDC_CLIENT_REGISTRATION_ENDPOINT, oidcClientRegistrationEndpoint);
}
/**
* Sets the OpenID Connect 1.0 UserInfo endpoint.
* Sets the Provider's OpenID Connect 1.0 UserInfo endpoint.
*
* @param oidcUserInfoEndpoint the OpenID Connect 1.0 UserInfo endpoint
* @return the {@link Builder} for further configuration
*/
public Builder oidcUserInfoEndpoint(String oidcUserInfoEndpoint) {
return setting(ConfigurationSettingNames.AuthorizationServer.OIDC_USER_INFO_ENDPOINT, oidcUserInfoEndpoint);
return setting(ConfigurationSettingNames.Provider.OIDC_USER_INFO_ENDPOINT, oidcUserInfoEndpoint);
}
/**
* Builds the {@link AuthorizationServerSettings}.
* Builds the {@link ProviderSettings}.
*
* @return the {@link AuthorizationServerSettings}
* @return the {@link ProviderSettings}
*/
@Override
public AuthorizationServerSettings build() {
return new AuthorizationServerSettings(getSettings());
public ProviderSettings build() {
return new ProviderSettings(getSettings());
}
}

View File

@@ -27,7 +27,6 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JwsHeader;
import org.springframework.security.oauth2.jwt.Jwt;
@@ -83,20 +82,16 @@ public final class JwtGenerator implements OAuth2TokenGenerator<Jwt> {
}
String issuer = null;
if (context.getAuthorizationServerContext() != null) {
issuer = context.getAuthorizationServerContext().getIssuer();
if (context.getProviderContext() != null) {
issuer = context.getProviderContext().getIssuer();
}
RegisteredClient registeredClient = context.getRegisteredClient();
Instant issuedAt = Instant.now();
Instant expiresAt;
JwsAlgorithm jwsAlgorithm = SignatureAlgorithm.RS256;
if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) {
// TODO Allow configuration for ID Token time-to-live
expiresAt = issuedAt.plus(30, ChronoUnit.MINUTES);
if (registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm() != null) {
jwsAlgorithm = registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm();
}
} else {
expiresAt = issuedAt.plus(registeredClient.getTokenSettings().getAccessTokenTimeToLive());
}
@@ -130,14 +125,14 @@ public final class JwtGenerator implements OAuth2TokenGenerator<Jwt> {
}
// @formatter:on
JwsHeader.Builder jwsHeaderBuilder = JwsHeader.with(jwsAlgorithm);
JwsHeader.Builder jwsHeaderBuilder = JwsHeader.with(SignatureAlgorithm.RS256);
if (this.jwtCustomizer != null) {
// @formatter:off
JwtEncodingContext.Builder jwtContextBuilder = JwtEncodingContext.with(jwsHeaderBuilder, claimsBuilder)
.registeredClient(context.getRegisteredClient())
.principal(context.getPrincipal())
.authorizationServerContext(context.getAuthorizationServerContext())
.providerContext(context.getProviderContext())
.authorizedScopes(context.getAuthorizedScopes())
.tokenType(context.getTokenType())
.authorizationGrantType(context.getAuthorizationGrantType());

View File

@@ -61,8 +61,8 @@ public final class OAuth2AccessTokenGenerator implements OAuth2TokenGenerator<OA
}
String issuer = null;
if (context.getAuthorizationServerContext() != null) {
issuer = context.getAuthorizationServerContext().getIssuer();
if (context.getProviderContext() != null) {
issuer = context.getProviderContext().getIssuer();
}
RegisteredClient registeredClient = context.getRegisteredClient();
@@ -91,7 +91,7 @@ public final class OAuth2AccessTokenGenerator implements OAuth2TokenGenerator<OA
OAuth2TokenClaimsContext.Builder accessTokenContextBuilder = OAuth2TokenClaimsContext.with(claimsBuilder)
.registeredClient(context.getRegisteredClient())
.principal(context.getPrincipal())
.authorizationServerContext(context.getAuthorizationServerContext())
.providerContext(context.getProviderContext())
.authorizedScopes(context.getAuthorizedScopes())
.tokenType(context.getTokenType())
.authorizationGrantType(context.getAuthorizationGrantType());

View File

@@ -27,8 +27,8 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
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.AuthorizationServerContext;
import org.springframework.security.oauth2.server.authorization.context.Context;
import org.springframework.security.oauth2.server.authorization.context.ProviderContext;
import org.springframework.util.Assert;
/**
@@ -63,13 +63,13 @@ public interface OAuth2TokenContext extends Context {
}
/**
* Returns the {@link AuthorizationServerContext authorization server context}.
* Returns the {@link ProviderContext provider context}.
*
* @return the {@link AuthorizationServerContext}
* @return the {@link ProviderContext}
* @since 0.2.3
*/
default AuthorizationServerContext getAuthorizationServerContext() {
return get(AuthorizationServerContext.class);
default ProviderContext getProviderContext() {
return get(ProviderContext.class);
}
/**
@@ -157,14 +157,14 @@ public interface OAuth2TokenContext extends Context {
}
/**
* Sets the {@link AuthorizationServerContext authorization server context}.
* Sets the {@link ProviderContext provider context}.
*
* @param authorizationServerContext the {@link AuthorizationServerContext}
* @param providerContext the {@link ProviderContext}
* @return the {@link AbstractBuilder} for further configuration
* @since 0.2.3
*/
public B authorizationServerContext(AuthorizationServerContext authorizationServerContext) {
return put(AuthorizationServerContext.class, authorizationServerContext);
public B providerContext(ProviderContext providerContext) {
return put(ProviderContext.class, providerContext);
}
/**

View File

@@ -22,8 +22,8 @@ package org.springframework.security.oauth2.server.authorization.util;
* @since 0.0.1
*/
public final class SpringAuthorizationServerVersion {
private static final int MAJOR = 0;
private static final int MINOR = 4;
private static final int MAJOR = 1;
private static final int MINOR = 0;
private static final int PATCH = 0;
/**

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.
@@ -18,10 +18,10 @@ package org.springframework.security.oauth2.server.authorization.web;
import java.io.IOException;
import java.io.Writer;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import com.nimbusds.jose.jwk.JWKMatcher;
import com.nimbusds.jose.jwk.JWKSelector;

View File

@@ -17,19 +17,17 @@ package org.springframework.security.oauth2.server.authorization.web;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
@@ -42,11 +40,7 @@ import org.springframework.security.oauth2.core.oidc.OidcScopes;
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.authentication.OAuth2AuthorizationConsentAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken;
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.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.AuthenticationConverter;
@@ -67,7 +61,7 @@ import org.springframework.web.util.UriComponentsBuilder;
/**
* A {@code Filter} for the OAuth 2.0 Authorization Code Grant,
* which handles the processing of the OAuth 2.0 Authorization Request and Consent.
* which handles the processing of the OAuth 2.0 Authorization Request (and Consent).
*
* @author Joe Grandja
* @author Paurav Munshi
@@ -77,7 +71,6 @@ import org.springframework.web.util.UriComponentsBuilder;
* @since 0.0.1
* @see AuthenticationManager
* @see OAuth2AuthorizationCodeRequestAuthenticationProvider
* @see OAuth2AuthorizationConsentAuthenticationProvider
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant</a>
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1">Section 4.1.1 Authorization Request</a>
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2">Section 4.1.2 Authorization Response</a>
@@ -117,10 +110,7 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte
Assert.hasText(authorizationEndpointUri, "authorizationEndpointUri cannot be empty");
this.authenticationManager = authenticationManager;
this.authorizationEndpointMatcher = createDefaultRequestMatcher(authorizationEndpointUri);
this.authenticationConverter = new DelegatingAuthenticationConverter(
Arrays.asList(
new OAuth2AuthorizationCodeRequestAuthenticationConverter(),
new OAuth2AuthorizationConsentAuthenticationConverter()));
this.authenticationConverter = new OAuth2AuthorizationCodeRequestAuthenticationConverter();
}
private static RequestMatcher createDefaultRequestMatcher(String authorizationEndpointUri) {
@@ -155,14 +145,14 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte
}
try {
Authentication authentication = this.authenticationConverter.convert(request);
if (authentication instanceof AbstractAuthenticationToken) {
((AbstractAuthenticationToken) authentication)
.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
Authentication authenticationResult = this.authenticationManager.authenticate(authentication);
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationConverter.convert(request);
authorizationCodeRequestAuthentication.setDetails(this.authenticationDetailsSource.buildDetails(request));
if (!authenticationResult.isAuthenticated()) {
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult =
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationManager.authenticate(authorizationCodeRequestAuthentication);
if (!authorizationCodeRequestAuthenticationResult.isAuthenticated()) {
// If the Principal (Resource Owner) is not authenticated then
// pass through the chain with the expectation that the authentication process
// will commence via AuthenticationEntryPoint
@@ -170,15 +160,13 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte
return;
}
if (authenticationResult instanceof OAuth2AuthorizationConsentAuthenticationToken) {
sendAuthorizationConsent(request, response,
(OAuth2AuthorizationCodeRequestAuthenticationToken) authentication,
(OAuth2AuthorizationConsentAuthenticationToken) authenticationResult);
if (authorizationCodeRequestAuthenticationResult.isConsentRequired()) {
sendAuthorizationConsent(request, response, authorizationCodeRequestAuthentication, authorizationCodeRequestAuthenticationResult);
return;
}
this.authenticationSuccessHandler.onAuthenticationSuccess(
request, response, authenticationResult);
request, response, authorizationCodeRequestAuthenticationResult);
} catch (OAuth2AuthenticationException ex) {
this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex);
@@ -198,8 +186,7 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte
/**
* Sets the {@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.
* to an instance of {@link OAuth2AuthorizationCodeRequestAuthenticationToken} used for authenticating the request.
*
* @param authenticationConverter the {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
*/
@@ -242,13 +229,13 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte
private void sendAuthorizationConsent(HttpServletRequest request, HttpServletResponse response,
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication,
OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication) throws IOException {
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult) throws IOException {
String clientId = authorizationConsentAuthentication.getClientId();
Authentication principal = (Authentication) authorizationConsentAuthentication.getPrincipal();
String clientId = authorizationCodeRequestAuthenticationResult.getClientId();
Authentication principal = (Authentication) authorizationCodeRequestAuthenticationResult.getPrincipal();
Set<String> requestedScopes = authorizationCodeRequestAuthentication.getScopes();
Set<String> authorizedScopes = authorizationConsentAuthentication.getScopes();
String state = authorizationConsentAuthentication.getState();
Set<String> authorizedScopes = authorizationCodeRequestAuthenticationResult.getScopes();
String state = authorizationCodeRequestAuthenticationResult.getState();
if (hasConsentUri()) {
String redirectUri = UriComponentsBuilder.fromUriString(resolveConsentUri(request))

View File

@@ -19,10 +19,10 @@ import java.io.IOException;
import java.util.List;
import java.util.function.Consumer;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
@@ -31,10 +31,9 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata;
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.context.ProviderContextHolder;
import org.springframework.security.oauth2.server.authorization.http.converter.OAuth2AuthorizationServerMetadataHttpMessageConverter;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ProviderSettings;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
@@ -45,10 +44,9 @@ import org.springframework.web.util.UriComponentsBuilder;
* A {@code Filter} that processes OAuth 2.0 Authorization Server Metadata Requests.
*
* @author Daniel Garnier-Moiroux
* @author Joe Grandja
* @since 0.1.1
* @see OAuth2AuthorizationServerMetadata
* @see AuthorizationServerSettings
* @see ProviderSettings
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-3">3. Obtaining Authorization Server Metadata</a>
*/
public final class OAuth2AuthorizationServerMetadataEndpointFilter extends OncePerRequestFilter {
@@ -57,23 +55,18 @@ public final class OAuth2AuthorizationServerMetadataEndpointFilter extends OnceP
*/
private static final String DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI = "/.well-known/oauth-authorization-server";
private final RequestMatcher requestMatcher = new AntPathRequestMatcher(
DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI,
HttpMethod.GET.name());
private final ProviderSettings providerSettings;
private final RequestMatcher requestMatcher;
private final OAuth2AuthorizationServerMetadataHttpMessageConverter authorizationServerMetadataHttpMessageConverter =
new OAuth2AuthorizationServerMetadataHttpMessageConverter();
private Consumer<OAuth2AuthorizationServerMetadata.Builder> authorizationServerMetadataCustomizer = (authorizationServerMetadata) -> {};
/**
* 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}
* @since 0.4.0
*/
public void setAuthorizationServerMetadataCustomizer(Consumer<OAuth2AuthorizationServerMetadata.Builder> authorizationServerMetadataCustomizer) {
Assert.notNull(authorizationServerMetadataCustomizer, "authorizationServerMetadataCustomizer cannot be null");
this.authorizationServerMetadataCustomizer = authorizationServerMetadataCustomizer;
public OAuth2AuthorizationServerMetadataEndpointFilter(ProviderSettings providerSettings) {
Assert.notNull(providerSettings, "providerSettings cannot be null");
this.providerSettings = providerSettings;
this.requestMatcher = new AntPathRequestMatcher(
DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI,
HttpMethod.GET.name()
);
}
@Override
@@ -85,31 +78,28 @@ public final class OAuth2AuthorizationServerMetadataEndpointFilter extends OnceP
return;
}
AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext();
String issuer = authorizationServerContext.getIssuer();
AuthorizationServerSettings authorizationServerSettings = authorizationServerContext.getAuthorizationServerSettings();
String issuer = ProviderContextHolder.getProviderContext().getIssuer();
OAuth2AuthorizationServerMetadata.Builder authorizationServerMetadata = OAuth2AuthorizationServerMetadata.builder()
OAuth2AuthorizationServerMetadata authorizationServerMetadata = OAuth2AuthorizationServerMetadata.builder()
.issuer(issuer)
.authorizationEndpoint(asUrl(issuer, authorizationServerSettings.getAuthorizationEndpoint()))
.tokenEndpoint(asUrl(issuer, authorizationServerSettings.getTokenEndpoint()))
.authorizationEndpoint(asUrl(issuer, this.providerSettings.getAuthorizationEndpoint()))
.tokenEndpoint(asUrl(issuer, this.providerSettings.getTokenEndpoint()))
.tokenEndpointAuthenticationMethods(clientAuthenticationMethods())
.jwkSetUrl(asUrl(issuer, authorizationServerSettings.getJwkSetEndpoint()))
.jwkSetUrl(asUrl(issuer, this.providerSettings.getJwkSetEndpoint()))
.responseType(OAuth2AuthorizationResponseType.CODE.getValue())
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.grantType(AuthorizationGrantType.REFRESH_TOKEN.getValue())
.tokenRevocationEndpoint(asUrl(issuer, authorizationServerSettings.getTokenRevocationEndpoint()))
.tokenRevocationEndpoint(asUrl(issuer, this.providerSettings.getTokenRevocationEndpoint()))
.tokenRevocationEndpointAuthenticationMethods(clientAuthenticationMethods())
.tokenIntrospectionEndpoint(asUrl(issuer, authorizationServerSettings.getTokenIntrospectionEndpoint()))
.tokenIntrospectionEndpoint(asUrl(issuer, this.providerSettings.getTokenIntrospectionEndpoint()))
.tokenIntrospectionEndpointAuthenticationMethods(clientAuthenticationMethods())
.codeChallengeMethod("S256");
this.authorizationServerMetadataCustomizer.accept(authorizationServerMetadata);
.codeChallengeMethod("S256")
.build();
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
this.authorizationServerMetadataHttpMessageConverter.write(
authorizationServerMetadata.build(), MediaType.APPLICATION_JSON, httpResponse);
authorizationServerMetadata, MediaType.APPLICATION_JSON, httpResponse);
}
private static Consumer<List<String>> clientAuthenticationMethods() {

View File

@@ -18,10 +18,10 @@ package org.springframework.security.oauth2.server.authorization.web;
import java.io.IOException;
import java.util.Arrays;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConverter;

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.
@@ -17,7 +17,7 @@ package org.springframework.security.oauth2.server.authorization.web;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

View File

@@ -20,10 +20,10 @@ import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Map;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;

View File

@@ -16,11 +16,13 @@
package org.springframework.security.oauth2.server.authorization.web;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
@@ -32,18 +34,21 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenIntrospection;
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.http.converter.OAuth2TokenIntrospectionHttpMessageConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2TokenIntrospectionAuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
/**
@@ -65,7 +70,8 @@ public final class OAuth2TokenIntrospectionEndpointFilter extends OncePerRequest
private final AuthenticationManager authenticationManager;
private final RequestMatcher tokenIntrospectionEndpointMatcher;
private AuthenticationConverter authenticationConverter;
private AuthenticationConverter authenticationConverter =
new DefaultTokenIntrospectionAuthenticationConverter();
private final HttpMessageConverter<OAuth2TokenIntrospection> tokenIntrospectionHttpResponseConverter =
new OAuth2TokenIntrospectionHttpMessageConverter();
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter();
@@ -94,7 +100,6 @@ public final class OAuth2TokenIntrospectionEndpointFilter extends OncePerRequest
this.authenticationManager = authenticationManager;
this.tokenIntrospectionEndpointMatcher = new AntPathRequestMatcher(
tokenIntrospectionEndpointUri, HttpMethod.POST.name());
this.authenticationConverter = new OAuth2TokenIntrospectionAuthenticationConverter();
}
@Override
@@ -170,4 +175,47 @@ public final class OAuth2TokenIntrospectionEndpointFilter extends OncePerRequest
this.errorHttpResponseConverter.write(error, null, httpResponse);
}
private static void throwError(String errorCode, String parameterName) {
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Token Introspection Parameter: " + parameterName,
"https://datatracker.ietf.org/doc/html/rfc7662#section-2.1");
throw new OAuth2AuthenticationException(error);
}
private static class DefaultTokenIntrospectionAuthenticationConverter
implements AuthenticationConverter {
@Override
public Authentication convert(HttpServletRequest request) {
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
// token (REQUIRED)
String token = parameters.getFirst(OAuth2ParameterNames.TOKEN);
if (!StringUtils.hasText(token) ||
parameters.get(OAuth2ParameterNames.TOKEN).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.TOKEN);
}
// token_type_hint (OPTIONAL)
String tokenTypeHint = parameters.getFirst(OAuth2ParameterNames.TOKEN_TYPE_HINT);
if (StringUtils.hasText(tokenTypeHint) &&
parameters.get(OAuth2ParameterNames.TOKEN_TYPE_HINT).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.TOKEN_TYPE_HINT);
}
Map<String, Object> additionalParameters = new HashMap<>();
parameters.forEach((key, value) -> {
if (!key.equals(OAuth2ParameterNames.TOKEN) &&
!key.equals(OAuth2ParameterNames.TOKEN_TYPE_HINT)) {
additionalParameters.put(key, value.get(0));
}
});
return new OAuth2TokenIntrospectionAuthenticationToken(
token, clientPrincipal, tokenTypeHint, additionalParameters);
}
}
}

View File

@@ -17,10 +17,10 @@ package org.springframework.security.oauth2.server.authorization.web;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
@@ -32,16 +32,19 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
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.web.authentication.OAuth2TokenRevocationAuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
/**
@@ -63,7 +66,8 @@ public final class OAuth2TokenRevocationEndpointFilter extends OncePerRequestFil
private final AuthenticationManager authenticationManager;
private final RequestMatcher tokenRevocationEndpointMatcher;
private AuthenticationConverter authenticationConverter;
private AuthenticationConverter authenticationConverter =
new DefaultTokenRevocationAuthenticationConverter();
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter =
new OAuth2ErrorHttpMessageConverter();
private AuthenticationSuccessHandler authenticationSuccessHandler = this::sendRevocationSuccessResponse;
@@ -91,7 +95,6 @@ public final class OAuth2TokenRevocationEndpointFilter extends OncePerRequestFil
this.authenticationManager = authenticationManager;
this.tokenRevocationEndpointMatcher = new AntPathRequestMatcher(
tokenRevocationEndpointUri, HttpMethod.POST.name());
this.authenticationConverter = new OAuth2TokenRevocationAuthenticationConverter();
}
@Override
@@ -116,9 +119,9 @@ public final class OAuth2TokenRevocationEndpointFilter extends OncePerRequestFil
/**
* 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 request.
* to an instance of {@link OAuth2TokenRevocationAuthenticationToken} used for authenticating the client.
*
* @param authenticationConverter the {@link AuthenticationConverter} used when attempting to extract a Revoke Token Request from {@link HttpServletRequest}
* @param authenticationConverter the {@link AuthenticationConverter} used when attempting to extract client credentials from {@link HttpServletRequest}
* @since 0.2.2
*/
public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) {
@@ -161,4 +164,36 @@ public final class OAuth2TokenRevocationEndpointFilter extends OncePerRequestFil
this.errorHttpResponseConverter.write(error, null, httpResponse);
}
private static void throwError(String errorCode, String parameterName) {
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Token Revocation Parameter: " + parameterName,
"https://datatracker.ietf.org/doc/html/rfc7009#section-2.1");
throw new OAuth2AuthenticationException(error);
}
private static class DefaultTokenRevocationAuthenticationConverter
implements AuthenticationConverter {
@Override
public Authentication convert(HttpServletRequest request) {
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
// token (REQUIRED)
String token = parameters.getFirst(OAuth2ParameterNames.TOKEN);
if (!StringUtils.hasText(token) ||
parameters.get(OAuth2ParameterNames.TOKEN).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.TOKEN);
}
// token_type_hint (OPTIONAL)
String tokenTypeHint = parameters.getFirst(OAuth2ParameterNames.TOKEN_TYPE_HINT);
if (StringUtils.hasText(tokenTypeHint) &&
parameters.get(OAuth2ParameterNames.TOKEN_TYPE_HINT).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.TOKEN_TYPE_HINT);
}
return new OAuth2TokenRevocationAuthenticationToken(token, clientPrincipal, tokenTypeHint);
}
}
}

View File

@@ -0,0 +1,86 @@
/*
* 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.web;
import java.io.IOException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.oauth2.server.authorization.context.ProviderContext;
import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder;
import org.springframework.security.oauth2.server.authorization.settings.ProviderSettings;
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 ProviderContext} to the {@link ProviderContextHolder}.
*
* @author Joe Grandja
* @since 0.2.2
* @see ProviderContext
* @see ProviderContextHolder
* @see ProviderSettings
*/
public final class ProviderContextFilter extends OncePerRequestFilter {
private final ProviderSettings providerSettings;
/**
* Constructs a {@code ProviderContextFilter} using the provided parameters.
*
* @param providerSettings the provider settings
*/
public ProviderContextFilter(ProviderSettings providerSettings) {
Assert.notNull(providerSettings, "providerSettings cannot be null");
this.providerSettings = providerSettings;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
ProviderContext providerContext = new ProviderContext(
this.providerSettings, () -> resolveIssuer(this.providerSettings, request));
ProviderContextHolder.setProviderContext(providerContext);
filterChain.doFilter(request, response);
} finally {
ProviderContextHolder.resetProviderContext();
}
}
private static String resolveIssuer(ProviderSettings providerSettings, HttpServletRequest request) {
return providerSettings.getIssuer() != null ?
providerSettings.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
}
}

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,7 +19,7 @@ import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;

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.
@@ -17,7 +17,7 @@ package org.springframework.security.oauth2.server.authorization.web.authenticat
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.lang.Nullable;
import org.springframework.security.core.Authentication;

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,7 +19,7 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.lang.Nullable;
import org.springframework.security.core.Authentication;

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.
@@ -17,7 +17,7 @@ package org.springframework.security.oauth2.server.authorization.web.authenticat
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.lang.Nullable;
import org.springframework.security.core.Authentication;

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.
@@ -18,7 +18,7 @@ package org.springframework.security.oauth2.server.authorization.web.authenticat
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.lang.Nullable;
import org.springframework.security.core.Authentication;

View File

@@ -21,7 +21,7 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
@@ -43,7 +43,7 @@ import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/**
* Attempts to extract an Authorization Request from {@link HttpServletRequest}
* Attempts to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
* for the OAuth 2.0 Authorization Code Grant and then converts it to
* an {@link OAuth2AuthorizationCodeRequestAuthenticationToken} used for authenticating the request.
*
@@ -62,19 +62,20 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationConverter impleme
@Override
public Authentication convert(HttpServletRequest request) {
if (!"GET".equals(request.getMethod()) && !OIDC_REQUEST_MATCHER.matches(request)) {
return null;
}
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
// response_type (REQUIRED)
String responseType = request.getParameter(OAuth2ParameterNames.RESPONSE_TYPE);
if (!StringUtils.hasText(responseType) ||
parameters.get(OAuth2ParameterNames.RESPONSE_TYPE).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.RESPONSE_TYPE);
} else if (!responseType.equals(OAuth2AuthorizationResponseType.CODE.getValue())) {
throwError(OAuth2ErrorCodes.UNSUPPORTED_RESPONSE_TYPE, OAuth2ParameterNames.RESPONSE_TYPE);
boolean authorizationRequest = false;
if ("GET".equals(request.getMethod()) || OIDC_REQUEST_MATCHER.matches(request)) {
authorizationRequest = true;
// response_type (REQUIRED)
String responseType = request.getParameter(OAuth2ParameterNames.RESPONSE_TYPE);
if (!StringUtils.hasText(responseType) ||
parameters.get(OAuth2ParameterNames.RESPONSE_TYPE).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.RESPONSE_TYPE);
} else if (!responseType.equals(OAuth2AuthorizationResponseType.CODE.getValue())) {
throwError(OAuth2ErrorCodes.UNSUPPORTED_RESPONSE_TYPE, OAuth2ParameterNames.RESPONSE_TYPE);
}
}
String authorizationUri = request.getRequestURL().toString();
@@ -100,21 +101,37 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationConverter impleme
// scope (OPTIONAL)
Set<String> scopes = null;
String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
if (StringUtils.hasText(scope) &&
parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE);
}
if (StringUtils.hasText(scope)) {
scopes = new HashSet<>(
Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
if (authorizationRequest) {
String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
if (StringUtils.hasText(scope) &&
parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE);
}
if (StringUtils.hasText(scope)) {
scopes = new HashSet<>(
Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
}
} else {
// Consent request
if (parameters.containsKey(OAuth2ParameterNames.SCOPE)) {
scopes = new HashSet<>(parameters.get(OAuth2ParameterNames.SCOPE));
}
}
// state (RECOMMENDED)
// state
// RECOMMENDED for Authorization Request
String state = parameters.getFirst(OAuth2ParameterNames.STATE);
if (StringUtils.hasText(state) &&
parameters.get(OAuth2ParameterNames.STATE).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE);
if (authorizationRequest) {
if (StringUtils.hasText(state) &&
parameters.get(OAuth2ParameterNames.STATE).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE);
}
} else {
// REQUIRED for Authorization Consent Request
if (!StringUtils.hasText(state) ||
parameters.get(OAuth2ParameterNames.STATE).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE);
}
}
// code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE)
@@ -142,8 +159,14 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationConverter impleme
}
});
return new OAuth2AuthorizationCodeRequestAuthenticationToken(authorizationUri, clientId, principal,
redirectUri, state, scopes, additionalParameters);
return OAuth2AuthorizationCodeRequestAuthenticationToken.with(clientId, principal)
.authorizationUri(authorizationUri)
.redirectUri(redirectUri)
.scopes(scopes)
.state(state)
.additionalParameters(additionalParameters)
.consent(!authorizationRequest)
.build();
}
private static RequestMatcher createOidcRequestMatcher() {

View File

@@ -1,109 +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.web.authentication;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
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.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/**
* Attempts to extract an Authorization Consent from {@link HttpServletRequest}
* for the OAuth 2.0 Authorization Code Grant and then converts it to
* an {@link OAuth2AuthorizationConsentAuthenticationToken} used for authenticating the request.
*
* @author Joe Grandja
* @since 0.4.0
* @see AuthenticationConverter
* @see OAuth2AuthorizationConsentAuthenticationToken
* @see OAuth2AuthorizationEndpointFilter
*/
public final class OAuth2AuthorizationConsentAuthenticationConverter implements AuthenticationConverter {
private static final String DEFAULT_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1";
private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken(
"anonymous", "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
@Override
public Authentication convert(HttpServletRequest request) {
if (!"POST".equals(request.getMethod()) ||
request.getParameter(OAuth2ParameterNames.RESPONSE_TYPE) != null) {
return null;
}
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
String authorizationUri = request.getRequestURL().toString();
// client_id (REQUIRED)
String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID);
if (!StringUtils.hasText(clientId) ||
parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID);
}
Authentication principal = SecurityContextHolder.getContext().getAuthentication();
if (principal == null) {
principal = ANONYMOUS_AUTHENTICATION;
}
// state (REQUIRED)
String state = parameters.getFirst(OAuth2ParameterNames.STATE);
if (!StringUtils.hasText(state) ||
parameters.get(OAuth2ParameterNames.STATE).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE);
}
// scope (OPTIONAL)
Set<String> scopes = null;
if (parameters.containsKey(OAuth2ParameterNames.SCOPE)) {
scopes = new HashSet<>(parameters.get(OAuth2ParameterNames.SCOPE));
}
Map<String, Object> additionalParameters = new HashMap<>();
parameters.forEach((key, value) -> {
if (!key.equals(OAuth2ParameterNames.CLIENT_ID) &&
!key.equals(OAuth2ParameterNames.STATE) &&
!key.equals(OAuth2ParameterNames.SCOPE)) {
additionalParameters.put(key, value.get(0));
}
});
return new OAuth2AuthorizationConsentAuthenticationToken(authorizationUri, clientId, principal,
state, scopes, additionalParameters);
}
private static void throwError(String errorCode, String parameterName) {
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, DEFAULT_ERROR_URI);
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null);
}
}

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 java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.lang.Nullable;
import org.springframework.security.core.Authentication;

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,7 +19,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;

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 java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.lang.Nullable;
import org.springframework.security.core.Authentication;

View File

@@ -1,86 +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.web.authentication;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/**
* Attempts to extract an Introspection Request from {@link HttpServletRequest}
* and then converts it to an {@link OAuth2TokenIntrospectionAuthenticationToken} used for authenticating the request.
*
* @author Gerardo Roza
* @author Joe Grandja
* @since 0.4.0
* @see AuthenticationConverter
* @see OAuth2TokenIntrospectionAuthenticationToken
* @see OAuth2TokenIntrospectionEndpointFilter
*/
public final class OAuth2TokenIntrospectionAuthenticationConverter implements AuthenticationConverter {
@Override
public Authentication convert(HttpServletRequest request) {
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
// token (REQUIRED)
String token = parameters.getFirst(OAuth2ParameterNames.TOKEN);
if (!StringUtils.hasText(token) ||
parameters.get(OAuth2ParameterNames.TOKEN).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.TOKEN);
}
// token_type_hint (OPTIONAL)
String tokenTypeHint = parameters.getFirst(OAuth2ParameterNames.TOKEN_TYPE_HINT);
if (StringUtils.hasText(tokenTypeHint) &&
parameters.get(OAuth2ParameterNames.TOKEN_TYPE_HINT).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.TOKEN_TYPE_HINT);
}
Map<String, Object> additionalParameters = new HashMap<>();
parameters.forEach((key, value) -> {
if (!key.equals(OAuth2ParameterNames.TOKEN) &&
!key.equals(OAuth2ParameterNames.TOKEN_TYPE_HINT)) {
additionalParameters.put(key, value.get(0));
}
});
return new OAuth2TokenIntrospectionAuthenticationToken(
token, clientPrincipal, tokenTypeHint, additionalParameters);
}
private static void throwError(String errorCode, String parameterName) {
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Token Introspection Parameter: " + parameterName,
"https://datatracker.ietf.org/doc/html/rfc7662#section-2.1");
throw new OAuth2AuthenticationException(error);
}
}

View File

@@ -1,74 +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.web.authentication;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/**
* Attempts to extract a Revoke Token Request from {@link HttpServletRequest}
* and then converts it to an {@link OAuth2TokenRevocationAuthenticationToken} used for authenticating the request.
*
* @author Vivek Babu
* @author Joe Grandja
* @since 0.4.0
* @see AuthenticationConverter
* @see OAuth2TokenRevocationAuthenticationToken
* @see OAuth2TokenRevocationEndpointFilter
*/
public final class OAuth2TokenRevocationAuthenticationConverter implements AuthenticationConverter {
@Override
public Authentication convert(HttpServletRequest request) {
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
// token (REQUIRED)
String token = parameters.getFirst(OAuth2ParameterNames.TOKEN);
if (!StringUtils.hasText(token) ||
parameters.get(OAuth2ParameterNames.TOKEN).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.TOKEN);
}
// token_type_hint (OPTIONAL)
String tokenTypeHint = parameters.getFirst(OAuth2ParameterNames.TOKEN_TYPE_HINT);
if (StringUtils.hasText(tokenTypeHint) &&
parameters.get(OAuth2ParameterNames.TOKEN_TYPE_HINT).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.TOKEN_TYPE_HINT);
}
return new OAuth2TokenRevocationAuthenticationToken(token, clientPrincipal, tokenTypeHint);
}
private static void throwError(String errorCode, String parameterName) {
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Token Revocation Parameter: " + parameterName,
"https://datatracker.ietf.org/doc/html/rfc7009#section-2.1");
throw new OAuth2AuthenticationException(error);
}
}

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.
@@ -17,7 +17,7 @@ package org.springframework.security.oauth2.server.authorization.web.authenticat
import java.util.HashMap;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.lang.Nullable;
import org.springframework.security.core.Authentication;

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;
@@ -184,26 +182,6 @@ public class ClientSecretAuthenticationProviderTests {
verify(this.passwordEncoder).matches(any(), any());
}
@Test
public void authenticateWhenExpiredClientSecretThenThrowOAuth2AuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.clientSecretExpiresAt(Instant.now().minus(1, ChronoUnit.HOURS).truncatedTo(ChronoUnit.SECONDS))
.build();
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret(), null);
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthenticationException.class)
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
.satisfies(error -> {
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
assertThat(error.getDescription()).contains("client_secret_expires_at");
});
verify(this.passwordEncoder).matches(any(), any());
}
@Test
public void authenticateWhenValidCredentialsThenAuthenticated() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();

View File

@@ -57,10 +57,10 @@ import org.springframework.security.oauth2.server.authorization.TestOAuth2Author
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.client.TestRegisteredClients;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.context.ProviderContext;
import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.ProviderSettings;
import org.springframework.web.util.UriComponentsBuilder;
import static org.assertj.core.api.Assertions.assertThat;
@@ -91,7 +91,7 @@ public class JwtClientAssertionAuthenticationProviderTests {
private RegisteredClientRepository registeredClientRepository;
private OAuth2AuthorizationService authorizationService;
private JwtClientAssertionAuthenticationProvider authenticationProvider;
private AuthorizationServerSettings authorizationServerSettings;
private ProviderSettings providerSettings;
@Before
public void setUp() {
@@ -99,8 +99,8 @@ public class JwtClientAssertionAuthenticationProviderTests {
this.authorizationService = mock(OAuth2AuthorizationService.class);
this.authenticationProvider = new JwtClientAssertionAuthenticationProvider(
this.registeredClientRepository, this.authorizationService);
this.authorizationServerSettings = AuthorizationServerSettings.builder().issuer("https://auth-server.com").build();
AuthorizationServerContextHolder.setContext(new TestAuthorizationServerContext(this.authorizationServerSettings, null));
this.providerSettings = ProviderSettings.builder().issuer("https://auth-server.com").build();
ProviderContextHolder.setProviderContext(new ProviderContext(this.providerSettings, null));
}
@Test
@@ -421,7 +421,7 @@ public class JwtClientAssertionAuthenticationProviderTests {
return JwtClaimsSet.builder()
.issuer(registeredClient.getClientId())
.subject(registeredClient.getClientId())
.audience(Collections.singletonList(asUrl(this.authorizationServerSettings.getIssuer(), this.authorizationServerSettings.getTokenEndpoint())))
.audience(Collections.singletonList(asUrl(this.providerSettings.getIssuer(), this.providerSettings.getTokenEndpoint())))
.issuedAt(issuedAt)
.expiresAt(expiresAt);
}

View File

@@ -54,10 +54,10 @@ import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.context.ProviderContext;
import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
import org.springframework.security.oauth2.server.authorization.settings.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
@@ -118,13 +118,13 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
});
this.authenticationProvider = new OAuth2AuthorizationCodeAuthenticationProvider(
this.authorizationService, this.tokenGenerator);
AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().issuer("https://provider.com").build();
AuthorizationServerContextHolder.setContext(new TestAuthorizationServerContext(authorizationServerSettings, null));
ProviderSettings providerSettings = ProviderSettings.builder().issuer("https://provider.com").build();
ProviderContextHolder.setProviderContext(new ProviderContext(providerSettings, null));
}
@After
public void cleanup() {
AuthorizationServerContextHolder.resetContext();
ProviderContextHolder.resetProviderContext();
}
@Test

View File

@@ -18,9 +18,11 @@ package org.springframework.security.oauth2.server.authorization.authentication;
import java.security.Principal;
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.junit.Before;
import org.junit.Test;
@@ -41,13 +43,15 @@ 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.TestOAuth2Authorizations;
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.client.TestRegisteredClients;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.context.ProviderContext;
import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import static org.assertj.core.api.Assertions.assertThat;
@@ -55,6 +59,8 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -65,8 +71,7 @@ import static org.mockito.Mockito.when;
* @author Steve Riesenberg
*/
public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
private static final String AUTHORIZATION_URI = "https://provider.com/oauth2/authorize";
private static final String STATE = "state";
private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE);
private RegisteredClientRepository registeredClientRepository;
private OAuth2AuthorizationService authorizationService;
private OAuth2AuthorizationConsentService authorizationConsentService;
@@ -82,8 +87,8 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
this.registeredClientRepository, this.authorizationService, this.authorizationConsentService);
this.principal = new TestingAuthenticationToken("principalName", "password");
this.principal.setAuthenticated(true);
AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().issuer("https://provider.com").build();
AuthorizationServerContextHolder.setContext(new TestAuthorizationServerContext(authorizationServerSettings, null));
ProviderSettings providerSettings = ProviderSettings.builder().issuer("https://provider.com").build();
ProviderContextHolder.setProviderContext(new ProviderContext(providerSettings, null));
}
@Test
@@ -123,19 +128,25 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
}
@Test
public void setAuthenticationValidatorWhenNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> this.authenticationProvider.setAuthenticationValidator(null))
public void setAuthenticationValidatorResolverWhenNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> this.authenticationProvider.setAuthenticationValidatorResolver(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("authenticationValidator cannot be null");
.hasMessage("authenticationValidatorResolver cannot be null");
}
@Test
public void setAuthorizationConsentCustomizerWhenNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> this.authenticationProvider.setAuthorizationConsentCustomizer(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("authorizationConsentCustomizer cannot be null");
}
@Test
public void authenticateWhenInvalidClientIdThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
authorizationCodeRequestAuthentication(registeredClient, this.principal)
.build();
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.satisfies(ex ->
@@ -151,9 +162,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
"https:///invalid", STATE, registeredClient.getScopes(), null);
authorizationCodeRequestAuthentication(registeredClient, this.principal)
.redirectUri("https:///invalid")
.build();
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.satisfies(ex ->
@@ -169,9 +180,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
"https://example.com#fragment", STATE, registeredClient.getScopes(), null);
authorizationCodeRequestAuthentication(registeredClient, this.principal)
.redirectUri("https://example.com#fragment")
.build();
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.satisfies(ex ->
@@ -187,9 +198,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
"https://localhost:5000", STATE, registeredClient.getScopes(), null);
authorizationCodeRequestAuthentication(registeredClient, this.principal)
.redirectUri("https://localhost:5000")
.build();
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.satisfies(ex ->
@@ -207,9 +218,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
"https://invalid-example.com", STATE, registeredClient.getScopes(), null);
authorizationCodeRequestAuthentication(registeredClient, this.principal)
.redirectUri("https://invalid-example.com")
.build();
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.satisfies(ex ->
@@ -227,9 +238,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
"https://127.0.0.1:5000", STATE, registeredClient.getScopes(), null);
authorizationCodeRequestAuthentication(registeredClient, this.principal)
.redirectUri("https://127.0.0.1:5000")
.build();
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
@@ -246,9 +257,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
"https://[::1]:5000", STATE, registeredClient.getScopes(), null);
authorizationCodeRequestAuthentication(registeredClient, this.principal)
.redirectUri("https://[::1]:5000")
.build();
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
@@ -262,9 +273,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
null, STATE, registeredClient.getScopes(), null);
authorizationCodeRequestAuthentication(registeredClient, this.principal)
.redirectUri(null)
.build();
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.satisfies(ex ->
@@ -282,9 +293,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
null, STATE, registeredClient.getScopes(), null);
authorizationCodeRequestAuthentication(registeredClient, this.principal)
.redirectUri(null)
.build();
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.satisfies(ex ->
@@ -302,9 +313,8 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
authorizationCodeRequestAuthentication(registeredClient, this.principal)
.build();
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.satisfies(ex ->
@@ -320,10 +330,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
registeredClient.getRedirectUris().iterator().next(), STATE,
Collections.singleton("invalid-scope"), null);
authorizationCodeRequestAuthentication(registeredClient, this.principal)
.scopes(Collections.singleton("invalid-scope"))
.build();
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.satisfies(ex ->
@@ -340,9 +349,8 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
authorizationCodeRequestAuthentication(registeredClient, this.principal)
.build();
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.satisfies(ex ->
@@ -360,9 +368,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, "code-challenge");
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "unsupported");
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), additionalParameters);
authorizationCodeRequestAuthentication(registeredClient, this.principal)
.additionalParameters(additionalParameters)
.build();
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.satisfies(ex ->
@@ -380,9 +388,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
Map<String, Object> additionalParameters = new HashMap<>();
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, "code-challenge");
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), additionalParameters);
authorizationCodeRequestAuthentication(registeredClient, this.principal)
.additionalParameters(additionalParameters)
.build();
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.satisfies(ex ->
@@ -399,9 +407,8 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
this.principal.setAuthenticated(false);
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
authorizationCodeRequestAuthentication(registeredClient, this.principal)
.build();
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
@@ -419,12 +426,11 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
.thenReturn(registeredClient);
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
authorizationCodeRequestAuthentication(registeredClient, this.principal)
.build();
OAuth2AuthorizationConsentAuthenticationToken authenticationResult =
(OAuth2AuthorizationConsentAuthenticationToken) this.authenticationProvider.authenticate(authentication);
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
verify(this.authorizationService).save(authorizationCaptor.capture());
@@ -453,6 +459,8 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
assertThat(authenticationResult.getAuthorizationUri()).isEqualTo(authorizationRequest.getAuthorizationUri());
assertThat(authenticationResult.getScopes()).isEmpty();
assertThat(authenticationResult.getState()).isEqualTo(state);
assertThat(authenticationResult.isConsentRequired()).isTrue();
assertThat(authenticationResult.getAuthorizationCode()).isNull();
assertThat(authenticationResult.isAuthenticated()).isTrue();
}
@@ -469,9 +477,8 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
.thenReturn(registeredClient);
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
authorizationCodeRequestAuthentication(registeredClient, this.principal)
.build();
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
@@ -495,9 +502,8 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
.thenReturn(previousAuthorizationConsent);
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
authorizationCodeRequestAuthentication(registeredClient, this.principal)
.build();
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
@@ -515,9 +521,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, "code-challenge");
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), additionalParameters);
authorizationCodeRequestAuthentication(registeredClient, this.principal)
.additionalParameters(additionalParameters)
.build();
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
@@ -536,9 +542,8 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
this.authenticationProvider.setAuthorizationCodeGenerator(authorizationCodeGenerator);
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
authorizationCodeRequestAuthentication(registeredClient, this.principal)
.build();
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
@@ -550,26 +555,28 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
}
@Test
public void authenticateWhenCustomAuthenticationValidatorThenUsed() {
public void authenticateWhenCustomAuthenticationValidatorResolverThenUsed() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
@SuppressWarnings("unchecked")
Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator = mock(Consumer.class);
this.authenticationProvider.setAuthenticationValidator(authenticationValidator);
Function<String, OAuth2AuthenticationValidator> authenticationValidatorResolver = mock(Function.class);
this.authenticationProvider.setAuthenticationValidatorResolver(authenticationValidatorResolver);
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
authorizationCodeRequestAuthentication(registeredClient, this.principal)
.build();
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
assertAuthorizationCodeRequestWithAuthorizationCodeResult(registeredClient, authentication, authenticationResult);
verify(authenticationValidator).accept(any());
ArgumentCaptor<String> parameterNameCaptor = ArgumentCaptor.forClass(String.class);
verify(authenticationValidatorResolver, times(2)).apply(parameterNameCaptor.capture());
assertThat(parameterNameCaptor.getAllValues()).containsExactly(
OAuth2ParameterNames.REDIRECT_URI, OAuth2ParameterNames.SCOPE);
}
private void assertAuthorizationCodeRequestWithAuthorizationCodeResult(
@@ -609,6 +616,410 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
assertThat(authenticationResult.isAuthenticated()).isTrue();
}
@Test
public void authenticateWhenConsentRequestInvalidStateThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.build();
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
authorizationConsentRequestAuthentication(registeredClient, this.principal)
.build();
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
.thenReturn(null);
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.satisfies(ex ->
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE, null)
);
}
@Test
public void authenticateWhenConsentRequestPrincipalNotAuthenticatedThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.build();
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
.principalName(this.principal.getName())
.build();
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
authorizationConsentRequestAuthentication(registeredClient, this.principal)
.build();
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
.thenReturn(authorization);
this.principal.setAuthenticated(false);
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.satisfies(ex ->
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE, null)
);
}
@Test
public void authenticateWhenConsentRequestInvalidPrincipalThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.build();
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
.principalName(this.principal.getName().concat("-other"))
.build();
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
authorizationConsentRequestAuthentication(registeredClient, this.principal)
.build();
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
.thenReturn(authorization);
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.satisfies(ex ->
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE, null)
);
}
@Test
public void authenticateWhenConsentRequestInvalidClientIdThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.build();
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
.principalName(this.principal.getName())
.build();
when(this.authorizationService.findByToken(eq("state"), eq(STATE_TOKEN_TYPE)))
.thenReturn(authorization);
RegisteredClient otherRegisteredClient = TestRegisteredClients.registeredClient2()
.build();
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
authorizationConsentRequestAuthentication(otherRegisteredClient, this.principal)
.state("state")
.build();
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.satisfies(ex ->
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID, null)
);
}
@Test
public void authenticateWhenConsentRequestDoesNotMatchClientThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.build();
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
RegisteredClient otherRegisteredClient = TestRegisteredClients.registeredClient2()
.build();
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(otherRegisteredClient)
.principalName(this.principal.getName())
.build();
when(this.authorizationService.findByToken(eq("state"), eq(STATE_TOKEN_TYPE)))
.thenReturn(authorization);
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
authorizationConsentRequestAuthentication(registeredClient, this.principal)
.state("state")
.build();
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.satisfies(ex ->
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID, null)
);
}
@Test
public void authenticateWhenConsentRequestScopeNotRequestedThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.build();
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
.principalName(this.principal.getName())
.build();
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
Set<String> authorizedScopes = new HashSet<>(authorizationRequest.getScopes());
authorizedScopes.add("scope-not-requested");
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
authorizationConsentRequestAuthentication(registeredClient, this.principal)
.scopes(authorizedScopes)
.build();
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
.thenReturn(authorization);
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.satisfies(ex ->
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE, authorizationRequest.getRedirectUri())
);
}
@Test
public void authenticateWhenConsentRequestNotApprovedThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.build();
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
.principalName(this.principal.getName())
.build();
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
authorizationConsentRequestAuthentication(registeredClient, this.principal)
.scopes(new HashSet<>()) // No scopes approved
.build();
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
.thenReturn(authorization);
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.satisfies(ex ->
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID, authorizationRequest.getRedirectUri())
);
verify(this.authorizationService).remove(eq(authorization));
}
@Test
public void authenticateWhenConsentRequestApproveAllThenReturnAuthorizationCode() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.build();
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
.principalName(this.principal.getName())
.build();
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
Set<String> authorizedScopes = authorizationRequest.getScopes();
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
authorizationConsentRequestAuthentication(registeredClient, this.principal)
.scopes(authorizedScopes) // Approve all scopes
.build();
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
.thenReturn(authorization);
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
assertAuthorizationConsentRequestWithAuthorizationCodeResult(registeredClient, authorization, authenticationResult);
}
@Test
public void authenticateWhenCustomAuthorizationConsentCustomizerThenUsed() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.build();
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
.principalName(this.principal.getName())
.build();
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
Set<String> authorizedScopes = authorizationRequest.getScopes();
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
authorizationConsentRequestAuthentication(registeredClient, this.principal)
.scopes(authorizedScopes) // Approve all scopes
.build();
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
.thenReturn(authorization);
@SuppressWarnings("unchecked")
Consumer<OAuth2AuthorizationConsentAuthenticationContext> authorizationConsentCustomizer = mock(Consumer.class);
this.authenticationProvider.setAuthorizationConsentCustomizer(authorizationConsentCustomizer);
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
assertAuthorizationConsentRequestWithAuthorizationCodeResult(registeredClient, authorization, authenticationResult);
ArgumentCaptor<OAuth2AuthorizationConsentAuthenticationContext> authenticationContextCaptor =
ArgumentCaptor.forClass(OAuth2AuthorizationConsentAuthenticationContext.class);
verify(authorizationConsentCustomizer).accept(authenticationContextCaptor.capture());
OAuth2AuthorizationConsentAuthenticationContext authenticationContext = authenticationContextCaptor.getValue();
assertThat(authenticationContext.<Authentication>getAuthentication()).isEqualTo(authentication);
assertThat(authenticationContext.getAuthorizationConsent()).isNotNull();
assertThat(authenticationContext.getRegisteredClient()).isEqualTo(registeredClient);
assertThat(authenticationContext.getAuthorization()).isEqualTo(authorization);
assertThat(authenticationContext.getAuthorizationRequest()).isEqualTo(authorizationRequest);
}
private void assertAuthorizationConsentRequestWithAuthorizationCodeResult(
RegisteredClient registeredClient,
OAuth2Authorization authorization,
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult) {
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
Set<String> authorizedScopes = authorizationRequest.getScopes();
ArgumentCaptor<OAuth2AuthorizationConsent> authorizationConsentCaptor = ArgumentCaptor.forClass(OAuth2AuthorizationConsent.class);
verify(this.authorizationConsentService).save(authorizationConsentCaptor.capture());
OAuth2AuthorizationConsent authorizationConsent = authorizationConsentCaptor.getValue();
assertThat(authorizationConsent.getRegisteredClientId()).isEqualTo(authorization.getRegisteredClientId());
assertThat(authorizationConsent.getPrincipalName()).isEqualTo(authorization.getPrincipalName());
assertThat(authorizationConsent.getAuthorities()).hasSize(authorizedScopes.size());
assertThat(authorizationConsent.getScopes()).containsExactlyInAnyOrderElementsOf(authorizedScopes);
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
verify(this.authorizationService).save(authorizationCaptor.capture());
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
assertThat(updatedAuthorization.getRegisteredClientId()).isEqualTo(authorization.getRegisteredClientId());
assertThat(updatedAuthorization.getPrincipalName()).isEqualTo(authorization.getPrincipalName());
assertThat(updatedAuthorization.getAuthorizationGrantType()).isEqualTo(authorization.getAuthorizationGrantType());
assertThat(updatedAuthorization.<Authentication>getAttribute(Principal.class.getName()))
.isEqualTo(authorization.<Authentication>getAttribute(Principal.class.getName()));
assertThat(updatedAuthorization.<OAuth2AuthorizationRequest>getAttribute(OAuth2AuthorizationRequest.class.getName()))
.isEqualTo(authorizationRequest);
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = updatedAuthorization.getToken(OAuth2AuthorizationCode.class);
assertThat(authorizationCode).isNotNull();
assertThat(updatedAuthorization.<String>getAttribute(OAuth2ParameterNames.STATE)).isNull();
assertThat(updatedAuthorization.getAuthorizedScopes()).isEqualTo(authorizedScopes);
assertThat(authenticationResult.getClientId()).isEqualTo(registeredClient.getClientId());
assertThat(authenticationResult.getPrincipal()).isEqualTo(this.principal);
assertThat(authenticationResult.getAuthorizationUri()).isEqualTo(authorizationRequest.getAuthorizationUri());
assertThat(authenticationResult.getRedirectUri()).isEqualTo(authorizationRequest.getRedirectUri());
assertThat(authenticationResult.getScopes()).isEqualTo(authorizedScopes);
assertThat(authenticationResult.getState()).isEqualTo(authorizationRequest.getState());
assertThat(authenticationResult.getAuthorizationCode()).isEqualTo(authorizationCode.getToken());
assertThat(authenticationResult.isAuthenticated()).isTrue();
}
@Test
public void authenticateWhenConsentRequestApproveNoneAndRevokePreviouslyApprovedThenAuthorizationConsentRemoved() {
String previouslyApprovedScope = "message.read";
String requestedScope = "message.write";
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.scopes(scopes -> {
scopes.clear();
scopes.add(previouslyApprovedScope);
scopes.add(requestedScope);
})
.build();
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
.principalName(this.principal.getName())
.build();
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
authorizationConsentRequestAuthentication(registeredClient, this.principal)
.scopes(new HashSet<>()) // No scopes approved
.build();
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
.thenReturn(authorization);
OAuth2AuthorizationConsent previousAuthorizationConsent =
OAuth2AuthorizationConsent.withId(authorization.getRegisteredClientId(), authorization.getPrincipalName())
.scope(previouslyApprovedScope)
.build();
when(this.authorizationConsentService.findById(eq(authorization.getRegisteredClientId()), eq(authorization.getPrincipalName())))
.thenReturn(previousAuthorizationConsent);
// Revoke all (including previously approved)
this.authenticationProvider.setAuthorizationConsentCustomizer((authorizationConsentContext) ->
authorizationConsentContext.getAuthorizationConsent().authorities(Set::clear));
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.satisfies(ex ->
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID, authorizationRequest.getRedirectUri())
);
verify(this.authorizationConsentService).remove(eq(previousAuthorizationConsent));
verify(this.authorizationService).remove(eq(authorization));
}
@Test
public void authenticateWhenConsentRequestApproveSomeAndPreviouslyApprovedThenAuthorizationConsentUpdated() {
String previouslyApprovedScope = "message.read";
String requestedScope = "message.write";
String otherPreviouslyApprovedScope = "other.scope";
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.scopes(scopes -> {
scopes.clear();
scopes.add(previouslyApprovedScope);
scopes.add(requestedScope);
})
.build();
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
.principalName(this.principal.getName())
.build();
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
Set<String> requestedScopes = authorizationRequest.getScopes();
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
authorizationConsentRequestAuthentication(registeredClient, this.principal)
.scopes(requestedScopes)
.build();
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
.thenReturn(authorization);
OAuth2AuthorizationConsent previousAuthorizationConsent =
OAuth2AuthorizationConsent.withId(authorization.getRegisteredClientId(), authorization.getPrincipalName())
.scope(previouslyApprovedScope)
.scope(otherPreviouslyApprovedScope)
.build();
when(this.authorizationConsentService.findById(eq(authorization.getRegisteredClientId()), eq(authorization.getPrincipalName())))
.thenReturn(previousAuthorizationConsent);
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
ArgumentCaptor<OAuth2AuthorizationConsent> authorizationConsentCaptor = ArgumentCaptor.forClass(OAuth2AuthorizationConsent.class);
verify(this.authorizationConsentService).save(authorizationConsentCaptor.capture());
OAuth2AuthorizationConsent updatedAuthorizationConsent = authorizationConsentCaptor.getValue();
assertThat(updatedAuthorizationConsent.getRegisteredClientId()).isEqualTo(previousAuthorizationConsent.getRegisteredClientId());
assertThat(updatedAuthorizationConsent.getPrincipalName()).isEqualTo(previousAuthorizationConsent.getPrincipalName());
assertThat(updatedAuthorizationConsent.getScopes()).containsExactlyInAnyOrder(
previouslyApprovedScope, otherPreviouslyApprovedScope, requestedScope);
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
verify(this.authorizationService).save(authorizationCaptor.capture());
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
assertThat(updatedAuthorization.getAuthorizedScopes()).isEqualTo(requestedScopes);
assertThat(authenticationResult.getScopes()).isEqualTo(requestedScopes);
}
@Test
public void authenticateWhenConsentRequestApproveNoneAndPreviouslyApprovedThenAuthorizationConsentNotUpdated() {
String previouslyApprovedScope = "message.read";
String requestedScope = "message.write";
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.scopes(scopes -> {
scopes.clear();
scopes.add(previouslyApprovedScope);
scopes.add(requestedScope);
})
.build();
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
.principalName(this.principal.getName())
.build();
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
authorizationConsentRequestAuthentication(registeredClient, this.principal)
.scopes(new HashSet<>()) // No scopes approved
.build();
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
.thenReturn(authorization);
OAuth2AuthorizationConsent previousAuthorizationConsent =
OAuth2AuthorizationConsent.withId(authorization.getRegisteredClientId(), authorization.getPrincipalName())
.scope(previouslyApprovedScope)
.build();
when(this.authorizationConsentService.findById(eq(authorization.getRegisteredClientId()), eq(authorization.getPrincipalName())))
.thenReturn(previousAuthorizationConsent);
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
verify(this.authorizationConsentService, never()).save(any());
assertThat(authenticationResult.getScopes()).isEqualTo(Collections.singleton(previouslyApprovedScope));
}
private static void assertAuthenticationException(OAuth2AuthorizationCodeRequestAuthenticationException authenticationException,
String errorCode, String parameterName, String redirectUri) {
@@ -619,6 +1030,30 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
authenticationException.getAuthorizationCodeRequestAuthentication();
assertThat(authorizationCodeRequestAuthentication.getRedirectUri()).isEqualTo(redirectUri);
// gh-595
if (OAuth2ErrorCodes.ACCESS_DENIED.equals(errorCode)) {
assertThat(authorizationCodeRequestAuthentication.isConsent()).isFalse();
assertThat(authorizationCodeRequestAuthentication.isConsentRequired()).isFalse();
}
}
private static OAuth2AuthorizationCodeRequestAuthenticationToken.Builder authorizationCodeRequestAuthentication(
RegisteredClient registeredClient, Authentication principal) {
return OAuth2AuthorizationCodeRequestAuthenticationToken.with(registeredClient.getClientId(), principal)
.authorizationUri("https://provider.com/oauth2/authorize")
.redirectUri(registeredClient.getRedirectUris().iterator().next())
.scopes(registeredClient.getScopes())
.state("state");
}
private static OAuth2AuthorizationCodeRequestAuthenticationToken.Builder authorizationConsentRequestAuthentication(
RegisteredClient registeredClient, Authentication principal) {
return OAuth2AuthorizationCodeRequestAuthenticationToken.with(registeredClient.getClientId(), principal)
.authorizationUri("https://provider.com/oauth2/authorize")
.scopes(registeredClient.getScopes())
.state("state")
.consent(true);
}
}

View File

@@ -38,57 +38,61 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
*/
public class OAuth2AuthorizationCodeRequestAuthenticationTokenTests {
private static final String AUTHORIZATION_URI = "https://provider.com/oauth2/authorize";
private static final String STATE = "state";
private static final RegisteredClient REGISTERED_CLIENT = TestRegisteredClients.registeredClient().build();
private static final TestingAuthenticationToken PRINCIPAL = new TestingAuthenticationToken("principalName", "password");
private static final OAuth2AuthorizationCode AUTHORIZATION_CODE =
new OAuth2AuthorizationCode("code", Instant.now(), Instant.now().plus(5, ChronoUnit.MINUTES));
@Test
public void constructorWhenAuthorizationUriNotProvidedThenThrowIllegalArgumentException() {
assertThatThrownBy(() ->
new OAuth2AuthorizationCodeRequestAuthenticationToken(null, REGISTERED_CLIENT.getClientId(), PRINCIPAL,
null, null, (Set<String>) null, null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("authorizationUri cannot be empty");
}
@Test
public void constructorWhenClientIdNotProvidedThenThrowIllegalArgumentException() {
assertThatThrownBy(() ->
new OAuth2AuthorizationCodeRequestAuthenticationToken(AUTHORIZATION_URI, null, PRINCIPAL,
null, null, (Set<String>) null, null))
public void withWhenClientIdNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> OAuth2AuthorizationCodeRequestAuthenticationToken.with(null, PRINCIPAL))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("clientId cannot be empty");
}
@Test
public void constructorWhenPrincipalNotProvidedThenThrowIllegalArgumentException() {
assertThatThrownBy(() ->
new OAuth2AuthorizationCodeRequestAuthenticationToken(AUTHORIZATION_URI, REGISTERED_CLIENT.getClientId(), null,
null, null, (Set<String>) null, null))
public void withWhenPrincipalNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> OAuth2AuthorizationCodeRequestAuthenticationToken.with(REGISTERED_CLIENT.getClientId(), null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("principal cannot be null");
}
@Test
public void constructorWhenAuthorizationCodeNotProvidedThenThrowIllegalArgumentException() {
public void buildWhenAuthorizationUriNotProvidedThenThrowIllegalArgumentException() {
assertThatThrownBy(() ->
new OAuth2AuthorizationCodeRequestAuthenticationToken(AUTHORIZATION_URI, REGISTERED_CLIENT.getClientId(), PRINCIPAL,
null, null, null, (Set<String>) null))
OAuth2AuthorizationCodeRequestAuthenticationToken.with(REGISTERED_CLIENT.getClientId(), PRINCIPAL)
.build())
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("authorizationCode cannot be null");
.hasMessage("authorizationUri cannot be empty");
}
@Test
public void constructorWhenAuthorizationRequestThenValuesAreSet() {
public void buildWhenStateNotProvidedThenThrowIllegalArgumentException() {
assertThatThrownBy(() ->
OAuth2AuthorizationCodeRequestAuthenticationToken.with(REGISTERED_CLIENT.getClientId(), PRINCIPAL)
.authorizationUri(AUTHORIZATION_URI)
.consent(true)
.build())
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("state cannot be empty");
}
@Test
public void buildWhenAuthorizationCodeRequestThenValuesAreSet() {
String clientId = REGISTERED_CLIENT.getClientId();
String redirectUri = REGISTERED_CLIENT.getRedirectUris().iterator().next();
String state = "state";
Set<String> requestedScopes = REGISTERED_CLIENT.getScopes();
Map<String, Object> additionalParameters = Collections.singletonMap("param1", "value1");
OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, clientId, PRINCIPAL, redirectUri, state, requestedScopes, additionalParameters);
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
OAuth2AuthorizationCodeRequestAuthenticationToken.with(clientId, PRINCIPAL)
.authorizationUri(AUTHORIZATION_URI)
.redirectUri(redirectUri)
.scopes(requestedScopes)
.state(STATE)
.additionalParameters(additionalParameters)
.build();
assertThat(authentication.getPrincipal()).isEqualTo(PRINCIPAL);
assertThat(authentication.getCredentials()).isEqualTo("");
@@ -96,22 +100,87 @@ public class OAuth2AuthorizationCodeRequestAuthenticationTokenTests {
assertThat(authentication.getAuthorizationUri()).isEqualTo(AUTHORIZATION_URI);
assertThat(authentication.getClientId()).isEqualTo(clientId);
assertThat(authentication.getRedirectUri()).isEqualTo(redirectUri);
assertThat(authentication.getState()).isEqualTo(state);
assertThat(authentication.getScopes()).containsExactlyInAnyOrderElementsOf(requestedScopes);
assertThat(authentication.getState()).isEqualTo(STATE);
assertThat(authentication.getAdditionalParameters()).containsExactlyInAnyOrderEntriesOf(additionalParameters);
assertThat(authentication.isConsentRequired()).isFalse();
assertThat(authentication.isConsent()).isFalse();
assertThat(authentication.getAuthorizationCode()).isNull();
assertThat(authentication.isAuthenticated()).isFalse();
}
@Test
public void constructorWhenAuthorizationResponseThenValuesAreSet() {
public void buildWhenAuthorizationConsentRequiredThenValuesAreSet() {
String clientId = REGISTERED_CLIENT.getClientId();
String redirectUri = REGISTERED_CLIENT.getRedirectUris().iterator().next();
String state = "state";
Set<String> authorizedScopes = REGISTERED_CLIENT.getScopes();
OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, clientId, PRINCIPAL, AUTHORIZATION_CODE, redirectUri, state, authorizedScopes);
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
OAuth2AuthorizationCodeRequestAuthenticationToken.with(clientId, PRINCIPAL)
.authorizationUri(AUTHORIZATION_URI)
.scopes(authorizedScopes)
.state(STATE)
.consentRequired(true)
.build();
assertThat(authentication.getPrincipal()).isEqualTo(PRINCIPAL);
assertThat(authentication.getCredentials()).isEqualTo("");
assertThat(authentication.getAuthorities()).isEmpty();
assertThat(authentication.getAuthorizationUri()).isEqualTo(AUTHORIZATION_URI);
assertThat(authentication.getClientId()).isEqualTo(clientId);
assertThat(authentication.getRedirectUri()).isNull();
assertThat(authentication.getScopes()).containsExactlyInAnyOrderElementsOf(authorizedScopes);
assertThat(authentication.getState()).isEqualTo(STATE);
assertThat(authentication.getAdditionalParameters()).isEmpty();
assertThat(authentication.isConsentRequired()).isTrue();
assertThat(authentication.isConsent()).isFalse();
assertThat(authentication.getAuthorizationCode()).isNull();
assertThat(authentication.isAuthenticated()).isTrue();
}
@Test
public void buildWhenAuthorizationConsentRequestThenValuesAreSet() {
String clientId = REGISTERED_CLIENT.getClientId();
Set<String> authorizedScopes = REGISTERED_CLIENT.getScopes();
Map<String, Object> additionalParameters = Collections.singletonMap("param1", "value1");
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
OAuth2AuthorizationCodeRequestAuthenticationToken.with(clientId, PRINCIPAL)
.authorizationUri(AUTHORIZATION_URI)
.scopes(authorizedScopes)
.state(STATE)
.additionalParameters(additionalParameters)
.consent(true)
.build();
assertThat(authentication.getPrincipal()).isEqualTo(PRINCIPAL);
assertThat(authentication.getCredentials()).isEqualTo("");
assertThat(authentication.getAuthorities()).isEmpty();
assertThat(authentication.getAuthorizationUri()).isEqualTo(AUTHORIZATION_URI);
assertThat(authentication.getClientId()).isEqualTo(clientId);
assertThat(authentication.getRedirectUri()).isNull();
assertThat(authentication.getScopes()).containsExactlyInAnyOrderElementsOf(authorizedScopes);
assertThat(authentication.getState()).isEqualTo(STATE);
assertThat(authentication.getAdditionalParameters()).containsExactlyInAnyOrderEntriesOf(additionalParameters);
assertThat(authentication.isConsentRequired()).isFalse();
assertThat(authentication.isConsent()).isTrue();
assertThat(authentication.getAuthorizationCode()).isNull();
assertThat(authentication.isAuthenticated()).isFalse();
}
@Test
public void buildWhenAuthorizationResponseThenValuesAreSet() {
String clientId = REGISTERED_CLIENT.getClientId();
String redirectUri = REGISTERED_CLIENT.getRedirectUris().iterator().next();
Set<String> authorizedScopes = REGISTERED_CLIENT.getScopes();
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
OAuth2AuthorizationCodeRequestAuthenticationToken.with(clientId, PRINCIPAL)
.authorizationUri(AUTHORIZATION_URI)
.redirectUri(redirectUri)
.scopes(authorizedScopes)
.state(STATE)
.authorizationCode(AUTHORIZATION_CODE)
.build();
assertThat(authentication.getPrincipal()).isEqualTo(PRINCIPAL);
assertThat(authentication.getCredentials()).isEqualTo("");
@@ -119,9 +188,11 @@ public class OAuth2AuthorizationCodeRequestAuthenticationTokenTests {
assertThat(authentication.getAuthorizationUri()).isEqualTo(AUTHORIZATION_URI);
assertThat(authentication.getClientId()).isEqualTo(clientId);
assertThat(authentication.getRedirectUri()).isEqualTo(redirectUri);
assertThat(authentication.getState()).isEqualTo(state);
assertThat(authentication.getScopes()).containsExactlyInAnyOrderElementsOf(authorizedScopes);
assertThat(authentication.getState()).isEqualTo(STATE);
assertThat(authentication.getAdditionalParameters()).isEmpty();
assertThat(authentication.isConsentRequired()).isFalse();
assertThat(authentication.isConsent()).isFalse();
assertThat(authentication.getAuthorizationCode()).isEqualTo(AUTHORIZATION_CODE);
assertThat(authentication.isAuthenticated()).isTrue();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2022 the original author or authors.
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,10 +42,10 @@ public class OAuth2AuthorizationConsentAuthenticationContextTests {
private final Authentication principal = this.authorization.getAttribute(Principal.class.getName());
private final OAuth2AuthorizationRequest authorizationRequest = this.authorization.getAttribute(
OAuth2AuthorizationRequest.class.getName());
private final OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication =
new OAuth2AuthorizationConsentAuthenticationToken(
this.authorizationRequest.getAuthorizationUri(), this.registeredClient.getClientId(),
this.principal, "state", null, null);
private final OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
OAuth2AuthorizationCodeRequestAuthenticationToken.with(this.registeredClient.getClientId(), this.principal)
.authorizationUri(this.authorizationRequest.getAuthorizationUri())
.build();
private final OAuth2AuthorizationConsent.Builder authorizationConsentBuilder =
OAuth2AuthorizationConsent.withId(this.authorization.getRegisteredClientId(), this.authorization.getPrincipalName());
@@ -59,7 +59,7 @@ public class OAuth2AuthorizationConsentAuthenticationContextTests {
@Test
public void setWhenValueNullThenThrowIllegalArgumentException() {
OAuth2AuthorizationConsentAuthenticationContext.Builder builder =
OAuth2AuthorizationConsentAuthenticationContext.with(this.authorizationConsentAuthentication);
OAuth2AuthorizationConsentAuthenticationContext.with(this.authorizationCodeRequestAuthentication);
assertThatThrownBy(() -> builder.authorizationConsent(null))
.isInstanceOf(IllegalArgumentException.class);
@@ -76,7 +76,7 @@ public class OAuth2AuthorizationConsentAuthenticationContextTests {
@Test
public void buildWhenRequiredValueNullThenThrowIllegalArgumentException() {
OAuth2AuthorizationConsentAuthenticationContext.Builder builder =
OAuth2AuthorizationConsentAuthenticationContext.with(this.authorizationConsentAuthentication);
OAuth2AuthorizationConsentAuthenticationContext.with(this.authorizationCodeRequestAuthentication);
assertThatThrownBy(builder::build)
.isInstanceOf(IllegalArgumentException.class)
@@ -104,7 +104,7 @@ public class OAuth2AuthorizationConsentAuthenticationContextTests {
@Test
public void buildWhenAllValuesProvidedThenAllValuesAreSet() {
OAuth2AuthorizationConsentAuthenticationContext context =
OAuth2AuthorizationConsentAuthenticationContext.with(this.authorizationConsentAuthentication)
OAuth2AuthorizationConsentAuthenticationContext.with(this.authorizationCodeRequestAuthentication)
.authorizationConsent(this.authorizationConsentBuilder)
.registeredClient(this.registeredClient)
.authorization(this.authorization)
@@ -113,7 +113,7 @@ public class OAuth2AuthorizationConsentAuthenticationContextTests {
.context(ctx -> ctx.put("custom-key-2", "custom-value-2"))
.build();
assertThat(context.<Authentication>getAuthentication()).isEqualTo(this.authorizationConsentAuthentication);
assertThat(context.<Authentication>getAuthentication()).isEqualTo(this.authorizationCodeRequestAuthentication);
assertThat(context.getAuthorizationConsent()).isEqualTo(this.authorizationConsentBuilder);
assertThat(context.getRegisteredClient()).isEqualTo(this.registeredClient);
assertThat(context.getAuthorization()).isEqualTo(this.authorization);

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