Compare commits

..

No commits in common. "main" and "7.0.0" have entirely different histories.
main ... 7.0.0

35 changed files with 266 additions and 482 deletions

View File

@ -111,3 +111,11 @@ updates:
labels: labels:
- 'type: task' - 'type: task'
- 'in: build' - 'in: build'
- package-ecosystem: npm
target-branch: 6.3.x
directory: /docs
schedule:
interval: weekly
labels:
- 'type: task'
- 'in: build'

View File

@ -35,6 +35,13 @@ jobs:
should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }} should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }}
default-publish-milestones-central: true default-publish-milestones-central: true
secrets: inherit secrets: inherit
deploy-docs:
name: Deploy Docs
needs: [ build ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-docs.yml@v1
with:
should-deploy-docs: ${{ needs.build.outputs.should-deploy-artifacts }}
secrets: inherit
deploy-schema: deploy-schema:
name: Deploy Schema name: Deploy Schema
needs: [ build ] needs: [ build ]
@ -44,7 +51,7 @@ jobs:
secrets: inherit secrets: inherit
perform-release: perform-release:
name: Perform Release name: Perform Release
needs: [ deploy-artifacts, deploy-schema ] needs: [ deploy-artifacts, deploy-docs, deploy-schema ]
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@v1 uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@v1
with: with:
should-perform-release: ${{ needs.deploy-artifacts.outputs.artifacts-deployed }} should-perform-release: ${{ needs.deploy-artifacts.outputs.artifacts-deployed }}

View File

@ -42,7 +42,7 @@ springRelease {
weekOfMonth = 3 weekOfMonth = 3
dayOfWeek = 1 dayOfWeek = 1
referenceDocUrl = "https://docs.spring.io/spring-security/reference/{version}/index.html" referenceDocUrl = "https://docs.spring.io/spring-security/reference/{version}/index.html"
apiDocUrl = "https://docs.spring.io/spring-security/reference/{version}/api/java/index.html" apiDocUrl = "https://docs.spring.io/spring-security/site/docs/{version}/api/"
replaceSnapshotVersionInReferenceDocUrl = true replaceSnapshotVersionInReferenceDocUrl = true
} }

View File

@ -0,0 +1,82 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.spring.gradle.convention
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.tasks.bundling.Zip
import org.gradle.api.Plugin
import org.gradle.api.Project
public class DeployDocsPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPluginManager().apply('org.hidetake.ssh')
project.ssh.settings {
knownHosts = allowAnyHosts
}
project.remotes {
docs {
role 'docs'
if (project.hasProperty('deployDocsHost')) {
host = project.findProperty('deployDocsHost')
} else {
host = 'docs.af.pivotal.io'
}
retryCount = 5 // retry 5 times (default is 0)
retryWaitSec = 10 // wait 10 seconds between retries (default is 0)
user = project.findProperty('deployDocsSshUsername')
if (project.hasProperty('deployDocsSshKeyPath')) {
identity = project.file(project.findProperty('deployDocsSshKeyPath'))
} else if (project.hasProperty('deployDocsSshKey')) {
identity = project.findProperty('deployDocsSshKey')
}
if(project.hasProperty('deployDocsSshPassphrase')) {
passphrase = project.findProperty('deployDocsSshPassphrase')
}
}
}
project.task('deployDocs') {
dependsOn 'docsZip'
doFirst {
project.ssh.run {
session(project.remotes.docs) {
def now = System.currentTimeMillis()
def name = project.rootProject.name
def version = project.rootProject.version
def tempPath = "/tmp/${name}-${now}-docs/".replaceAll(' ', '_')
execute "mkdir -p $tempPath"
project.tasks.docsZip.outputs.each { o ->
put from: o.files, into: tempPath
}
execute "unzip $tempPath*.zip -d $tempPath"
def extractPath = "/var/www/domains/spring.io/docs/htdocs/autorepo/docs/${name}/${version}/"
execute "rm -rf $extractPath"
execute "mkdir -p $extractPath"
execute "mv $tempPath/docs/* $extractPath"
execute "chmod -R g+w $extractPath"
}
}
}
}
}
}

View File

@ -17,6 +17,7 @@ public class DocsPlugin implements Plugin<Project> {
PluginManager pluginManager = project.getPluginManager(); PluginManager pluginManager = project.getPluginManager();
pluginManager.apply(BasePlugin); pluginManager.apply(BasePlugin);
pluginManager.apply(DeployDocsPlugin);
pluginManager.apply(JavadocApiPlugin); pluginManager.apply(JavadocApiPlugin);
Task docsZip = project.tasks.create('docsZip', Zip) { Task docsZip = project.tasks.create('docsZip', Zip) {

View File

@ -30,6 +30,16 @@ ossrh: {
} }
} }
}, },
docs: {
stage('Deploy Docs') {
node {
checkout scm
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace"
}
}
}
},
schema: { schema: {
stage('Deploy Schema') { stage('Deploy Schema') {
node { node {
@ -39,4 +49,4 @@ schema: {
} }
} }
} }
} }

View File

@ -42,8 +42,7 @@ final class MethodSecuritySelector implements ImportSelector {
.isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null); .isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null);
private static final boolean isWebPresent = ClassUtils private static final boolean isWebPresent = ClassUtils
.isPresent("org.springframework.web.servlet.DispatcherServlet", null) .isPresent("org.springframework.web.servlet.DispatcherServlet", null);
&& ClassUtils.isPresent("org.springframework.security.web.util.ThrowableAnalyzer", null);
private static final boolean isObservabilityPresent = ClassUtils private static final boolean isObservabilityPresent = ClassUtils
.isPresent("io.micrometer.observation.ObservationRegistry", null); .isPresent("io.micrometer.observation.ObservationRegistry", null);

View File

@ -16,12 +16,10 @@
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import jakarta.servlet.Filter;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
@ -38,12 +36,10 @@ import org.springframework.security.oauth2.server.authorization.authentication.O
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationValidator; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationValidator;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken;
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.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter; import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
@ -54,7 +50,6 @@ import org.springframework.security.web.servlet.util.matcher.PathPatternRequestM
import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@ -88,8 +83,6 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationCodeRequestAuthenticationValidator; private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationCodeRequestAuthenticationValidator;
private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationCodeRequestAuthenticationValidatorComposite;
private SessionAuthenticationStrategy sessionAuthenticationStrategy; private SessionAuthenticationStrategy sessionAuthenticationStrategy;
/** /**
@ -255,16 +248,8 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
authenticationProviders.addAll(0, this.authenticationProviders); authenticationProviders.addAll(0, this.authenticationProviders);
} }
this.authenticationProvidersConsumer.accept(authenticationProviders); this.authenticationProvidersConsumer.accept(authenticationProviders);
authenticationProviders.forEach((authenticationProvider) -> { authenticationProviders.forEach(
httpSecurity.authenticationProvider(postProcess(authenticationProvider)); (authenticationProvider) -> httpSecurity.authenticationProvider(postProcess(authenticationProvider)));
if (authenticationProvider instanceof OAuth2AuthorizationCodeRequestAuthenticationProvider) {
Method method = ReflectionUtils.findMethod(OAuth2AuthorizationCodeRequestAuthenticationProvider.class,
"getAuthenticationValidatorComposite");
ReflectionUtils.makeAccessible(method);
this.authorizationCodeRequestAuthenticationValidatorComposite = (Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext>) ReflectionUtils
.invokeMethod(method, authenticationProvider);
}
});
} }
@Override @Override
@ -297,18 +282,7 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
if (this.sessionAuthenticationStrategy != null) { if (this.sessionAuthenticationStrategy != null) {
authorizationEndpointFilter.setSessionAuthenticationStrategy(this.sessionAuthenticationStrategy); authorizationEndpointFilter.setSessionAuthenticationStrategy(this.sessionAuthenticationStrategy);
} }
httpSecurity.addFilterAfter(postProcess(authorizationEndpointFilter), AuthorizationFilter.class); httpSecurity.addFilterBefore(postProcess(authorizationEndpointFilter),
// Create and add
// OAuth2AuthorizationEndpointFilter.OAuth2AuthorizationCodeRequestValidatingFilter
Method method = ReflectionUtils.findMethod(OAuth2AuthorizationEndpointFilter.class,
"createAuthorizationCodeRequestValidatingFilter", RegisteredClientRepository.class, Consumer.class);
ReflectionUtils.makeAccessible(method);
RegisteredClientRepository registeredClientRepository = OAuth2ConfigurerUtils
.getRegisteredClientRepository(httpSecurity);
Filter authorizationCodeRequestValidatingFilter = (Filter) ReflectionUtils.invokeMethod(method,
authorizationEndpointFilter, registeredClientRepository,
this.authorizationCodeRequestAuthenticationValidatorComposite);
httpSecurity.addFilterBefore(postProcess(authorizationCodeRequestValidatingFilter),
AbstractPreAuthenticatedProcessingFilter.class); AbstractPreAuthenticatedProcessingFilter.class);
} }

View File

@ -307,8 +307,8 @@ public class OAuth2AuthorizationCodeGrantTests {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
this.mvc this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) .perform(
.queryParams(getAuthorizationRequestParameters(registeredClient))) get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).params(getAuthorizationRequestParameters(registeredClient)))
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andReturn(); .andReturn();
} }
@ -851,31 +851,21 @@ public class OAuth2AuthorizationCodeGrantTests {
this.spring.register(AuthorizationServerConfigurationCustomAuthorizationEndpoint.class).autowire(); this.spring.register(AuthorizationServerConfigurationCustomAuthorizationEndpoint.class).autowire();
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
this.registeredClientRepository.save(registeredClient);
TestingAuthenticationToken principal = new TestingAuthenticationToken("principalName", "password"); TestingAuthenticationToken principal = new TestingAuthenticationToken("principalName", "password");
Map<String, Object> additionalParameters = new HashMap<>();
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE);
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = new OAuth2AuthorizationCodeRequestAuthenticationToken(
"https://provider.com/oauth2/authorize", registeredClient.getClientId(), principal,
registeredClient.getRedirectUris().iterator().next(), STATE_URL_UNENCODED, registeredClient.getScopes(),
additionalParameters);
OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode("code", Instant.now(), OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode("code", Instant.now(),
Instant.now().plus(5, ChronoUnit.MINUTES)); Instant.now().plus(5, ChronoUnit.MINUTES));
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken( OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken(
"https://provider.com/oauth2/authorize", registeredClient.getClientId(), principal, authorizationCode, "https://provider.com/oauth2/authorize", registeredClient.getClientId(), principal, authorizationCode,
registeredClient.getRedirectUris().iterator().next(), STATE_URL_UNENCODED, registeredClient.getRedirectUris().iterator().next(), STATE_URL_UNENCODED,
registeredClient.getScopes()); registeredClient.getScopes());
given(authorizationRequestConverter.convert(any())).willReturn(authorizationCodeRequestAuthentication); given(authorizationRequestConverter.convert(any())).willReturn(authorizationCodeRequestAuthenticationResult);
given(authorizationRequestAuthenticationProvider given(authorizationRequestAuthenticationProvider
.supports(eq(OAuth2AuthorizationCodeRequestAuthenticationToken.class))).willReturn(true); .supports(eq(OAuth2AuthorizationCodeRequestAuthenticationToken.class))).willReturn(true);
given(authorizationRequestAuthenticationProvider.authenticate(any())) given(authorizationRequestAuthenticationProvider.authenticate(any()))
.willReturn(authorizationCodeRequestAuthenticationResult); .willReturn(authorizationCodeRequestAuthenticationResult);
this.mvc this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).params(getAuthorizationRequestParameters(registeredClient))
.queryParams(getAuthorizationRequestParameters(registeredClient))
.with(user("user"))) .with(user("user")))
.andExpect(status().isOk()); .andExpect(status().isOk());
@ -890,7 +880,8 @@ public class OAuth2AuthorizationCodeGrantTests {
|| converter instanceof OAuth2AuthorizationCodeRequestAuthenticationConverter || converter instanceof OAuth2AuthorizationCodeRequestAuthenticationConverter
|| converter instanceof OAuth2AuthorizationConsentAuthenticationConverter); || converter instanceof OAuth2AuthorizationConsentAuthenticationConverter);
verify(authorizationRequestAuthenticationProvider).authenticate(eq(authorizationCodeRequestAuthentication)); verify(authorizationRequestAuthenticationProvider)
.authenticate(eq(authorizationCodeRequestAuthenticationResult));
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
ArgumentCaptor<List<AuthenticationProvider>> authenticationProvidersCaptor = ArgumentCaptor ArgumentCaptor<List<AuthenticationProvider>> authenticationProvidersCaptor = ArgumentCaptor

View File

@ -275,7 +275,7 @@ class ServerJwtDslTests {
} }
class NullConverter: Converter<Jwt, Mono<AbstractAuthenticationToken>> { class NullConverter: Converter<Jwt, Mono<AbstractAuthenticationToken>> {
override fun convert(source: Jwt): Mono<AbstractAuthenticationToken> { override fun convert(source: Jwt): Mono<AbstractAuthenticationToken>? {
return Mono.empty() return Mono.empty()
} }

View File

@ -31,7 +31,7 @@ urls:
redirect_facility: httpd redirect_facility: httpd
ui: ui:
bundle: bundle:
url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.25/ui-bundle.zip url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip
snapshot: true snapshot: true
runtime: runtime:
log: log:

View File

@ -39,7 +39,7 @@ Gradle::
+ +
[source,groovy,role="secondary",subs="verbatim,attributes"] [source,groovy,role="secondary",subs="verbatim,attributes"]
---- ----
dependencies { depenendencies {
implementation "org.springframework.security:spring-security-web" implementation "org.springframework.security:spring-security-web"
implementation "com.webauthn4j:webauthn4j-core:{webauthn4j-core-version}" implementation "com.webauthn4j:webauthn4j-core:{webauthn4j-core-version}"
} }

View File

@ -38,7 +38,7 @@ Gradle::
+ +
[source,groovy,role="secondary"] [source,groovy,role="secondary"]
---- ----
dependencies { depenendencies {
implementation "org.springframework.boot:spring-boot-starter-data-ldap" implementation "org.springframework.boot:spring-boot-starter-data-ldap"
implementation "org.springframework.security:spring-security-ldap" implementation "org.springframework.security:spring-security-ldap"
} }
@ -150,7 +150,7 @@ Gradle::
+ +
[source,groovy,role="secondary",subs="verbatim,attributes"] [source,groovy,role="secondary",subs="verbatim,attributes"]
---- ----
dependencies { depenendencies {
runtimeOnly "com.unboundid:unboundid-ldapsdk:{unboundid-ldapsdk-version}" runtimeOnly "com.unboundid:unboundid-ldapsdk:{unboundid-ldapsdk-version}"
} }
---- ----

View File

@ -639,7 +639,7 @@ This is because Spring Security requires all URIs to be absolute (minus the cont
[TIP] [TIP]
===== =====
There are several other components that create request matchers for you like {spring-boot-api-url}org/springframework/boot/security/autoconfigure/web/servlet/PathRequest.html[`PathRequest#toStaticResources#atCommonLocations`] There are several other components that create request matchers for you like {spring-boot-api-url}org/springframework/boot/autoconfigure/security/servlet/PathRequest.html[`PathRequest#toStaticResources#atCommonLocations`]
===== =====
[[match-by-custom]] [[match-by-custom]]

View File

@ -493,14 +493,14 @@ public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurit
private boolean flag; private boolean flag;
@Override @Override
public void init(HttpSecurity http) { public void init(HttpSecurity http) throws Exception {
// any method that adds another configurer // any method that adds another configurer
// must be done in the init method // must be done in the init method
http.csrf(csrf -> csrf.disable()); http.csrf().disable();
} }
@Override @Override
public void configure(HttpSecurity http) { public void configure(HttpSecurity http) throws Exception {
ApplicationContext context = http.getSharedObject(ApplicationContext.class); ApplicationContext context = http.getSharedObject(ApplicationContext.class);
// here we lookup from the ApplicationContext. You can also just create a new instance. // here we lookup from the ApplicationContext. You can also just create a new instance.

View File

@ -118,7 +118,7 @@ The default arrangement of Spring Boot and Spring Security affords the following
* Publishes xref:servlet/authentication/events.adoc[authentication success and failure events] * Publishes xref:servlet/authentication/events.adoc[authentication success and failure events]
It can be helpful to understand how Spring Boot is coordinating with Spring Security to achieve this. It can be helpful to understand how Spring Boot is coordinating with Spring Security to achieve this.
Taking a look at {spring-boot-api-url}org/springframework/boot/security/autoconfigure/SecurityAutoConfiguration.html[Boot's security auto configuration], it does the following (simplified for illustration): Taking a look at {spring-boot-api-url}org/springframework/boot/autoconfigure/security/servlet/SecurityAutoConfiguration.html[Boot's security auto configuration], it does the following (simplified for illustration):
.Spring Boot Security Auto Configuration .Spring Boot Security Auto Configuration
[source,java] [source,java]

View File

@ -370,7 +370,7 @@ Java::
---- ----
@Bean @Bean
public AnnotationTemplateExpressionDefaults templateDefaults() { public AnnotationTemplateExpressionDefaults templateDefaults() {
return new AnnotationTemplateExpressionDefaults(); return new AnnotationTemplateExpressionDeafults();
} }
---- ----
@ -380,7 +380,7 @@ Kotlin::
---- ----
@Bean @Bean
fun templateDefaults(): AnnotationTemplateExpressionDefaults { fun templateDefaults(): AnnotationTemplateExpressionDefaults {
return AnnotationTemplateExpressionDefaults() return AnnotationTemplateExpressionDeafults()
} }
---- ----

View File

@ -41,7 +41,7 @@ See xref:servlet/oauth2/resource-server/index.adoc[OAuth 2.0 Resource Server] fo
To get started, add the `spring-security-oauth2-resource-server` dependency to your project. To get started, add the `spring-security-oauth2-resource-server` dependency to your project.
When using Spring Boot, add the following starter: When using Spring Boot, add the following starter:
.OAuth2 Resource Server with Spring Boot .OAuth2 Client with Spring Boot
[tabs] [tabs]
====== ======
Gradle:: Gradle::

View File

@ -1,6 +1,6 @@
{ {
"dependencies": { "dependencies": {
"antora": "3.2.0-alpha.11", "antora": "3.2.0-alpha.10",
"@antora/atlas-extension": "1.0.0-alpha.5", "@antora/atlas-extension": "1.0.0-alpha.5",
"@antora/collector-extension": "1.0.2", "@antora/collector-extension": "1.0.2",
"@asciidoctor/tabs": "1.0.0-beta.6", "@asciidoctor/tabs": "1.0.0-beta.6",

View File

@ -14,7 +14,7 @@
# limitations under the License. # limitations under the License.
# #
springBootVersion=4.0.0-SNAPSHOT springBootVersion=4.0.0-SNAPSHOT
version=7.0.3-SNAPSHOT version=7.0.0
samplesBranch=main samplesBranch=main
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true org.gradle.parallel=true

View File

@ -4,39 +4,39 @@ io-rsocket = "1.1.5"
io-spring-javaformat = "0.0.47" io-spring-javaformat = "0.0.47"
io-spring-nohttp = "0.0.11" io-spring-nohttp = "0.0.11"
jakarta-websocket = "2.2.0" jakarta-websocket = "2.2.0"
org-apache-maven-resolver = "1.9.25" org-apache-maven-resolver = "1.9.24"
org-aspectj = "1.9.25.1" org-aspectj = "1.9.24"
org-bouncycastle = "1.80" org-bouncycastle = "1.80"
org-eclipse-jetty = "11.0.26" org-eclipse-jetty = "11.0.26"
org-jetbrains-kotlin = "2.2.21" org-jetbrains-kotlin = "2.2.21"
org-jetbrains-kotlinx = "1.10.2" org-jetbrains-kotlinx = "1.10.2"
org-mockito = "5.17.0" org-mockito = "5.17.0"
org-opensaml5 = "5.1.6" org-opensaml5 = "5.1.6"
org-springframework = "7.0.2" org-springframework = "7.0.0"
com-password4j = "1.8.4" com-password4j = "1.8.4"
[libraries] [libraries]
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.22" ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.20"
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.20.1" com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.20.0"
com-google-inject-guice = "com.google.inject:guice:3.0" com-google-inject-guice = "com.google.inject:guice:3.0"
com-netflix-nebula-nebula-project-plugin = "com.netflix.nebula:nebula-project-plugin:8.2.0" com-netflix-nebula-nebula-project-plugin = "com.netflix.nebula:nebula-project-plugin:8.2.0"
com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:10.4" com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:10.4"
com-nimbusds-oauth2-oidc-sdk = "com.nimbusds:oauth2-oidc-sdk:11.26.1" com-nimbusds-oauth2-oidc-sdk = "com.nimbusds:oauth2-oidc-sdk:11.26.1"
com-squareup-okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "com-squareup-okhttp3" } com-squareup-okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "com-squareup-okhttp3" }
com-squareup-okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "com-squareup-okhttp3" } com-squareup-okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "com-squareup-okhttp3" }
com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:7.0.4" com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:7.0.3"
com-jayway-jsonpath-json-path = "com.jayway.jsonpath:json-path:2.9.0" com-jayway-jsonpath-json-path = "com.jayway.jsonpath:json-path:2.9.0"
commons-collections = "commons-collections:commons-collections:3.2.2" commons-collections = "commons-collections:commons-collections:3.2.2"
io-micrometer-context-propagation = "io.micrometer:context-propagation:1.1.3" io-micrometer-context-propagation = "io.micrometer:context-propagation:1.1.3"
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.14" io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.14.12"
io-mockk = "io.mockk:mockk:1.14.7" io-mockk = "io.mockk:mockk:1.14.6"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.1" io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.0"
io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" } io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" }
io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" } io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" }
io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" } io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" }
io-spring-nohttp-nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version.ref = "io-spring-nohttp" } io-spring-nohttp-nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version.ref = "io-spring-nohttp" }
io-spring-nohttp-nohttp-gradle = { module = "io.spring.nohttp:nohttp-gradle", version.ref = "io-spring-nohttp" } io-spring-nohttp-nohttp-gradle = { module = "io.spring.nohttp:nohttp-gradle", version.ref = "io-spring-nohttp" }
io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.13" io-spring-security-release-plugin = "io.spring.gradle:spring-security-release-plugin:1.0.10"
jakarta-annotation-jakarta-annotation-api = "jakarta.annotation:jakarta.annotation-api:3.0.0" jakarta-annotation-jakarta-annotation-api = "jakarta.annotation:jakarta.annotation-api:3.0.0"
jakarta-inject-jakarta-inject-api = "jakarta.inject:jakarta.inject-api:2.0.1" jakarta-inject-jakarta-inject-api = "jakarta.inject:jakarta.inject-api:2.0.1"
jakarta-persistence-jakarta-persistence-api = "jakarta.persistence:jakarta.persistence-api:3.2.0" jakarta-persistence-jakarta-persistence-api = "jakarta.persistence:jakarta.persistence-api:3.2.0"
@ -50,8 +50,8 @@ ldapsdk = "ldapsdk:ldapsdk:4.1"
net-sourceforge-htmlunit = "net.sourceforge.htmlunit:htmlunit:2.70.0" net-sourceforge-htmlunit = "net.sourceforge.htmlunit:htmlunit:2.70.0"
org-htmlunit-htmlunit = "org.htmlunit:htmlunit:4.11.1" org-htmlunit-htmlunit = "org.htmlunit:htmlunit:4.11.1"
org-apache-httpcomponents-httpclient = "org.apache.httpcomponents.client5:httpclient5:5.5.1" org-apache-httpcomponents-httpclient = "org.apache.httpcomponents.client5:httpclient5:5.5.1"
org-apache-kerby-simplekdc='org.apache.kerby:kerb-simplekdc:2.1.1' org-apache-kerby-simplekdc='org.apache.kerby:kerb-simplekdc:2.1.0'
org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-provider:3.9.12" org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-provider:3.9.11"
org-apache-maven-resolver-maven-resolver-connector-basic = { module = "org.apache.maven.resolver:maven-resolver-connector-basic", version.ref = "org-apache-maven-resolver" } org-apache-maven-resolver-maven-resolver-connector-basic = { module = "org.apache.maven.resolver:maven-resolver-connector-basic", version.ref = "org-apache-maven-resolver" }
org-apache-maven-resolver-maven-resolver-impl = { module = "org.apache.maven.resolver:maven-resolver-impl", version.ref = "org-apache-maven-resolver" } org-apache-maven-resolver-maven-resolver-impl = { module = "org.apache.maven.resolver:maven-resolver-impl", version.ref = "org-apache-maven-resolver" }
org-apache-maven-resolver-maven-resolver-transport-http = { module = "org.apache.maven.resolver:maven-resolver-transport-http", version.ref = "org-apache-maven-resolver" } org-apache-maven-resolver-maven-resolver-transport-http = { module = "org.apache.maven.resolver:maven-resolver-transport-http", version.ref = "org-apache-maven-resolver" }
@ -70,7 +70,7 @@ org-hsqldb = "org.hsqldb:hsqldb:2.7.4"
org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" } org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" }
org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.21" org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.21"
org-jetbrains-kotlinx-kotlinx-coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version.ref = "org-jetbrains-kotlinx" } org-jetbrains-kotlinx-kotlinx-coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version.ref = "org-jetbrains-kotlinx" }
org-junit-junit-bom = "org.junit:junit-bom:6.0.1" org-junit-junit-bom = "org.junit:junit-bom:6.0.0"
org-mockito-mockito-bom = { module = "org.mockito:mockito-bom", version.ref = "org-mockito" } org-mockito-mockito-bom = { module = "org.mockito:mockito-bom", version.ref = "org-mockito" }
org-opensaml-opensaml5-saml-api = { module = "org.opensaml:opensaml-saml-api", version.ref = "org-opensaml5" } org-opensaml-opensaml5-saml-api = { module = "org.opensaml:opensaml-saml-api", version.ref = "org-opensaml5" }
org-opensaml-opensaml5-saml-impl = { module = "org.opensaml:opensaml-saml-impl", version.ref = "org-opensaml5" } org-opensaml-opensaml5-saml-impl = { module = "org.opensaml:opensaml-saml-impl", version.ref = "org-opensaml5" }
@ -81,11 +81,11 @@ org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-sup
org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3" org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3"
org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36" org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36"
org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.17" org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.17"
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2025.1.1" org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2025.1.0"
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:4.0.1" org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:4.0.0"
org-springframework-spring-framework-bom = { module = "org.springframework:spring-framework-bom", version.ref = "org-springframework" } org-springframework-spring-framework-bom = { module = "org.springframework:spring-framework-bom", version.ref = "org-springframework" }
org-synchronoss-cloud-nio-multipart-parser = "org.synchronoss.cloud:nio-multipart-parser:1.1.0" org-synchronoss-cloud-nio-multipart-parser = "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
tools-jackson-jackson-bom = "tools.jackson:jackson-bom:3.0.3" tools-jackson-jackson-bom = "tools.jackson:jackson-bom:3.0.1"
com-google-code-gson-gson = "com.google.code.gson:gson:2.13.2" com-google-code-gson-gson = "com.google.code.gson:gson:2.13.2"
com-thaiopensource-trag = "com.thaiopensource:trang:20091111" com-thaiopensource-trag = "com.thaiopensource:trang:20091111"

View File

@ -1944,9 +1944,9 @@
"dev": true "dev": true
}, },
"node_modules/js-yaml": { "node_modules/js-yaml": {
"version": "4.1.1", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"argparse": "^2.0.1" "argparse": "^2.0.1"
@ -4524,9 +4524,9 @@
"dev": true "dev": true
}, },
"js-yaml": { "js-yaml": {
"version": "4.1.1", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true, "dev": true,
"requires": { "requires": {
"argparse": "^2.0.1" "argparse": "^2.0.1"

View File

@ -1,53 +0,0 @@
/*
* Copyright 2004-present 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.aot.hint;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
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.web.OAuth2AuthorizationEndpointFilter;
/**
* {@link RuntimeHintsRegistrar} that contributes the required {@link RuntimeHints} for
* OAuth 2.1 Authorization Server. Statically registered via
* META-INF/spring/aot.factories.
*
* @author Joe Grandja
* @since 7.0
*/
class OAuth2AuthorizationServerRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.reflection()
.registerType(OAuth2AuthorizationCodeRequestAuthenticationProvider.class,
MemberCategory.INVOKE_DECLARED_METHODS);
hints.reflection()
.registerType(OAuth2AuthorizationEndpointFilter.class, MemberCategory.INVOKE_DECLARED_METHODS);
hints.reflection()
.registerType(TypeReference
.of("org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter$OAuth2AuthorizationCodeRequestValidatingFilter"),
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
hints.reflection()
.registerType(OAuth2AuthorizationCodeRequestAuthenticationToken.class, MemberCategory.DECLARED_FIELDS);
}
}

View File

@ -190,55 +190,51 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
OAuth2AuthorizationCodeRequestAuthenticationContext.Builder authenticationContextBuilder = OAuth2AuthorizationCodeRequestAuthenticationContext OAuth2AuthorizationCodeRequestAuthenticationContext.Builder authenticationContextBuilder = OAuth2AuthorizationCodeRequestAuthenticationContext
.with(authorizationCodeRequestAuthentication) .with(authorizationCodeRequestAuthentication)
.registeredClient(registeredClient); .registeredClient(registeredClient);
OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext = authenticationContextBuilder
.build();
if (!authorizationCodeRequestAuthentication.isValidated()) { // grant_type
OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext = authenticationContextBuilder OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_AUTHORIZATION_GRANT_TYPE_VALIDATOR
.build(); .accept(authenticationContext);
// grant_type // redirect_uri and scope
OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_AUTHORIZATION_GRANT_TYPE_VALIDATOR this.authenticationValidator.accept(authenticationContext);
.accept(authenticationContext);
// redirect_uri and scope // code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE)
this.authenticationValidator.accept(authenticationContext); OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_CODE_CHALLENGE_VALIDATOR
.accept(authenticationContext);
// code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE) // prompt (OPTIONAL for OpenID Connect 1.0 Authentication Request)
OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_CODE_CHALLENGE_VALIDATOR Set<String> promptValues = Collections.emptySet();
.accept(authenticationContext); if (authorizationCodeRequestAuthentication.getScopes().contains(OidcScopes.OPENID)) {
String prompt = (String) authorizationCodeRequestAuthentication.getAdditionalParameters().get("prompt");
// prompt (OPTIONAL for OpenID Connect 1.0 Authentication Request) if (StringUtils.hasText(prompt)) {
OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_PROMPT_VALIDATOR OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_PROMPT_VALIDATOR
.accept(authenticationContext); .accept(authenticationContext);
promptValues = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(prompt, " ")));
authorizationCodeRequestAuthentication.setValidated(true);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Validated authorization code request parameters");
} }
} }
if (this.logger.isTraceEnabled()) {
this.logger.trace("Validated authorization code request parameters");
}
// --------------- // ---------------
// The request is valid - ensure the resource owner is authenticated // The request is valid - ensure the resource owner is authenticated
// --------------- // ---------------
Authentication principal = (Authentication) authorizationCodeRequestAuthentication.getPrincipal(); Authentication principal = (Authentication) authorizationCodeRequestAuthentication.getPrincipal();
Set<String> promptValues = Collections.emptySet();
if (authorizationCodeRequestAuthentication.getScopes().contains(OidcScopes.OPENID)) {
String prompt = (String) authorizationCodeRequestAuthentication.getAdditionalParameters().get("prompt");
if (StringUtils.hasText(prompt)) {
promptValues = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(prompt, " ")));
}
}
if (!isPrincipalAuthenticated(principal)) { if (!isPrincipalAuthenticated(principal)) {
if (promptValues.contains(OidcPrompt.NONE)) { if (promptValues.contains(OidcPrompt.NONE)) {
// Return an error instead of displaying the login page (via the
// configured AuthenticationEntryPoint)
throwError("login_required", "prompt", authorizationCodeRequestAuthentication, registeredClient); throwError("login_required", "prompt", authorizationCodeRequestAuthentication, registeredClient);
} }
else { if (this.logger.isTraceEnabled()) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, "principal", authorizationCodeRequestAuthentication, this.logger.trace("Did not authenticate authorization code request since principal not authenticated");
registeredClient);
} }
// Return the authorization request as-is where isAuthenticated() is false
return authorizationCodeRequestAuthentication;
} }
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode() OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
@ -404,13 +400,6 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
this.authorizationConsentRequired = authorizationConsentRequired; this.authorizationConsentRequired = authorizationConsentRequired;
} }
Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> getAuthenticationValidatorComposite() {
return OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_AUTHORIZATION_GRANT_TYPE_VALIDATOR
.andThen(this.authenticationValidator)
.andThen(OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_CODE_CHALLENGE_VALIDATOR)
.andThen(OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_PROMPT_VALIDATOR);
}
private static boolean isAuthorizationConsentRequired( private static boolean isAuthorizationConsentRequired(
OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) { OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) {
if (!authenticationContext.getRegisteredClient().getClientSettings().isRequireAuthorizationConsent()) { if (!authenticationContext.getRegisteredClient().getClientSettings().isRequireAuthorizationConsent()) {

View File

@ -42,8 +42,6 @@ public class OAuth2AuthorizationCodeRequestAuthenticationToken
private final OAuth2AuthorizationCode authorizationCode; private final OAuth2AuthorizationCode authorizationCode;
private boolean validated;
/** /**
* Constructs an {@code OAuth2AuthorizationCodeRequestAuthenticationToken} using the * Constructs an {@code OAuth2AuthorizationCodeRequestAuthenticationToken} using the
* provided parameters. * provided parameters.
@ -91,12 +89,4 @@ public class OAuth2AuthorizationCodeRequestAuthenticationToken
return this.authorizationCode; return this.authorizationCode;
} }
final boolean isValidated() {
return this.validated;
}
final void setValidated(boolean validated) {
this.validated = validated;
}
} }

View File

@ -17,14 +17,11 @@
package org.springframework.security.oauth2.server.authorization.web; package org.springframework.security.oauth2.server.authorization.web;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
@ -41,18 +38,14 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationContext;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider; 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.OAuth2AuthorizationCodeRequestAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider; 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.authentication.OAuth2AuthorizationConsentAuthenticationToken;
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.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter;
import org.springframework.security.web.DefaultRedirectStrategy; import org.springframework.security.web.DefaultRedirectStrategy;
@ -71,7 +64,6 @@ import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
@ -188,18 +180,21 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte
} }
try { try {
// Get the pre-validated authorization code request (if available), Authentication authentication = this.authenticationConverter.convert(request);
// which was set by OAuth2AuthorizationCodeRequestValidatingFilter if (authentication instanceof AbstractAuthenticationToken authenticationToken) {
Authentication authentication = (Authentication) request authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request));
.getAttribute(OAuth2AuthorizationCodeRequestAuthenticationToken.class.getName());
if (authentication == null) {
authentication = this.authenticationConverter.convert(request);
if (authentication instanceof AbstractAuthenticationToken authenticationToken) {
authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
} }
Authentication authenticationResult = this.authenticationManager.authenticate(authentication); Authentication authenticationResult = this.authenticationManager.authenticate(authentication);
if (!authenticationResult.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
filterChain.doFilter(request, response);
return;
}
if (authenticationResult instanceof OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthenticationToken) { if (authenticationResult instanceof OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthenticationToken) {
if (this.logger.isTraceEnabled()) { if (this.logger.isTraceEnabled()) {
this.logger.trace("Authorization consent is required"); this.logger.trace("Authorization consent is required");
@ -406,109 +401,4 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte
this.redirectStrategy.sendRedirect(request, response, redirectUri); this.redirectStrategy.sendRedirect(request, response, redirectUri);
} }
Filter createAuthorizationCodeRequestValidatingFilter(RegisteredClientRepository registeredClientRepository,
Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator) {
return new OAuth2AuthorizationCodeRequestValidatingFilter(registeredClientRepository, authenticationValidator);
}
/**
* A {@code Filter} that is applied before {@code OAuth2AuthorizationEndpointFilter}
* and handles the pre-validation of an OAuth 2.0 Authorization Code Request.
*/
private final class OAuth2AuthorizationCodeRequestValidatingFilter extends OncePerRequestFilter {
private final RegisteredClientRepository registeredClientRepository;
private final Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator;
private final Field setValidatedField;
private OAuth2AuthorizationCodeRequestValidatingFilter(RegisteredClientRepository registeredClientRepository,
Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator) {
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
Assert.notNull(authenticationValidator, "authenticationValidator cannot be null");
this.registeredClientRepository = registeredClientRepository;
this.authenticationValidator = authenticationValidator;
this.setValidatedField = ReflectionUtils.findField(OAuth2AuthorizationCodeRequestAuthenticationToken.class,
"validated");
ReflectionUtils.makeAccessible(this.setValidatedField);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
if (!OAuth2AuthorizationEndpointFilter.this.authorizationEndpointMatcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
try {
Authentication authentication = OAuth2AuthorizationEndpointFilter.this.authenticationConverter
.convert(request);
if (!(authentication instanceof OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication)) {
filterChain.doFilter(request, response);
return;
}
String requestUri = (String) authorizationCodeRequestAuthentication.getAdditionalParameters()
.get(OAuth2ParameterNames.REQUEST_URI);
if (StringUtils.hasText(requestUri)) {
filterChain.doFilter(request, response);
return;
}
authorizationCodeRequestAuthentication.setDetails(
OAuth2AuthorizationEndpointFilter.this.authenticationDetailsSource.buildDetails(request));
RegisteredClient registeredClient = this.registeredClientRepository
.findByClientId(authorizationCodeRequestAuthentication.getClientId());
if (registeredClient == null) {
String redirectUri = null; // Prevent redirect
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken(
authorizationCodeRequestAuthentication.getAuthorizationUri(),
authorizationCodeRequestAuthentication.getClientId(),
(Authentication) authorizationCodeRequestAuthentication.getPrincipal(), redirectUri,
authorizationCodeRequestAuthentication.getState(),
authorizationCodeRequestAuthentication.getScopes(),
authorizationCodeRequestAuthentication.getAdditionalParameters());
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST,
"OAuth 2.0 Parameter: " + OAuth2ParameterNames.CLIENT_ID,
"https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1");
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error,
authorizationCodeRequestAuthenticationResult);
}
OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext = OAuth2AuthorizationCodeRequestAuthenticationContext
.with(authorizationCodeRequestAuthentication)
.registeredClient(registeredClient)
.build();
this.authenticationValidator.accept(authenticationContext);
ReflectionUtils.setField(this.setValidatedField, authorizationCodeRequestAuthentication, true);
// Set the validated authorization code request as a request
// attribute
// to be used upstream by OAuth2AuthorizationEndpointFilter
request.setAttribute(OAuth2AuthorizationCodeRequestAuthenticationToken.class.getName(),
authorizationCodeRequestAuthentication);
filterChain.doFilter(request, response);
}
catch (OAuth2AuthenticationException ex) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Authorization request failed: %s", ex.getError()), ex);
}
OAuth2AuthorizationEndpointFilter.this.authenticationFailureHandler.onAuthenticationFailure(request,
response, ex);
}
finally {
request.removeAttribute(OAuth2AuthorizationCodeRequestAuthenticationToken.class.getName());
}
}
}
} }

View File

@ -1,5 +1,2 @@
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\ org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
org.springframework.security.oauth2.server.authorization.aot.hint.OAuth2AuthorizationServerBeanRegistrationAotProcessor org.springframework.security.oauth2.server.authorization.aot.hint.OAuth2AuthorizationServerBeanRegistrationAotProcessor
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.security.oauth2.server.authorization.aot.hint.OAuth2AuthorizationServerRuntimeHints

View File

@ -428,7 +428,7 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
} }
@Test @Test
public void authenticateWhenPrincipalNotAuthenticatedThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { public void authenticateWhenPrincipalNotAuthenticatedThenReturnAuthorizationCodeRequest() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) given(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.willReturn(registeredClient); .willReturn(registeredClient);
@ -438,10 +438,12 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, redirectUri, STATE, AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, redirectUri, STATE,
registeredClient.getScopes(), createPkceParameters()); registeredClient.getScopes(), createPkceParameters());
assertThatExceptionOfType(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication)) OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider
.satisfies((ex) -> assertAuthenticationException(ex, OAuth2ErrorCodes.INVALID_REQUEST, "principal", .authenticate(authentication);
authentication.getRedirectUri()));
assertThat(authenticationResult).isSameAs(authentication);
assertThat(authenticationResult.isAuthenticated()).isFalse();
} }
@Test @Test

View File

@ -372,11 +372,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
given(authenticationConverter.convert(any())).willReturn(authorizationCodeRequestAuthentication); given(authenticationConverter.convert(any())).willReturn(authorizationCodeRequestAuthentication);
this.filter.setAuthenticationConverter(authenticationConverter); this.filter.setAuthenticationConverter(authenticationConverter);
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken( given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthentication);
AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, this.authorizationCode,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes());
authorizationCodeRequestAuthenticationResult.setAuthenticated(true);
given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthenticationResult);
MockHttpServletRequest request = createAuthorizationRequest(registeredClient); MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
@ -386,7 +382,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
verify(authenticationConverter).convert(any()); verify(authenticationConverter).convert(any());
verify(this.authenticationManager).authenticate(any()); verify(this.authenticationManager).authenticate(any());
verifyNoInteractions(filterChain); verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
} }
@Test @Test
@ -465,6 +461,9 @@ public class OAuth2AuthorizationEndpointFilterTests {
@Test @Test
public void doFilterWhenCustomAuthenticationDetailsSourceThenUsed() throws Exception { public void doFilterWhenCustomAuthenticationDetailsSourceThenUsed() throws Exception {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), this.principal,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
MockHttpServletRequest request = createAuthorizationRequest(registeredClient); MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource = mock( AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource = mock(
@ -473,11 +472,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
given(authenticationDetailsSource.buildDetails(request)).willReturn(webAuthenticationDetails); given(authenticationDetailsSource.buildDetails(request)).willReturn(webAuthenticationDetails);
this.filter.setAuthenticationDetailsSource(authenticationDetailsSource); this.filter.setAuthenticationDetailsSource(authenticationDetailsSource);
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken( given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthentication);
AUTHORIZATION_URI, registeredClient.getClientId(), this.principal, this.authorizationCode,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes());
authorizationCodeRequestAuthenticationResult.setAuthenticated(true);
given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthenticationResult);
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class); FilterChain filterChain = mock(FilterChain.class);
@ -486,7 +481,27 @@ public class OAuth2AuthorizationEndpointFilterTests {
verify(authenticationDetailsSource).buildDetails(any()); verify(authenticationDetailsSource).buildDetails(any());
verify(this.authenticationManager).authenticate(any()); verify(this.authenticationManager).authenticate(any());
verifyNoInteractions(filterChain); verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@Test
public void doFilterWhenAuthorizationRequestPrincipalNotAuthenticatedThenCommenceAuthentication() throws Exception {
this.principal.setAuthenticated(false);
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken(
AUTHORIZATION_URI, registeredClient.getClientId(), this.principal,
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
authorizationCodeRequestAuthenticationResult.setAuthenticated(false);
given(this.authenticationManager.authenticate(any())).willReturn(authorizationCodeRequestAuthenticationResult);
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
this.filter.doFilter(request, response, filterChain);
verify(this.authenticationManager).authenticate(any());
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
} }
@Test @Test

View File

@ -705,6 +705,9 @@ public final class ClientRegistration implements Serializable {
if (!AuthorizationGrantType.AUTHORIZATION_CODE.equals(this.authorizationGrantType) if (!AuthorizationGrantType.AUTHORIZATION_CODE.equals(this.authorizationGrantType)
&& this.clientSettings.isRequireProofKey()) { && this.clientSettings.isRequireProofKey()) {
this.clientSettings = ClientSettings.builder().requireProofKey(false).build(); this.clientSettings = ClientSettings.builder().requireProofKey(false).build();
logger.warn(LogMessage.format(
"clientSettings.isRequireProofKey=true is only valid with authorizationGrantType=%s. Got authorizationGrantType=%s. Resetting to clientSettings.isRequireProofKey=false",
AuthorizationGrantType.AUTHORIZATION_CODE, this.authorizationGrantType));
} }
} }

View File

@ -319,17 +319,21 @@ public final class NimbusJwtDecoder implements JwtDecoder {
} }
/** /**
* Whether to use Nimbus's {@code typ} header verification. This is {@code false} * Whether to use Nimbus's typ header verification. This is {@code true} by
* by default. * default, however it may change to {@code false} in a future major release.
* *
* <p> * <p>
* By turning on this feature, {@link NimbusJwtDecoder} will delegate checking the * By turning off this feature, {@link NimbusJwtDecoder} expects applications to
* {@code typ} header to Nimbus by using Nimbus's default * check the {@code typ} header themselves in order to determine what kind of
* {@link JOSEObjectTypeVerifier}. * validation is needed
* </p> * </p>
* *
* <p> * <p>
* When this is set to {@code false}, this: <code> * This is done for you when you use {@link JwtValidators} to construct a
* validator.
*
* <p>
* That means that this: <code>
* NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build(); * NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build();
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer); * jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer);
* </code> * </code>
@ -596,17 +600,21 @@ public final class NimbusJwtDecoder implements JwtDecoder {
} }
/** /**
* Whether to use Nimbus's {@code typ} header verification. This is {@code false} * Whether to use Nimbus's typ header verification. This is {@code true} by
* by default. * default, however it may change to {@code false} in a future major release.
* *
* <p> * <p>
* By turning on this feature, {@link NimbusJwtDecoder} will delegate checking the * By turning off this feature, {@link NimbusJwtDecoder} expects applications to
* {@code typ} header to Nimbus by using Nimbus's default * check the {@code typ} header themselves in order to determine what kind of
* {@link JOSEObjectTypeVerifier}. * validation is needed
* </p> * </p>
* *
* <p> * <p>
* When this is set to {@code false}, this: <code> * This is done for you when you use {@link JwtValidators} to construct a
* validator.
*
* <p>
* That means that this: <code>
* NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build(); * NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build();
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer); * jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer);
* </code> * </code>
@ -721,17 +729,21 @@ public final class NimbusJwtDecoder implements JwtDecoder {
} }
/** /**
* Whether to use Nimbus's {@code typ} header verification. This is {@code false} * Whether to use Nimbus's typ header verification. This is {@code true} by
* by default. * default, however it may change to {@code false} in a future major release.
* *
* <p> * <p>
* By turning on this feature, {@link NimbusJwtDecoder} will delegate checking the * By turning off this feature, {@link NimbusJwtDecoder} expects applications to
* {@code typ} header to Nimbus by using Nimbus's default * check the {@code typ} header themselves in order to determine what kind of
* {@link JOSEObjectTypeVerifier}. * validation is needed
* </p> * </p>
* *
* <p> * <p>
* When this is set to {@code false}, this: <code> * This is done for you when you use {@link JwtValidators} to construct a
* validator.
*
* <p>
* That means that this: <code>
* NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build(); * NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build();
* jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer); * jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer);
* </code> * </code>

View File

@ -136,7 +136,7 @@ public final class NimbusJwtEncoder implements JwtEncoder {
algorithm = MacAlgorithm.from(jwk.getAlgorithm().getName()); algorithm = MacAlgorithm.from(jwk.getAlgorithm().getName());
} }
Assert.notNull(algorithm, "Failed to derive supported algorithm from " + jwk.getAlgorithm()); Assert.notNull(algorithm, "Failed to derive supported algorithm from " + jwk.getAlgorithm());
JwsHeader.Builder builder = JwsHeader.with(algorithm).type("JWT").keyId(jwk.getKeyID()); JwsHeader.Builder builder = JwsHeader.with(algorithm).type(jwk.getKeyType().getValue()).keyId(jwk.getKeyID());
URI x509Url = jwk.getX509CertURL(); URI x509Url = jwk.getX509CertURL();
if (x509Url != null) { if (x509Url != null) {
builder.x509Url(jwk.getX509CertURL().toASCIIString()); builder.x509Url(jwk.getX509CertURL().toASCIIString());

View File

@ -1,114 +0,0 @@
/*
* Copyright 2004-present 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.jwt;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.util.Objects;
import java.util.Set;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.crypto.impl.ECDSA;
import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.KeyOperation;
import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import org.junit.jupiter.api.Test;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Use {@link NimbusJwtDecoder} to decode JWT's encoded with {@link NimbusJwtEncoder}
*
* @author Ziqin Wang
*/
class NimbusJwtEncoderDecoderTests {
@Test
void encodeAndDecodeHS256() throws GeneralSecurityException {
KeyGenerator keyGenerator = KeyGenerator.getInstance("HmacSHA256");
SecretKey secretKey = keyGenerator.generateKey();
NimbusJwtEncoder jwtEncoder = NimbusJwtEncoder.withSecretKey(secretKey).build();
JwtClaimsSet claims = TestJwtClaimsSets.jwtClaimsSet().build();
String jwt = jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withSecretKey(secretKey).build();
Jwt decodedJwt = jwtDecoder.decode(jwt);
assertThat(decodedJwt.getSubject()).isEqualTo("subject");
}
@Test
void encodeAndDecodeRS256() throws GeneralSecurityException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
NimbusJwtEncoder jwtEncoder = NimbusJwtEncoder.withKeyPair(publicKey, privateKey).build();
JwtClaimsSet claims = TestJwtClaimsSets.jwtClaimsSet().build();
String jwt = jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(publicKey).build();
Jwt decodedJwt = jwtDecoder.decode(jwt);
assertThat(decodedJwt.getSubject()).isEqualTo("subject");
}
@Test
void encodeAndDecodeES256() throws GeneralSecurityException, JOSEException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
keyPairGenerator.initialize(new ECGenParameterSpec("secp256r1"));
KeyPair keyPair = keyPairGenerator.generateKeyPair();
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
NimbusJwtEncoder jwtEncoder = NimbusJwtEncoder.withKeyPair(publicKey, privateKey).build();
JwtClaimsSet claims = TestJwtClaimsSets.jwtClaimsSet().build();
String jwt = jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
Curve curve = Curve.forECParameterSpec(publicKey.getParams());
JWK jwk = new ECKey.Builder(curve, publicKey).keyOperations(Set.of(KeyOperation.VERIFY))
.keyUse(KeyUse.SIGNATURE)
.algorithm(ECDSA.resolveAlgorithm(curve))
.keyIDFromThumbprint()
.build();
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSource(new ImmutableJWKSet<>(new JWKSet(jwk)))
.jwsAlgorithm(Objects.requireNonNull(SignatureAlgorithm.from(jwk.getAlgorithm().getName())))
.build();
Jwt decodedJwt = jwtDecoder.decode(jwt);
assertThat(decodedJwt.getSubject()).isEqualTo("subject");
}
}

View File

@ -146,14 +146,6 @@ public class PathPatternRequestMatcherTests {
assertThat(matcher.matches(mock)).isTrue(); assertThat(matcher.matches(mock)).isTrue();
} }
@Test
void matcherWhenRequestMethodIsNullThenNoNullPointerException() {
RequestMatcher matcher = pathPattern(HttpMethod.GET, "/");
MockHttpServletRequest mock = new MockHttpServletRequest(null, "/");
ServletRequestPathUtils.parseAndCache(mock);
assertThat(matcher.matches(mock)).isFalse();
}
MockHttpServletRequest request(String uri) { MockHttpServletRequest request(String uri) {
MockHttpServletRequest request = new MockHttpServletRequest("GET", uri); MockHttpServletRequest request = new MockHttpServletRequest("GET", uri);
ServletRequestPathUtils.parseAndCache(request); ServletRequestPathUtils.parseAndCache(request);

View File

@ -174,12 +174,6 @@ public class WebAuthnAuthenticationFilter extends AbstractAuthenticationProcessi
this.delegate = delegate; this.delegate = delegate;
} }
@Override
public boolean canRead(ResolvableType type, @Nullable MediaType mediaType) {
Class<?> clazz = type.resolve();
return (clazz != null) ? canRead(clazz, mediaType) : canRead(mediaType);
}
@Override @Override
public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) { public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
return this.delegate.canRead(clazz, mediaType); return this.delegate.canRead(clazz, mediaType);
@ -212,11 +206,6 @@ public class WebAuthnAuthenticationFilter extends AbstractAuthenticationProcessi
return this.delegate.read(type.getType(), null, inputMessage); return this.delegate.read(type.getType(), null, inputMessage);
} }
@Override
public boolean canWrite(ResolvableType targetType, Class<?> valueClass, @Nullable MediaType mediaType) {
return this.delegate.canWrite(targetType.getType(), valueClass, mediaType);
}
} }
} }