Merge branch 'spring-projects:main' into fix-ror-link

This commit is contained in:
Konstantin Filtschew 2025-06-20 14:51:42 +02:00 committed by GitHub
commit 18d8a3a56e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
524 changed files with 12995 additions and 4878 deletions

View File

@ -4,6 +4,32 @@ registries:
type: maven-repository type: maven-repository
url: https://repo.spring.io/milestone url: https://repo.spring.io/milestone
updates: updates:
- package-ecosystem: gradle
target-branch: 6.5.x
directory: /
schedule:
interval: daily
time: '03:00'
timezone: Etc/UTC
labels:
- 'type: dependency-upgrade'
registries:
- spring-milestones
ignore:
- dependency-name: com.nimbusds:nimbus-jose-jwt
- dependency-name: org.python:jython
- dependency-name: org.apache.directory.server:*
- dependency-name: org.apache.directory.shared:*
- dependency-name: org.junit:junit-bom
update-types:
- version-update:semver-major
- dependency-name: org.mockito:mockito-bom
update-types:
- version-update:semver-major
- dependency-name: '*'
update-types:
- version-update:semver-major
- version-update:semver-minor
- package-ecosystem: gradle - package-ecosystem: gradle
target-branch: 6.4.x target-branch: 6.4.x
directory: / directory: /

View File

@ -39,48 +39,25 @@ jobs:
toolchain: 17 toolchain: 17
with: with:
java-version: ${{ matrix.java-version }} java-version: ${{ matrix.java-version }}
test-args: --refresh-dependencies -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=6.2.+ -PreactorVersion=2023.0.+ -PspringDataVersion=2024.0.+ --stacktrace test-args: --refresh-dependencies -PforceMavenRepositories=snapshot,https://oss.sonatype.org/content/repositories/snapshots -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=7.+ -PreactorVersion=2025.+ -PspringDataVersion=2025.+ --stacktrace
secrets: inherit secrets: inherit
check-samples:
name: Check Samples
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'spring-projects' }}
steps:
- uses: actions/checkout@v4
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v2
with:
java-version: 17
distribution: temurin
- name: Check samples project
env:
LOCAL_REPOSITORY_PATH: ${{ github.workspace }}/build/publications/repos
SAMPLES_DIR: ../spring-security-samples
run: |
# Extract version from gradle.properties
version=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}')
# Extract samplesBranch from gradle.properties
samples_branch=$(cat gradle.properties | grep "samplesBranch=" | awk -F'=' '{print $2}')
./gradlew publishMavenJavaPublicationToLocalRepository
./gradlew cloneRepository -PrepositoryName="spring-projects/spring-security-samples" -Pref="$samples_branch" -PcloneOutputDirectory="$SAMPLES_DIR"
./gradlew --refresh-dependencies --project-dir "$SAMPLES_DIR" --init-script spring-security-ci.gradle -PlocalRepositoryPath="$LOCAL_REPOSITORY_PATH" -PspringSecurityVersion="$version" test integrationTest
deploy-artifacts: deploy-artifacts:
name: Deploy Artifacts name: Deploy Artifacts
needs: [ build, test, check-samples ] needs: [ build, test]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@v1 uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@v1
with: with:
should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }} should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }}
secrets: inherit secrets: inherit
deploy-docs: deploy-docs:
name: Deploy Docs name: Deploy Docs
needs: [ build, test, check-samples ] needs: [ build, test ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-docs.yml@v1 uses: spring-io/spring-security-release-tools/.github/workflows/deploy-docs.yml@v1
with: with:
should-deploy-docs: ${{ needs.build.outputs.should-deploy-artifacts }} should-deploy-docs: ${{ needs.build.outputs.should-deploy-artifacts }}
secrets: inherit secrets: inherit
deploy-schema: deploy-schema:
name: Deploy Schema name: Deploy Schema
needs: [ build, test, check-samples ] needs: [ build, test ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@v1 uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@v1
with: with:
should-deploy-schema: ${{ needs.build.outputs.should-deploy-artifacts }} should-deploy-schema: ${{ needs.build.outputs.should-deploy-artifacts }}

View File

@ -11,7 +11,7 @@ jobs:
strategy: strategy:
matrix: matrix:
# List of active maintenance branches. # List of active maintenance branches.
branch: [ main, 6.4.x, 6.3.x ] branch: [ main, 6.5.x, 6.4.x, 6.3.x ]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout

View File

@ -80,6 +80,11 @@ class RepositoryConventionPlugin implements Plugin<Project> {
} }
url = 'https://repo.spring.io/release/' url = 'https://repo.spring.io/release/'
} }
forceMavenRepositories.findAll { it.startsWith('https://') || it.startsWith('file://') }.each { mavenUrl ->
maven {
url mavenUrl
}
}
} }
} }

View File

@ -32,10 +32,13 @@ public class SchemaZipPlugin implements Plugin<Project> {
for (def key : schemas.keySet()) { for (def key : schemas.keySet()) {
def shortName = key.replaceAll(/http.*schema.(.*).spring-.*/, '$1') def shortName = key.replaceAll(/http.*schema.(.*).spring-.*/, '$1')
assert shortName != key assert shortName != key
def schemaResourceName = schemas.get(key)
File xsdFile = module.sourceSets.main.resources.find { File xsdFile = module.sourceSets.main.resources.find {
it.path.endsWith(schemas.get(key)) it.path.endsWith(schemaResourceName)
}
if (xsdFile == null) {
throw new IllegalStateException("Could not find schema file for resource name " + schemaResourceName + " in src/main/resources")
} }
assert xsdFile != null
schemaZip.into (shortName) { schemaZip.into (shortName) {
duplicatesStrategy 'exclude' duplicatesStrategy 'exclude'
from xsdFile.path from xsdFile.path

View File

@ -81,9 +81,6 @@ public class CheckClasspathForProhibitedDependencies extends DefaultTask {
if (group.startsWith("javax")) { if (group.startsWith("javax")) {
return true; return true;
} }
if (group.equals("commons-logging")) {
return true;
}
if (group.equals("org.slf4j") && id.getName().equals("jcl-over-slf4j")) { if (group.equals("org.slf4j") && id.getName().equals("jcl-over-slf4j")) {
return true; return true;
} }

View File

@ -78,12 +78,6 @@ dependencies {
exclude group: 'commons-logging', module: 'commons-logging' exclude group: 'commons-logging', module: 'commons-logging'
exclude group: 'xml-apis', module: 'xml-apis' exclude group: 'xml-apis', module: 'xml-apis'
} }
testImplementation "org.apache.directory.server:apacheds-core"
testImplementation "org.apache.directory.server:apacheds-core-entry"
testImplementation "org.apache.directory.server:apacheds-protocol-shared"
testImplementation "org.apache.directory.server:apacheds-protocol-ldap"
testImplementation "org.apache.directory.server:apacheds-server-jndi"
testImplementation 'org.apache.directory.shared:shared-ldap'
testImplementation "com.unboundid:unboundid-ldapsdk" testImplementation "com.unboundid:unboundid-ldapsdk"
testImplementation 'jakarta.persistence:jakarta.persistence-api' testImplementation 'jakarta.persistence:jakarta.persistence-api'
testImplementation "org.hibernate.orm:hibernate-core" testImplementation "org.hibernate.orm:hibernate-core"
@ -127,6 +121,7 @@ dependencies {
testRuntimeOnly 'org.hsqldb:hsqldb' testRuntimeOnly 'org.hsqldb:hsqldb'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
} }
def rncToXsd = tasks.named('rncToXsd', RncToXsd) def rncToXsd = tasks.named('rncToXsd', RncToXsd)

View File

@ -44,7 +44,7 @@ import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMap
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource; import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider; import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.security.ldap.server.ApacheDSContainer; import org.springframework.security.ldap.server.UnboundIdContainer;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator; import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
@ -326,11 +326,11 @@ public class LdapAuthenticationProviderBuilderSecurityBuilderTests {
abstract static class BaseLdapServerConfig extends BaseLdapProviderConfig { abstract static class BaseLdapServerConfig extends BaseLdapProviderConfig {
@Bean @Bean
ApacheDSContainer ldapServer() throws Exception { UnboundIdContainer ldapServer() throws Exception {
ApacheDSContainer apacheDSContainer = new ApacheDSContainer("dc=springframework,dc=org", UnboundIdContainer unboundIdContainer = new UnboundIdContainer("dc=springframework,dc=org",
"classpath:/test-server.ldif"); "classpath:/test-server.ldif");
apacheDSContainer.setPort(getPort()); unboundIdContainer.setPort(getPort());
return apacheDSContainer; return unboundIdContainer;
} }
} }

View File

@ -43,7 +43,7 @@ import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMap
import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource; import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.server.ApacheDSContainer; import org.springframework.security.ldap.server.UnboundIdContainer;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator; import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator; import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.UserDetailsContextMapper; import org.springframework.security.ldap.userdetails.UserDetailsContextMapper;
@ -226,18 +226,18 @@ public class LdapBindAuthenticationManagerFactoryITests {
@EnableWebSecurity @EnableWebSecurity
abstract static class BaseLdapServerConfig implements DisposableBean { abstract static class BaseLdapServerConfig implements DisposableBean {
private ApacheDSContainer container; private UnboundIdContainer container;
@Bean @Bean
ApacheDSContainer ldapServer() throws Exception { UnboundIdContainer ldapServer() {
this.container = new ApacheDSContainer("dc=springframework,dc=org", "classpath:/test-server.ldif"); this.container = new UnboundIdContainer("dc=springframework,dc=org", "classpath:/test-server.ldif");
this.container.setPort(0); this.container.setPort(0);
return this.container; return this.container;
} }
@Bean @Bean
BaseLdapPathContextSource contextSource(ApacheDSContainer container) { BaseLdapPathContextSource contextSource(UnboundIdContainer container) {
int port = container.getLocalPort(); int port = container.getPort();
return new DefaultSpringSecurityContextSource("ldap://localhost:" + port + "/dc=springframework,dc=org"); return new DefaultSpringSecurityContextSource("ldap://localhost:" + port + "/dc=springframework,dc=org");
} }

View File

@ -31,7 +31,7 @@ import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource; import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.server.ApacheDSContainer; import org.springframework.security.ldap.server.UnboundIdContainer;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
@ -93,18 +93,18 @@ public class LdapPasswordComparisonAuthenticationManagerFactoryITests {
@EnableWebSecurity @EnableWebSecurity
abstract static class BaseLdapServerConfig implements DisposableBean { abstract static class BaseLdapServerConfig implements DisposableBean {
private ApacheDSContainer container; private UnboundIdContainer container;
@Bean @Bean
ApacheDSContainer ldapServer() throws Exception { UnboundIdContainer ldapServer() {
this.container = new ApacheDSContainer("dc=springframework,dc=org", "classpath:/test-server.ldif"); this.container = new UnboundIdContainer("dc=springframework,dc=org", "classpath:/test-server.ldif");
this.container.setPort(0); this.container.setPort(0);
return this.container; return this.container;
} }
@Bean @Bean
BaseLdapPathContextSource contextSource(ApacheDSContainer container) { BaseLdapPathContextSource contextSource(UnboundIdContainer container) {
int port = container.getLocalPort(); int port = container.getPort();
return new DefaultSpringSecurityContextSource("ldap://localhost:" + port + "/dc=springframework,dc=org"); return new DefaultSpringSecurityContextSource("ldap://localhost:" + port + "/dc=springframework,dc=org");
} }

View File

@ -56,7 +56,7 @@ public class LdapProviderBeanDefinitionParserTests {
AuthenticationManager authenticationManager = this.appCtx.getBean(BeanIds.AUTHENTICATION_MANAGER, AuthenticationManager authenticationManager = this.appCtx.getBean(BeanIds.AUTHENTICATION_MANAGER,
AuthenticationManager.class); AuthenticationManager.class);
Authentication auth = authenticationManager Authentication auth = authenticationManager
.authenticate(UsernamePasswordAuthenticationToken.unauthenticated("ben", "benspassword")); .authenticate(UsernamePasswordAuthenticationToken.unauthenticated("otherben", "otherbenspassword"));
UserDetails ben = (UserDetails) auth.getPrincipal(); UserDetails ben = (UserDetails) auth.getPrincipal();
assertThat(ben.getAuthorities()).hasSize(3); assertThat(ben.getAuthorities()).hasSize(3);
} }
@ -127,6 +127,27 @@ public class LdapProviderBeanDefinitionParserTests {
assertThat(auth).isNotNull(); assertThat(auth).isNotNull();
} }
@Test
public void supportsShaPasswordEncoder() {
this.appCtx = new InMemoryXmlApplicationContext("""
<ldap-server ldif='classpath:test-server.ldif' port='0'/>
<authentication-manager>
<ldap-authentication-provider user-dn-pattern='uid={0},ou=people'>
<password-compare>
<password-encoder ref='pe' />
</password-compare>
</ldap-authentication-provider>
</authentication-manager>
<b:bean id='pe' class='org.springframework.security.crypto.password.LdapShaPasswordEncoder' />
""");
AuthenticationManager authenticationManager = this.appCtx.getBean(BeanIds.AUTHENTICATION_MANAGER,
AuthenticationManager.class);
Authentication auth = authenticationManager
.authenticate(UsernamePasswordAuthenticationToken.unauthenticated("ben", "benspassword"));
assertThat(auth).isNotNull();
}
@Test @Test
public void inetOrgContextMapperIsSupported() { public void inetOrgContextMapperIsSupported() {
this.appCtx = new InMemoryXmlApplicationContext( this.appCtx = new InMemoryXmlApplicationContext(

View File

@ -26,7 +26,7 @@ import org.springframework.ldap.core.LdapTemplate;
import org.springframework.security.config.BeanIds; import org.springframework.security.config.BeanIds;
import org.springframework.security.config.util.InMemoryXmlApplicationContext; import org.springframework.security.config.util.InMemoryXmlApplicationContext;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource; import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.server.ApacheDSContainer; import org.springframework.security.ldap.server.UnboundIdContainer;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -92,9 +92,9 @@ public class LdapServerBeanDefinitionParserTests {
@Test @Test
public void defaultLdifFileIsSuccessful() { public void defaultLdifFileIsSuccessful() {
this.appCtx = new InMemoryXmlApplicationContext("<ldap-server/>"); this.appCtx = new InMemoryXmlApplicationContext("<ldap-server/>");
ApacheDSContainer dsContainer = this.appCtx.getBean(ApacheDSContainer.class); UnboundIdContainer dsContainer = this.appCtx.getBean(UnboundIdContainer.class);
assertThat(ReflectionTestUtils.getField(dsContainer, "ldifResources")).isEqualTo("classpath*:*.ldif"); assertThat(ReflectionTestUtils.getField(dsContainer, "ldif")).isEqualTo("classpath*:*.ldif");
} }
private int getDefaultPort() throws IOException { private int getDefaultPort() throws IOException {

View File

@ -7,7 +7,6 @@
<logger name="org.springframework.security" level="${sec.log.level:-WARN}"/> <logger name="org.springframework.security" level="${sec.log.level:-WARN}"/>
<logger name="org.apache.directory" level="ERROR"/>
<logger name="JdbmTable" level="INFO"/> <logger name="JdbmTable" level="INFO"/>
<logger name="JdbmIndex" level="INFO"/> <logger name="JdbmIndex" level="INFO"/>
<logger name="org.apache.mina" level="WARN"/> <logger name="org.apache.mina" level="WARN"/>

View File

@ -54,8 +54,6 @@ public abstract class BeanIds {
public static final String METHOD_SECURITY_METADATA_SOURCE_ADVISOR = PREFIX + "methodSecurityMetadataSourceAdvisor"; public static final String METHOD_SECURITY_METADATA_SOURCE_ADVISOR = PREFIX + "methodSecurityMetadataSourceAdvisor";
public static final String EMBEDDED_APACHE_DS = PREFIX + "apacheDirectoryServerContainer";
public static final String EMBEDDED_UNBOUNDID = PREFIX + "unboundidServerContainer"; public static final String EMBEDDED_UNBOUNDID = PREFIX + "unboundidServerContainer";
public static final String CONTEXT_SOURCE = PREFIX + "securityContextSource"; public static final String CONTEXT_SOURCE = PREFIX + "securityContextSource";

View File

@ -96,7 +96,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
pc.getReaderContext() pc.getReaderContext()
.fatal("You cannot use a spring-security-2.0.xsd or spring-security-3.0.xsd or " .fatal("You cannot use a spring-security-2.0.xsd or spring-security-3.0.xsd or "
+ "spring-security-3.1.xsd schema or spring-security-3.2.xsd schema or spring-security-4.0.xsd schema " + "spring-security-3.1.xsd schema or spring-security-3.2.xsd schema or spring-security-4.0.xsd schema "
+ "with Spring Security 6.5. Please update your schema declarations to the 6.5 schema.", + "with Spring Security 7.0. Please update your schema declarations to the 7.0 schema.",
element); element);
} }
String name = pc.getDelegate().getLocalName(element); String name = pc.getDelegate().getLocalName(element);
@ -221,7 +221,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
private boolean matchesVersionInternal(Element element) { private boolean matchesVersionInternal(Element element) {
String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation"); String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation");
return schemaLocation.matches("(?m).*spring-security-6\\.5.*.xsd.*") return schemaLocation.matches("(?m).*spring-security-7\\.0.*.xsd.*")
|| schemaLocation.matches("(?m).*spring-security.xsd.*") || schemaLocation.matches("(?m).*spring-security.xsd.*")
|| !schemaLocation.matches("(?m).*spring-security.*"); || !schemaLocation.matches("(?m).*spring-security.*");
} }

View File

@ -37,7 +37,6 @@ import org.springframework.security.ldap.authentication.LdapAuthenticator;
import org.springframework.security.ldap.authentication.PasswordComparisonAuthenticator; import org.springframework.security.ldap.authentication.PasswordComparisonAuthenticator;
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch; import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
import org.springframework.security.ldap.search.LdapUserSearch; import org.springframework.security.ldap.search.LdapUserSearch;
import org.springframework.security.ldap.server.ApacheDSContainer;
import org.springframework.security.ldap.server.UnboundIdContainer; import org.springframework.security.ldap.server.UnboundIdContainer;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator; import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.InetOrgPersonContextMapper; import org.springframework.security.ldap.userdetails.InetOrgPersonContextMapper;
@ -60,12 +59,8 @@ import org.springframework.util.ClassUtils;
public class LdapAuthenticationProviderConfigurer<B extends ProviderManagerBuilder<B>> public class LdapAuthenticationProviderConfigurer<B extends ProviderManagerBuilder<B>>
extends SecurityConfigurerAdapter<AuthenticationManager, B> { extends SecurityConfigurerAdapter<AuthenticationManager, B> {
private static final String APACHEDS_CLASSNAME = "org.apache.directory.server.core.DefaultDirectoryService";
private static final String UNBOUNDID_CLASSNAME = "com.unboundid.ldap.listener.InMemoryDirectoryServer"; private static final String UNBOUNDID_CLASSNAME = "com.unboundid.ldap.listener.InMemoryDirectoryServer";
private static final boolean apacheDsPresent;
private static final boolean unboundIdPresent; private static final boolean unboundIdPresent;
private String groupRoleAttribute = "cn"; private String groupRoleAttribute = "cn";
@ -100,7 +95,6 @@ public class LdapAuthenticationProviderConfigurer<B extends ProviderManagerBuild
static { static {
ClassLoader classLoader = LdapAuthenticationProviderConfigurer.class.getClassLoader(); ClassLoader classLoader = LdapAuthenticationProviderConfigurer.class.getClassLoader();
apacheDsPresent = ClassUtils.isPresent(APACHEDS_CLASSNAME, classLoader);
unboundIdPresent = ClassUtils.isPresent(UNBOUNDID_CLASSNAME, classLoader); unboundIdPresent = ClassUtils.isPresent(UNBOUNDID_CLASSNAME, classLoader);
} }
@ -467,8 +461,6 @@ public class LdapAuthenticationProviderConfigurer<B extends ProviderManagerBuild
*/ */
public final class ContextSourceBuilder { public final class ContextSourceBuilder {
private static final String APACHEDS_CLASSNAME = "org.apache.directory.server.core.DefaultDirectoryService";
private static final String UNBOUNDID_CLASSNAME = "com.unboundid.ldap.listener.InMemoryDirectoryServer"; private static final String UNBOUNDID_CLASSNAME = "com.unboundid.ldap.listener.InMemoryDirectoryServer";
private static final int DEFAULT_PORT = 33389; private static final int DEFAULT_PORT = 33389;
@ -584,14 +576,8 @@ public class LdapAuthenticationProviderConfigurer<B extends ProviderManagerBuild
return contextSource; return contextSource;
} }
private void startEmbeddedLdapServer() throws Exception { private void startEmbeddedLdapServer() {
if (apacheDsPresent) { if (unboundIdPresent) {
ApacheDSContainer apacheDsContainer = new ApacheDSContainer(this.root, this.ldif);
apacheDsContainer.setPort(getPort());
postProcess(apacheDsContainer);
this.port = apacheDsContainer.getLocalPort();
}
else if (unboundIdPresent) {
UnboundIdContainer unboundIdContainer = new UnboundIdContainer(this.root, this.ldif); UnboundIdContainer unboundIdContainer = new UnboundIdContainer(this.root, this.ldif);
unboundIdContainer.setPort(getPort()); unboundIdContainer.setPort(getPort());
postProcess(unboundIdContainer); postProcess(unboundIdContainer);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,13 +16,22 @@
package org.springframework.security.config.annotation.method.configuration; package org.springframework.security.config.annotation.method.configuration;
import java.util.List;
import org.springframework.aop.framework.AopInfrastructureBean; import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role; import org.springframework.context.annotation.Role;
import org.springframework.core.Ordered;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.SliceImpl;
import org.springframework.data.geo.GeoPage;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.GeoResults;
import org.springframework.security.aot.hint.SecurityHintsRegistrar; import org.springframework.security.aot.hint.SecurityHintsRegistrar;
import org.springframework.security.authorization.AuthorizationProxyFactory; import org.springframework.security.authorization.AuthorizationProxyFactory;
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
import org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar; import org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar;
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ -34,4 +43,45 @@ final class AuthorizationProxyDataConfiguration implements AopInfrastructureBean
return new AuthorizeReturnObjectDataHintsRegistrar(proxyFactory); return new AuthorizeReturnObjectDataHintsRegistrar(proxyFactory);
} }
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
DataTargetVisitor dataTargetVisitor() {
return new DataTargetVisitor();
}
private static final class DataTargetVisitor implements AuthorizationAdvisorProxyFactory.TargetVisitor, Ordered {
private static final int DEFAULT_ORDER = 200;
@Override
public Object visit(AuthorizationAdvisorProxyFactory proxyFactory, Object target) {
if (target instanceof GeoResults<?> geoResults) {
return new GeoResults<>(proxyFactory.proxy(geoResults.getContent()), geoResults.getAverageDistance());
}
if (target instanceof GeoResult<?> geoResult) {
return new GeoResult<>(proxyFactory.proxy(geoResult.getContent()), geoResult.getDistance());
}
if (target instanceof GeoPage<?> geoPage) {
GeoResults<?> results = new GeoResults<>(proxyFactory.proxy(geoPage.getContent()),
geoPage.getAverageDistance());
return new GeoPage<>(results, geoPage.getPageable(), geoPage.getTotalElements());
}
if (target instanceof PageImpl<?> page) {
List<?> content = proxyFactory.proxy(page.getContent());
return new PageImpl<>(content, page.getPageable(), page.getTotalElements());
}
if (target instanceof SliceImpl<?> slice) {
List<?> content = proxyFactory.proxy(slice.getContent());
return new SliceImpl<>(content, slice.getPageable(), slice.hasNext());
}
return null;
}
@Override
public int getOrder() {
return DEFAULT_ORDER;
}
}
} }

View File

@ -16,8 +16,12 @@
package org.springframework.security.config.annotation.method.configuration; package org.springframework.security.config.annotation.method.configuration;
import java.util.List;
import java.util.Map; import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -25,12 +29,17 @@ import org.springframework.context.annotation.Role;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory; import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
import org.springframework.security.web.util.ThrowableAnalyzer;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View; import org.springframework.web.servlet.View;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
@Configuration @Configuration
class AuthorizationProxyWebConfiguration { class AuthorizationProxyWebConfiguration implements WebMvcConfigurer {
@Bean @Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE) @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ -38,6 +47,18 @@ class AuthorizationProxyWebConfiguration {
return new WebTargetVisitor(); return new WebTargetVisitor();
} }
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
for (int i = 0; i < resolvers.size(); i++) {
HandlerExceptionResolver resolver = resolvers.get(i);
if (resolver instanceof DefaultHandlerExceptionResolver) {
resolvers.add(i, new AccessDeniedExceptionResolver());
return;
}
}
resolvers.add(new AccessDeniedExceptionResolver());
}
static class WebTargetVisitor implements AuthorizationAdvisorProxyFactory.TargetVisitor, Ordered { static class WebTargetVisitor implements AuthorizationAdvisorProxyFactory.TargetVisitor, Ordered {
private static final int DEFAULT_ORDER = 100; private static final int DEFAULT_ORDER = 100;
@ -54,7 +75,7 @@ class AuthorizationProxyWebConfiguration {
if (target instanceof ModelAndView mav) { if (target instanceof ModelAndView mav) {
View view = mav.getView(); View view = mav.getView();
String viewName = mav.getViewName(); String viewName = mav.getViewName();
Map<String, Object> model = (Map<String, Object>) proxyFactory.proxy(mav.getModel()); Map<String, Object> model = proxyFactory.proxy(mav.getModel());
ModelAndView proxied = (view != null) ? new ModelAndView(view, model) ModelAndView proxied = (view != null) ? new ModelAndView(view, model)
: new ModelAndView(viewName, model); : new ModelAndView(viewName, model);
proxied.setStatus(mav.getStatus()); proxied.setStatus(mav.getStatus());
@ -70,4 +91,24 @@ class AuthorizationProxyWebConfiguration {
} }
static class AccessDeniedExceptionResolver implements HandlerExceptionResolver {
final ThrowableAnalyzer throwableAnalyzer = new ThrowableAnalyzer();
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
Throwable accessDeniedException = this.throwableAnalyzer
.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
if (accessDeniedException != null) {
return new ModelAndView((model, req, res) -> {
throw ex;
});
}
return null;
}
}
} }

View File

@ -107,7 +107,7 @@ import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
* A {@link HttpSecurity} is similar to Spring Security's XML &lt;http&gt; element in the * A {@link HttpSecurity} is similar to Spring Security's XML &lt;http&gt; element in the
* namespace configuration. It allows configuring web based security for specific http * namespace configuration. It allows configuring web based security for specific http
* requests. By default it will be applied to all requests, but can be restricted using * requests. By default it will be applied to all requests, but can be restricted using
* {@link #requestMatcher(RequestMatcher)} or other similar methods. * {@link #authorizeHttpRequests(Customizer)} or other similar methods.
* *
* <h2>Example Usage</h2> * <h2>Example Usage</h2>
* *
@ -124,7 +124,12 @@ import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
* *
* &#064;Bean * &#064;Bean
* public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { * public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
* http.authorizeHttpRequests().requestMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;).and().formLogin(); * http
* .authorizeHttpRequests((authorizeHttpRequests) -&gt;
* authorizeHttpRequests
* .requestMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
* )
* .formLogin(withDefaults());
* return http.build(); * return http.build();
* } * }
* *

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2023 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,9 +19,11 @@ package org.springframework.security.config.annotation.web.configurers;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.function.Supplier;
import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.ObservationRegistry;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AccessDeniedException;
@ -34,13 +36,17 @@ import org.springframework.security.web.access.CompositeAccessDeniedHandler;
import org.springframework.security.web.access.DelegatingAccessDeniedHandler; import org.springframework.security.web.access.DelegatingAccessDeniedHandler;
import org.springframework.security.web.access.ObservationMarkingAccessDeniedHandler; import org.springframework.security.web.access.ObservationMarkingAccessDeniedHandler;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfAuthenticationStrategy; import org.springframework.security.web.csrf.CsrfAuthenticationStrategy;
import org.springframework.security.web.csrf.CsrfFilter; import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfLogoutHandler; import org.springframework.security.web.csrf.CsrfLogoutHandler;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository; import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.security.web.csrf.CsrfTokenRequestHandler; import org.springframework.security.web.csrf.CsrfTokenRequestHandler;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository; import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.security.web.csrf.MissingCsrfTokenException; import org.springframework.security.web.csrf.MissingCsrfTokenException;
import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler;
import org.springframework.security.web.session.InvalidSessionAccessDeniedHandler; import org.springframework.security.web.session.InvalidSessionAccessDeniedHandler;
import org.springframework.security.web.session.InvalidSessionStrategy; import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.security.web.util.matcher.AndRequestMatcher; import org.springframework.security.web.util.matcher.AndRequestMatcher;
@ -48,6 +54,7 @@ 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.StringUtils;
/** /**
* Adds * Adds
@ -214,6 +221,21 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
return this; return this;
} }
/**
* <p>
* Sensible CSRF defaults when used in combination with a single page application.
* Creates a cookie-based token repository and a custom request handler to resolve the
* actual token value instead of the encoded token.
* </p>
* @return the {@link CsrfConfigurer} for further customizations
* @since 7.0
*/
public CsrfConfigurer<H> spa() {
this.csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
this.requestHandler = new SpaCsrfTokenRequestHandler();
return this;
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public void configure(H http) { public void configure(H http) {
@ -375,4 +397,27 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
} }
private static final class SpaCsrfTokenRequestHandler implements CsrfTokenRequestHandler {
private final CsrfTokenRequestAttributeHandler plain = new CsrfTokenRequestAttributeHandler();
private final CsrfTokenRequestAttributeHandler xor = new XorCsrfTokenRequestAttributeHandler();
SpaCsrfTokenRequestHandler() {
this.xor.setCsrfRequestAttributeName(null);
}
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken) {
this.xor.handle(request, response, csrfToken);
}
@Override
public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
String headerValue = request.getHeader(csrfToken.getHeaderName());
return (StringUtils.hasText(headerValue) ? this.plain : this.xor).resolveCsrfTokenValue(request, csrfToken);
}
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -161,7 +161,10 @@ public final class X509Configurer<H extends HttpSecurityBuilder<H>>
* @param subjectPrincipalRegex the regex to extract the user principal from the * @param subjectPrincipalRegex the regex to extract the user principal from the
* certificate (i.e. "CN=(.*?)(?:,|$)"). * certificate (i.e. "CN=(.*?)(?:,|$)").
* @return the {@link X509Configurer} for further customizations * @return the {@link X509Configurer} for further customizations
* @deprecated Please use {{@link #x509PrincipalExtractor(X509PrincipalExtractor)}
* instead
*/ */
@Deprecated
public X509Configurer<H> subjectPrincipalRegex(String subjectPrincipalRegex) { public X509Configurer<H> subjectPrincipalRegex(String subjectPrincipalRegex) {
SubjectDnX509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor(); SubjectDnX509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor();
principalExtractor.setSubjectDnRegex(subjectPrincipalRegex); principalExtractor.setSubjectDnRegex(subjectPrincipalRegex);

View File

@ -18,11 +18,6 @@ package org.springframework.security.config.annotation.web.configurers.oauth2.cl
import java.util.function.Function; import java.util.function.Function;
import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier;
import com.nimbusds.jose.proc.JOSEObjectTypeVerifier;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
@ -38,6 +33,7 @@ import org.springframework.security.oauth2.jwt.BadJwtException;
import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtDecoderFactory; import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
import org.springframework.security.oauth2.jwt.JwtTypeValidator;
import org.springframework.security.oauth2.jwt.JwtValidators; import org.springframework.security.oauth2.jwt.JwtValidators;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -67,8 +63,10 @@ final class OidcBackChannelLogoutAuthenticationProvider implements Authenticatio
* Construct an {@link OidcBackChannelLogoutAuthenticationProvider} * Construct an {@link OidcBackChannelLogoutAuthenticationProvider}
*/ */
OidcBackChannelLogoutAuthenticationProvider() { OidcBackChannelLogoutAuthenticationProvider() {
JwtTypeValidator type = new JwtTypeValidator("JWT", "logout+jwt");
type.setAllowEmpty(true);
Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidator = (clientRegistration) -> JwtValidators Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidator = (clientRegistration) -> JwtValidators
.createDefaultWithValidators(new OidcBackChannelLogoutTokenValidator(clientRegistration)); .createDefaultWithValidators(type, new OidcBackChannelLogoutTokenValidator(clientRegistration));
this.logoutTokenDecoderFactory = (clientRegistration) -> { this.logoutTokenDecoderFactory = (clientRegistration) -> {
String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri(); String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri();
if (!StringUtils.hasText(jwkSetUri)) { if (!StringUtils.hasText(jwkSetUri)) {
@ -79,11 +77,7 @@ final class OidcBackChannelLogoutAuthenticationProvider implements Authenticatio
null); null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
} }
JOSEObjectTypeVerifier<SecurityContext> typeVerifier = new DefaultJOSEObjectTypeVerifier<>(null, NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();
JOSEObjectType.JWT, new JOSEObjectType("logout+jwt"));
NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri)
.jwtProcessorCustomizer((processor) -> processor.setJWSTypeVerifier(typeVerifier))
.build();
decoder.setJwtValidator(jwtValidator.apply(clientRegistration)); decoder.setJwtValidator(jwtValidator.apply(clientRegistration));
decoder.setClaimSetConverter(OidcIdTokenDecoderFactory.createDefaultClaimTypeConverter()); decoder.setClaimSetConverter(OidcIdTokenDecoderFactory.createDefaultClaimTypeConverter());
return decoder; return decoder;

View File

@ -37,6 +37,7 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer; import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoder;
@ -49,13 +50,14 @@ import org.springframework.security.oauth2.server.resource.introspection.OpaqueT
import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint; import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler; import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter; import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl; import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.access.DelegatingAccessDeniedHandler; import org.springframework.security.web.access.DelegatingAccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.csrf.CsrfException; import org.springframework.security.web.csrf.CsrfException;
import org.springframework.security.web.util.matcher.AndRequestMatcher; import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher; import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
@ -156,7 +158,7 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
private AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver; private AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver;
private BearerTokenResolver bearerTokenResolver; private AuthenticationConverter authenticationConverter;
private JwtConfigurer jwtConfigurer; private JwtConfigurer jwtConfigurer;
@ -196,7 +198,19 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
public OAuth2ResourceServerConfigurer<H> bearerTokenResolver(BearerTokenResolver bearerTokenResolver) { public OAuth2ResourceServerConfigurer<H> bearerTokenResolver(BearerTokenResolver bearerTokenResolver) {
Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null"); Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null");
this.bearerTokenResolver = bearerTokenResolver; this.authenticationConverter = new BearerTokenResolverHoldingAuthenticationConverter(bearerTokenResolver);
return this;
}
/**
* Sets the {@link AuthenticationConverter} to use.
* @param authenticationConverter the authentication converter
* @return the {@link OAuth2ResourceServerConfigurer} for further configuration
* @since 7.0
*/
public OAuth2ResourceServerConfigurer<H> authenticationConverter(AuthenticationConverter authenticationConverter) {
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
this.authenticationConverter = authenticationConverter;
return this; return this;
} }
@ -271,16 +285,15 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
@Override @Override
public void configure(H http) { public void configure(H http) {
BearerTokenResolver bearerTokenResolver = getBearerTokenResolver();
this.requestMatcher.setBearerTokenResolver(bearerTokenResolver);
AuthenticationManagerResolver resolver = this.authenticationManagerResolver; AuthenticationManagerResolver resolver = this.authenticationManagerResolver;
if (resolver == null) { if (resolver == null) {
AuthenticationManager authenticationManager = getAuthenticationManager(http); AuthenticationManager authenticationManager = getAuthenticationManager(http);
resolver = (request) -> authenticationManager; resolver = (request) -> authenticationManager;
} }
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver); AuthenticationConverter converter = getAuthenticationConverter();
filter.setBearerTokenResolver(bearerTokenResolver); this.requestMatcher.setAuthenticationConverter(converter);
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver, converter);
filter.setAuthenticationEntryPoint(this.authenticationEntryPoint); filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
filter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy()); filter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
filter = postProcess(filter); filter = postProcess(filter);
@ -367,16 +380,29 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
return this.authenticationManagerResolver; return this.authenticationManagerResolver;
} }
BearerTokenResolver getBearerTokenResolver() { AuthenticationConverter getAuthenticationConverter() {
if (this.bearerTokenResolver == null) { if (this.authenticationConverter != null) {
if (this.context.getBeanNamesForType(BearerTokenResolver.class).length > 0) { return this.authenticationConverter;
this.bearerTokenResolver = this.context.getBean(BearerTokenResolver.class); }
if (this.context.getBeanNamesForType(AuthenticationConverter.class).length > 0) {
this.authenticationConverter = this.context.getBean(AuthenticationConverter.class);
}
else if (this.context.getBeanNamesForType(BearerTokenResolver.class).length > 0) {
BearerTokenResolver bearerTokenResolver = this.context.getBean(BearerTokenResolver.class);
this.authenticationConverter = new BearerTokenResolverHoldingAuthenticationConverter(bearerTokenResolver);
} }
else { else {
this.bearerTokenResolver = new DefaultBearerTokenResolver(); this.authenticationConverter = new BearerTokenAuthenticationConverter();
} }
return this.authenticationConverter;
} }
return this.bearerTokenResolver;
BearerTokenResolver getBearerTokenResolver() {
AuthenticationConverter authenticationConverter = getAuthenticationConverter();
if (authenticationConverter instanceof OAuth2ResourceServerConfigurer.BearerTokenResolverHoldingAuthenticationConverter bearer) {
return bearer.bearerTokenResolver;
}
return null;
} }
public class JwtConfigurer { public class JwtConfigurer {
@ -564,21 +590,41 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
private static final class BearerTokenRequestMatcher implements RequestMatcher { private static final class BearerTokenRequestMatcher implements RequestMatcher {
private BearerTokenResolver bearerTokenResolver; private AuthenticationConverter authenticationConverter;
@Override @Override
public boolean matches(HttpServletRequest request) { public boolean matches(HttpServletRequest request) {
try { try {
return this.bearerTokenResolver.resolve(request) != null; return this.authenticationConverter.convert(request) != null;
} }
catch (OAuth2AuthenticationException ex) { catch (OAuth2AuthenticationException ex) {
return false; return false;
} }
} }
void setBearerTokenResolver(BearerTokenResolver tokenResolver) { void setAuthenticationConverter(AuthenticationConverter authenticationConverter) {
Assert.notNull(tokenResolver, "resolver cannot be null"); Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
this.bearerTokenResolver = tokenResolver; this.authenticationConverter = authenticationConverter;
}
}
private static final class BearerTokenResolverHoldingAuthenticationConverter implements AuthenticationConverter {
private final BearerTokenResolver bearerTokenResolver;
private final AuthenticationConverter authenticationConverter;
BearerTokenResolverHoldingAuthenticationConverter(BearerTokenResolver bearerTokenResolver) {
this.bearerTokenResolver = bearerTokenResolver;
BearerTokenAuthenticationConverter authenticationConverter = new BearerTokenAuthenticationConverter();
authenticationConverter.setBearerTokenResolver(bearerTokenResolver);
this.authenticationConverter = authenticationConverter;
}
@Override
public Authentication convert(HttpServletRequest request) {
return this.authenticationConverter.convert(request);
} }
} }

View File

@ -34,6 +34,8 @@ import org.springframework.security.config.annotation.web.configurers.LogoutConf
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor;
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml4LogoutRequestValidator; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml4LogoutRequestValidator;
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml4LogoutResponseValidator; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml4LogoutResponseValidator;
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml5LogoutRequestValidator; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml5LogoutRequestValidator;
@ -534,7 +536,13 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
if (authentication == null) { if (authentication == null) {
return false; return false;
} }
return authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal; if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal) {
return true;
}
if (authentication.getCredentials() instanceof Saml2ResponseAssertionAccessor) {
return true;
}
return authentication instanceof Saml2Authentication;
} }
} }

View File

@ -521,11 +521,23 @@ final class AuthenticationConfigBuilder {
filterBuilder.addPropertyValue("authenticationManager", authManager); filterBuilder.addPropertyValue("authenticationManager", authManager);
filterBuilder.addPropertyValue("securityContextHolderStrategy", filterBuilder.addPropertyValue("securityContextHolderStrategy",
authenticationFilterSecurityContextHolderStrategyRef); authenticationFilterSecurityContextHolderStrategyRef);
String regex = x509Elt.getAttribute("subject-principal-regex"); String principalExtractorRef = x509Elt.getAttribute("principal-extractor-ref");
if (StringUtils.hasText(regex)) { String subjectPrincipalRegex = x509Elt.getAttribute("subject-principal-regex");
boolean hasPrincipalExtractorRef = StringUtils.hasText(principalExtractorRef);
boolean hasSubjectPrincipalRegex = StringUtils.hasText(subjectPrincipalRegex);
if (hasPrincipalExtractorRef && hasSubjectPrincipalRegex) {
this.pc.getReaderContext()
.error("The attribute 'principal-extractor-ref' cannot be used together with the 'subject-principal-regex' attribute within <"
+ Elements.X509 + ">", this.pc.extractSource(x509Elt));
}
if (hasPrincipalExtractorRef) {
RuntimeBeanReference principalExtractor = new RuntimeBeanReference(principalExtractorRef);
filterBuilder.addPropertyValue("principalExtractor", principalExtractor);
}
if (hasSubjectPrincipalRegex) {
BeanDefinitionBuilder extractor = BeanDefinitionBuilder BeanDefinitionBuilder extractor = BeanDefinitionBuilder
.rootBeanDefinition(SubjectDnX509PrincipalExtractor.class); .rootBeanDefinition(SubjectDnX509PrincipalExtractor.class);
extractor.addPropertyValue("subjectDnRegex", regex); extractor.addPropertyValue("subjectDnRegex", subjectPrincipalRegex);
filterBuilder.addPropertyValue("principalExtractor", extractor.getBeanDefinition()); filterBuilder.addPropertyValue("principalExtractor", extractor.getBeanDefinition());
} }
injectAuthenticationDetailsSource(x509Elt, filterBuilder); injectAuthenticationDetailsSource(x509Elt, filterBuilder);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -23,6 +23,7 @@ import jakarta.servlet.http.HttpServletRequest;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference; import org.springframework.beans.factory.config.BeanReference;
@ -43,9 +44,10 @@ import org.springframework.security.oauth2.server.resource.authentication.Opaque
import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint; import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler; import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter; import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
import org.springframework.security.web.authentication.AuthenticationConverter;
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.StringUtils; import org.springframework.util.StringUtils;
@ -64,6 +66,8 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
static final String BEARER_TOKEN_RESOLVER_REF = "bearer-token-resolver-ref"; static final String BEARER_TOKEN_RESOLVER_REF = "bearer-token-resolver-ref";
static final String AUTHENTICATION_CONVERTER_REF = "authentication-converter-ref";
static final String ENTRY_POINT_REF = "entry-point-ref"; static final String ENTRY_POINT_REF = "entry-point-ref";
static final String BEARER_TOKEN_RESOLVER = "bearerTokenResolver"; static final String BEARER_TOKEN_RESOLVER = "bearerTokenResolver";
@ -124,11 +128,16 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
pc.getReaderContext().registerWithGeneratedName(opaqueTokenAuthenticationProvider))); pc.getReaderContext().registerWithGeneratedName(opaqueTokenAuthenticationProvider)));
} }
BeanMetadataElement bearerTokenResolver = getBearerTokenResolver(oauth2ResourceServer); BeanMetadataElement bearerTokenResolver = getBearerTokenResolver(oauth2ResourceServer);
BeanDefinitionBuilder requestMatcherBuilder = BeanDefinitionBuilder BeanMetadataElement authenticationConverter = getAuthenticationConverter(oauth2ResourceServer);
.rootBeanDefinition(BearerTokenRequestMatcher.class); if (bearerTokenResolver != null && authenticationConverter != null) {
requestMatcherBuilder.addConstructorArgValue(bearerTokenResolver); throw new BeanDefinitionStoreException(
BeanDefinition requestMatcher = requestMatcherBuilder.getBeanDefinition(); "You cannot use bearer-token-ref and authentication-converter-ref in the same oauth2-resource-server element");
}
if (bearerTokenResolver == null && authenticationConverter == null) {
authenticationConverter = new RootBeanDefinition(BearerTokenAuthenticationConverter.class);
}
BeanMetadataElement authenticationEntryPoint = getEntryPoint(oauth2ResourceServer); BeanMetadataElement authenticationEntryPoint = getEntryPoint(oauth2ResourceServer);
BeanDefinition requestMatcher = buildRequestMatcher(bearerTokenResolver, authenticationConverter);
this.entryPoints.put(requestMatcher, authenticationEntryPoint); this.entryPoints.put(requestMatcher, authenticationEntryPoint);
this.deniedHandlers.put(requestMatcher, this.accessDeniedHandler); this.deniedHandlers.put(requestMatcher, this.accessDeniedHandler);
this.ignoreCsrfRequestMatchers.add(requestMatcher); this.ignoreCsrfRequestMatchers.add(requestMatcher);
@ -136,13 +145,35 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
.rootBeanDefinition(BearerTokenAuthenticationFilter.class); .rootBeanDefinition(BearerTokenAuthenticationFilter.class);
BeanMetadataElement authenticationManagerResolver = getAuthenticationManagerResolver(oauth2ResourceServer); BeanMetadataElement authenticationManagerResolver = getAuthenticationManagerResolver(oauth2ResourceServer);
filterBuilder.addConstructorArgValue(authenticationManagerResolver); filterBuilder.addConstructorArgValue(authenticationManagerResolver);
filterBuilder.addPropertyValue(BEARER_TOKEN_RESOLVER, bearerTokenResolver);
filterBuilder.addPropertyValue(AUTHENTICATION_ENTRY_POINT, authenticationEntryPoint); filterBuilder.addPropertyValue(AUTHENTICATION_ENTRY_POINT, authenticationEntryPoint);
filterBuilder.addPropertyValue("securityContextHolderStrategy", filterBuilder.addPropertyValue("securityContextHolderStrategy",
this.authenticationFilterSecurityContextHolderStrategy); this.authenticationFilterSecurityContextHolderStrategy);
if (authenticationConverter != null) {
filterBuilder.addConstructorArgValue(authenticationConverter);
}
if (bearerTokenResolver != null) {
filterBuilder.addPropertyValue(BEARER_TOKEN_RESOLVER, bearerTokenResolver);
}
return filterBuilder.getBeanDefinition(); return filterBuilder.getBeanDefinition();
} }
private BeanDefinition buildRequestMatcher(BeanMetadataElement bearerTokenResolver,
BeanMetadataElement authenticationConverter) {
if (bearerTokenResolver != null) {
BeanDefinitionBuilder requestMatcherBuilder = BeanDefinitionBuilder
.rootBeanDefinition(BearerTokenRequestMatcher.class);
requestMatcherBuilder.addConstructorArgValue(bearerTokenResolver);
return requestMatcherBuilder.getBeanDefinition();
}
BeanDefinitionBuilder requestMatcherBuilder = BeanDefinitionBuilder
.rootBeanDefinition(BearerTokenAuthenticationRequestMatcher.class);
if (authenticationConverter != null) {
requestMatcherBuilder.addConstructorArgValue(authenticationConverter);
}
return requestMatcherBuilder.getBeanDefinition();
}
void validateConfiguration(Element oauth2ResourceServer, Element jwt, Element opaqueToken, ParserContext pc) { void validateConfiguration(Element oauth2ResourceServer, Element jwt, Element opaqueToken, ParserContext pc) {
if (!oauth2ResourceServer.hasAttribute(AUTHENTICATION_MANAGER_RESOLVER_REF)) { if (!oauth2ResourceServer.hasAttribute(AUTHENTICATION_MANAGER_RESOLVER_REF)) {
if (jwt == null && opaqueToken == null) { if (jwt == null && opaqueToken == null) {
@ -178,11 +209,19 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
BeanMetadataElement getBearerTokenResolver(Element element) { BeanMetadataElement getBearerTokenResolver(Element element) {
String bearerTokenResolverRef = element.getAttribute(BEARER_TOKEN_RESOLVER_REF); String bearerTokenResolverRef = element.getAttribute(BEARER_TOKEN_RESOLVER_REF);
if (!StringUtils.hasLength(bearerTokenResolverRef)) { if (!StringUtils.hasLength(bearerTokenResolverRef)) {
return new RootBeanDefinition(DefaultBearerTokenResolver.class); return null;
} }
return new RuntimeBeanReference(bearerTokenResolverRef); return new RuntimeBeanReference(bearerTokenResolverRef);
} }
BeanMetadataElement getAuthenticationConverter(Element element) {
String authenticationConverterRef = element.getAttribute(AUTHENTICATION_CONVERTER_REF);
if (!StringUtils.hasLength(authenticationConverterRef)) {
return null;
}
return new RuntimeBeanReference(authenticationConverterRef);
}
BeanMetadataElement getEntryPoint(Element element) { BeanMetadataElement getEntryPoint(Element element) {
String entryPointRef = element.getAttribute(ENTRY_POINT_REF); String entryPointRef = element.getAttribute(ENTRY_POINT_REF);
if (!StringUtils.hasLength(entryPointRef)) { if (!StringUtils.hasLength(entryPointRef)) {
@ -366,4 +405,29 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
} }
static final class BearerTokenAuthenticationRequestMatcher implements RequestMatcher {
private final AuthenticationConverter authenticationConverter;
BearerTokenAuthenticationRequestMatcher() {
this.authenticationConverter = new BearerTokenAuthenticationConverter();
}
BearerTokenAuthenticationRequestMatcher(AuthenticationConverter authenticationConverter) {
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
this.authenticationConverter = authenticationConverter;
}
@Override
public boolean matches(HttpServletRequest request) {
try {
return this.authenticationConverter.convert(request) != null;
}
catch (OAuth2AuthenticationException ex) {
return false;
}
}
}
} }

View File

@ -32,6 +32,8 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor;
import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver; import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter;
import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseFilter; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseFilter;
@ -239,7 +241,13 @@ final class Saml2LogoutBeanDefinitionParser implements BeanDefinitionParser {
if (authentication == null) { if (authentication == null) {
return false; return false;
} }
return authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal; if (authentication.getPrincipal() instanceof Saml2AuthenticatedPrincipal) {
return true;
}
if (authentication.getCredentials() instanceof Saml2ResponseAssertionAccessor) {
return true;
}
return authentication instanceof Saml2Authentication;
} }
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {

View File

@ -32,7 +32,6 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.security.config.BeanIds; import org.springframework.security.config.BeanIds;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource; import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.server.ApacheDSContainer;
import org.springframework.security.ldap.server.UnboundIdContainer; import org.springframework.security.ldap.server.UnboundIdContainer;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -47,7 +46,7 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser {
private static final String CONTEXT_SOURCE_CLASS = "org.springframework.security.ldap.DefaultSpringSecurityContextSource"; private static final String CONTEXT_SOURCE_CLASS = "org.springframework.security.ldap.DefaultSpringSecurityContextSource";
/** /**
* Defines the Url of the ldap server to use. If not specified, an embedded apache DS * Defines the Url of the ldap server to use. If not specified, an embedded UnboundID
* instance will be created * instance will be created
*/ */
private static final String ATT_URL = "url"; private static final String ATT_URL = "url";
@ -78,22 +77,15 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser {
private static final int DEFAULT_PORT = 33389; private static final int DEFAULT_PORT = 33389;
private static final String APACHEDS_CLASSNAME = "org.apache.directory.server.core.DefaultDirectoryService";
private static final String UNBOUNID_CLASSNAME = "com.unboundid.ldap.listener.InMemoryDirectoryServer"; private static final String UNBOUNID_CLASSNAME = "com.unboundid.ldap.listener.InMemoryDirectoryServer";
private static final String APACHEDS_CONTAINER_CLASSNAME = "org.springframework.security.ldap.server.ApacheDSContainer";
private static final String UNBOUNDID_CONTAINER_CLASSNAME = "org.springframework.security.ldap.server.UnboundIdContainer"; private static final String UNBOUNDID_CONTAINER_CLASSNAME = "org.springframework.security.ldap.server.UnboundIdContainer";
private static final boolean unboundIdPresent; private static final boolean unboundIdPresent;
private static final boolean apacheDsPresent;
static { static {
ClassLoader classLoader = LdapServerBeanDefinitionParser.class.getClassLoader(); ClassLoader classLoader = LdapServerBeanDefinitionParser.class.getClassLoader();
unboundIdPresent = ClassUtils.isPresent(UNBOUNID_CLASSNAME, classLoader); unboundIdPresent = ClassUtils.isPresent(UNBOUNID_CLASSNAME, classLoader);
apacheDsPresent = ClassUtils.isPresent(APACHEDS_CLASSNAME, classLoader);
} }
@Override @Override
@ -128,10 +120,9 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser {
/** /**
* Will be called if no url attribute is supplied. * Will be called if no url attribute is supplied.
* *
* Registers beans to create an embedded apache directory server. * Registers beans to create an embedded UnboundID Server.
* @return the BeanDefinition for the ContextSource for the embedded server. * @return the BeanDefinition for the ContextSource for the embedded server.
* *
* @see ApacheDSContainer
* @see UnboundIdContainer * @see UnboundIdContainer
*/ */
private RootBeanDefinition createEmbeddedServer(Element element, ParserContext parserContext) { private RootBeanDefinition createEmbeddedServer(Element element, ParserContext parserContext) {
@ -162,8 +153,7 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser {
} }
ldapContainer.getConstructorArgumentValues().addGenericArgumentValue(ldifs); ldapContainer.getConstructorArgumentValues().addGenericArgumentValue(ldifs);
ldapContainer.getPropertyValues().addPropertyValue("port", getPort(element)); ldapContainer.getPropertyValues().addPropertyValue("port", getPort(element));
if (parserContext.getRegistry().containsBeanDefinition(BeanIds.EMBEDDED_APACHE_DS) if (parserContext.getRegistry().containsBeanDefinition(BeanIds.EMBEDDED_UNBOUNDID)) {
|| parserContext.getRegistry().containsBeanDefinition(BeanIds.EMBEDDED_UNBOUNDID)) {
parserContext.getReaderContext() parserContext.getReaderContext()
.error("Only one embedded server bean is allowed per application context", element); .error("Only one embedded server bean is allowed per application context", element);
} }
@ -175,9 +165,6 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser {
} }
private RootBeanDefinition getRootBeanDefinition(String mode) { private RootBeanDefinition getRootBeanDefinition(String mode) {
if (isApacheDsEnabled(mode)) {
return new RootBeanDefinition(APACHEDS_CONTAINER_CLASSNAME, null, null);
}
if (isUnboundidEnabled(mode)) { if (isUnboundidEnabled(mode)) {
return new RootBeanDefinition(UNBOUNDID_CONTAINER_CLASSNAME, null, null); return new RootBeanDefinition(UNBOUNDID_CONTAINER_CLASSNAME, null, null);
} }
@ -185,19 +172,12 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser {
} }
private String resolveBeanId(String mode) { private String resolveBeanId(String mode) {
if (isApacheDsEnabled(mode)) {
return BeanIds.EMBEDDED_APACHE_DS;
}
if (isUnboundidEnabled(mode)) { if (isUnboundidEnabled(mode)) {
return BeanIds.EMBEDDED_UNBOUNDID; return BeanIds.EMBEDDED_UNBOUNDID;
} }
return null; return null;
} }
private boolean isApacheDsEnabled(String mode) {
return "apacheds".equals(mode) || apacheDsPresent;
}
private boolean isUnboundidEnabled(String mode) { private boolean isUnboundidEnabled(String mode) {
return "unboundid".equals(mode) || unboundIdPresent; return "unboundid".equals(mode) || unboundIdPresent;
} }
@ -233,10 +213,6 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser {
} }
private int getPort() { private int getPort() {
if (apacheDsPresent) {
ApacheDSContainer apacheDSContainer = this.applicationContext.getBean(ApacheDSContainer.class);
return apacheDSContainer.getLocalPort();
}
if (unboundIdPresent) { if (unboundIdPresent) {
UnboundIdContainer unboundIdContainer = this.applicationContext.getBean(UnboundIdContainer.class); UnboundIdContainer unboundIdContainer = this.applicationContext.getBean(UnboundIdContainer.class);
return unboundIdContainer.getPort(); return unboundIdContainer.getPort();

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,6 +16,7 @@
package org.springframework.security.config.oauth2.client; package org.springframework.security.config.oauth2.client;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistration.Builder; import org.springframework.security.oauth2.client.registration.ClientRegistration.Builder;
@ -27,7 +28,7 @@ import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
* Common OAuth2 Providers that can be used to create * Common OAuth2 Providers that can be used to create
* {@link org.springframework.security.oauth2.client.registration.ClientRegistration.Builder * {@link org.springframework.security.oauth2.client.registration.ClientRegistration.Builder
* builders} pre-configured with sensible defaults for the * builders} pre-configured with sensible defaults for the
* {@link HttpSecurity#oauth2Login()} flow. * {@link HttpSecurity#oauth2Login(Customizer)} flow.
* *
* @author Phillip Webb * @author Phillip Webb
* @since 5.0 * @since 5.0
@ -87,6 +88,23 @@ public enum CommonOAuth2Provider {
}, },
X {
@Override
public Builder getBuilder(String registrationId) {
ClientRegistration.Builder builder = getBuilder(registrationId,
ClientAuthenticationMethod.CLIENT_SECRET_POST, DEFAULT_REDIRECT_URL);
builder.scope("users.read", "tweet.read");
builder.authorizationUri("https://x.com/i/oauth2/authorize");
builder.tokenUri("https://api.x.com/2/oauth2/token");
builder.userInfoUri("https://api.x.com/2/users/me");
builder.userNameAttributeName("username");
builder.clientName("X");
return builder;
}
},
OKTA { OKTA {
@Override @Override

View File

@ -18,10 +18,6 @@ package org.springframework.security.config.web.server;
import java.util.function.Function; import java.util.function.Function;
import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier;
import com.nimbusds.jose.proc.JOSEObjectTypeVerifier;
import com.nimbusds.jose.proc.JWKSecurityContext;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
@ -41,6 +37,7 @@ import org.springframework.security.oauth2.jwt.BadJwtException;
import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtDecoderFactory; import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
import org.springframework.security.oauth2.jwt.JwtTypeValidator;
import org.springframework.security.oauth2.jwt.JwtValidators; import org.springframework.security.oauth2.jwt.JwtValidators;
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
@ -72,8 +69,10 @@ final class OidcBackChannelLogoutReactiveAuthenticationManager implements Reacti
* Construct an {@link OidcBackChannelLogoutReactiveAuthenticationManager} * Construct an {@link OidcBackChannelLogoutReactiveAuthenticationManager}
*/ */
OidcBackChannelLogoutReactiveAuthenticationManager() { OidcBackChannelLogoutReactiveAuthenticationManager() {
JwtTypeValidator type = new JwtTypeValidator("JWT", "logout+jwt");
type.setAllowEmpty(true);
Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidator = (clientRegistration) -> JwtValidators Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidator = (clientRegistration) -> JwtValidators
.createDefaultWithValidators(new OidcBackChannelLogoutTokenValidator(clientRegistration)); .createDefaultWithValidators(type, new OidcBackChannelLogoutTokenValidator(clientRegistration));
this.logoutTokenDecoderFactory = (clientRegistration) -> { this.logoutTokenDecoderFactory = (clientRegistration) -> {
String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri(); String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri();
if (!StringUtils.hasText(jwkSetUri)) { if (!StringUtils.hasText(jwkSetUri)) {
@ -84,11 +83,7 @@ final class OidcBackChannelLogoutReactiveAuthenticationManager implements Reacti
null); null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
} }
JOSEObjectTypeVerifier<JWKSecurityContext> typeVerifier = new DefaultJOSEObjectTypeVerifier<>(null, NimbusReactiveJwtDecoder decoder = NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).build();
JOSEObjectType.JWT, new JOSEObjectType("logout+jwt"));
NimbusReactiveJwtDecoder decoder = NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri)
.jwtProcessorCustomizer((processor) -> processor.setJWSTypeVerifier(typeVerifier))
.build();
decoder.setJwtValidator(jwtValidator.apply(clientRegistration)); decoder.setJwtValidator(jwtValidator.apply(clientRegistration));
decoder.setClaimSetConverter( decoder.setClaimSetConverter(
new ClaimTypeConverter(OidcIdTokenDecoderFactory.createDefaultClaimTypeConverters())); new ClaimTypeConverter(OidcIdTokenDecoderFactory.createDefaultClaimTypeConverters()));

View File

@ -119,7 +119,7 @@ import org.springframework.security.oauth2.server.resource.web.server.BearerToke
import org.springframework.security.oauth2.server.resource.web.server.authentication.ServerBearerTokenAuthenticationConverter; import org.springframework.security.oauth2.server.resource.web.server.authentication.ServerBearerTokenAuthenticationConverter;
import org.springframework.security.web.PortMapper; import org.springframework.security.web.PortMapper;
import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor; import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor;
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
import org.springframework.security.web.server.DefaultServerRedirectStrategy; import org.springframework.security.web.server.DefaultServerRedirectStrategy;
import org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint; import org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint;
@ -298,6 +298,7 @@ import org.springframework.web.util.pattern.PathPatternParser;
* @author Parikshit Dutta * @author Parikshit Dutta
* @author Ankur Pathak * @author Ankur Pathak
* @author Alexey Nesterov * @author Alexey Nesterov
* @author Yanming Zhou
* @since 5.0 * @since 5.0
*/ */
public class ServerHttpSecurity { public class ServerHttpSecurity {
@ -943,8 +944,8 @@ public class ServerHttpSecurity {
* } * }
* </pre> * </pre>
* *
* Note that if extractor is not specified, {@link SubjectDnX509PrincipalExtractor} * Note that if extractor is not specified, {@link SubjectX500PrincipalExtractor} will
* will be used. If authenticationManager is not specified, * be used. If authenticationManager is not specified,
* {@link ReactivePreAuthenticatedAuthenticationManager} will be used. * {@link ReactivePreAuthenticatedAuthenticationManager} will be used.
* @return the {@link X509Spec} to customize * @return the {@link X509Spec} to customize
* @since 5.2 * @since 5.2
@ -978,8 +979,8 @@ public class ServerHttpSecurity {
* } * }
* </pre> * </pre>
* *
* Note that if extractor is not specified, {@link SubjectDnX509PrincipalExtractor} * Note that if extractor is not specified, {@link SubjectX500PrincipalExtractor} will
* will be used. If authenticationManager is not specified, * be used. If authenticationManager is not specified,
* {@link ReactivePreAuthenticatedAuthenticationManager} will be used. * {@link ReactivePreAuthenticatedAuthenticationManager} will be used.
* @param x509Customizer the {@link Customizer} to provide more options for the * @param x509Customizer the {@link Customizer} to provide more options for the
* {@link X509Spec} * {@link X509Spec}
@ -4180,7 +4181,7 @@ public class ServerHttpSecurity {
if (this.principalExtractor != null) { if (this.principalExtractor != null) {
return this.principalExtractor; return this.principalExtractor;
} }
return new SubjectDnX509PrincipalExtractor(); return new SubjectX500PrincipalExtractor();
} }
private ReactiveAuthenticationManager getAuthenticationManager() { private ReactiveAuthenticationManager getAuthenticationManager() {
@ -5443,8 +5444,11 @@ public class ServerHttpSecurity {
public OpaqueTokenSpec introspectionUri(String introspectionUri) { public OpaqueTokenSpec introspectionUri(String introspectionUri) {
Assert.hasText(introspectionUri, "introspectionUri cannot be empty"); Assert.hasText(introspectionUri, "introspectionUri cannot be empty");
this.introspectionUri = introspectionUri; this.introspectionUri = introspectionUri;
this.introspector = () -> new SpringReactiveOpaqueTokenIntrospector(this.introspectionUri, this.introspector = () -> SpringReactiveOpaqueTokenIntrospector
this.clientId, this.clientSecret); .withIntrospectionUri(this.introspectionUri)
.clientId(this.clientId)
.clientSecret(this.clientSecret)
.build();
return this; return this;
} }
@ -5459,8 +5463,11 @@ public class ServerHttpSecurity {
Assert.notNull(clientSecret, "clientSecret cannot be null"); Assert.notNull(clientSecret, "clientSecret cannot be null");
this.clientId = clientId; this.clientId = clientId;
this.clientSecret = clientSecret; this.clientSecret = clientSecret;
this.introspector = () -> new SpringReactiveOpaqueTokenIntrospector(this.introspectionUri, this.introspector = () -> SpringReactiveOpaqueTokenIntrospector
this.clientId, this.clientSecret); .withIntrospectionUri(this.introspectionUri)
.clientId(this.clientId)
.clientSecret(this.clientSecret)
.build();
return this; return this;
} }

View File

@ -16,9 +16,13 @@
package org.springframework.security.config.websocket; package org.springframework.security.config.websocket;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.w3c.dom.Element; import org.w3c.dom.Element;
@ -307,6 +311,11 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
private static final String TEMPLATE_EXPRESSION_BEAN_ID = "annotationExpressionTemplateDefaults"; private static final String TEMPLATE_EXPRESSION_BEAN_ID = "annotationExpressionTemplateDefaults";
private static final Set<String> CSRF_HANDSHAKE_HANDLER_CLASSES = Collections.unmodifiableSet(
new HashSet<>(Arrays.asList("org.springframework.web.socket.server.support.WebSocketHttpRequestHandler",
"org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsService",
"org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService")));
private final String inboundSecurityInterceptorId; private final String inboundSecurityInterceptorId;
private final boolean sameOriginDisabled; private final boolean sameOriginDisabled;
@ -345,16 +354,7 @@ public final class WebSocketMessageBrokerSecurityBeanDefinitionParser implements
} }
} }
} }
else if ("org.springframework.web.socket.server.support.WebSocketHttpRequestHandler" else if (CSRF_HANDSHAKE_HANDLER_CLASSES.contains(beanClassName)) {
.equals(beanClassName)) {
addCsrfTokenHandshakeInterceptor(bd);
}
else if ("org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsService"
.equals(beanClassName)) {
addCsrfTokenHandshakeInterceptor(bd);
}
else if ("org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService"
.equals(beanClassName)) {
addCsrfTokenHandshakeInterceptor(bd); addCsrfTokenHandshakeInterceptor(bd);
} }
} }

View File

@ -51,6 +51,7 @@ class X509Dsl {
var authenticationDetailsSource: AuthenticationDetailsSource<HttpServletRequest, PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails>? = null var authenticationDetailsSource: AuthenticationDetailsSource<HttpServletRequest, PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails>? = null
var userDetailsService: UserDetailsService? = null var userDetailsService: UserDetailsService? = null
var authenticationUserDetailsService: AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken>? = null var authenticationUserDetailsService: AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken>? = null
@Deprecated("Use x509PrincipalExtractor instead")
var subjectPrincipalRegex: String? = null var subjectPrincipalRegex: String? = null

View File

@ -14,7 +14,8 @@
# limitations under the License. # limitations under the License.
# #
http\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-6.5.xsd http\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-7.0.xsd
http\://www.springframework.org/schema/security/spring-security-7.0.xsd=org/springframework/security/config/spring-security-7.0.xsd
http\://www.springframework.org/schema/security/spring-security-6.5.xsd=org/springframework/security/config/spring-security-6.5.xsd http\://www.springframework.org/schema/security/spring-security-6.5.xsd=org/springframework/security/config/spring-security-6.5.xsd
http\://www.springframework.org/schema/security/spring-security-6.4.xsd=org/springframework/security/config/spring-security-6.4.xsd http\://www.springframework.org/schema/security/spring-security-6.4.xsd=org/springframework/security/config/spring-security-6.4.xsd
http\://www.springframework.org/schema/security/spring-security-6.3.xsd=org/springframework/security/config/spring-security-6.3.xsd http\://www.springframework.org/schema/security/spring-security-6.3.xsd=org/springframework/security/config/spring-security-6.3.xsd
@ -41,7 +42,8 @@ http\://www.springframework.org/schema/security/spring-security-2.0.xsd=org/spri
http\://www.springframework.org/schema/security/spring-security-2.0.1.xsd=org/springframework/security/config/spring-security-2.0.1.xsd http\://www.springframework.org/schema/security/spring-security-2.0.1.xsd=org/springframework/security/config/spring-security-2.0.1.xsd
http\://www.springframework.org/schema/security/spring-security-2.0.2.xsd=org/springframework/security/config/spring-security-2.0.2.xsd http\://www.springframework.org/schema/security/spring-security-2.0.2.xsd=org/springframework/security/config/spring-security-2.0.2.xsd
http\://www.springframework.org/schema/security/spring-security-2.0.4.xsd=org/springframework/security/config/spring-security-2.0.4.xsd http\://www.springframework.org/schema/security/spring-security-2.0.4.xsd=org/springframework/security/config/spring-security-2.0.4.xsd
https\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-6.5.xsd https\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-7.0.xsd
https\://www.springframework.org/schema/security/spring-security-7.0.xsd=org/springframework/security/config/spring-security-7.0.xsd
https\://www.springframework.org/schema/security/spring-security-6.5.xsd=org/springframework/security/config/spring-security-6.5.xsd https\://www.springframework.org/schema/security/spring-security-6.5.xsd=org/springframework/security/config/spring-security-6.5.xsd
https\://www.springframework.org/schema/security/spring-security-6.4.xsd=org/springframework/security/config/spring-security-6.4.xsd https\://www.springframework.org/schema/security/spring-security-6.4.xsd=org/springframework/security/config/spring-security-6.4.xsd
https\://www.springframework.org/schema/security/spring-security-6.3.xsd=org/springframework/security/config/spring-security-6.3.xsd https\://www.springframework.org/schema/security/spring-security-6.3.xsd=org/springframework/security/config/spring-security-6.3.xsd

View File

@ -170,11 +170,14 @@ import org.springframework.security.saml2.core.Saml2Error;
import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.security.saml2.core.Saml2X509Credential;
import org.springframework.security.saml2.credentials.TestSaml2X509Credentials; import org.springframework.security.saml2.credentials.TestSaml2X509Credentials;
import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal;
import org.springframework.security.saml2.provider.service.authentication.Saml2AssertionAuthentication;
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken;
import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest; import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest;
import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest; import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest;
import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertion;
import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor;
import org.springframework.security.saml2.provider.service.authentication.TestSaml2AuthenticationTokens; import org.springframework.security.saml2.provider.service.authentication.TestSaml2AuthenticationTokens;
import org.springframework.security.saml2.provider.service.authentication.TestSaml2Authentications; import org.springframework.security.saml2.provider.service.authentication.TestSaml2Authentications;
import org.springframework.security.saml2.provider.service.authentication.TestSaml2LogoutRequests; import org.springframework.security.saml2.provider.service.authentication.TestSaml2LogoutRequests;
@ -520,8 +523,16 @@ final class SerializationSamples {
generatorByClassName.put(Saml2Exception.class, (r) -> new Saml2Exception("message", new IOException("fail"))); generatorByClassName.put(Saml2Exception.class, (r) -> new Saml2Exception("message", new IOException("fail")));
generatorByClassName.put(DefaultSaml2AuthenticatedPrincipal.class, generatorByClassName.put(DefaultSaml2AuthenticatedPrincipal.class,
(r) -> TestSaml2Authentications.authentication().getPrincipal()); (r) -> TestSaml2Authentications.authentication().getPrincipal());
generatorByClassName.put(Saml2Authentication.class, Saml2Authentication saml2 = TestSaml2Authentications.authentication();
(r) -> applyDetails(TestSaml2Authentications.authentication())); generatorByClassName.put(Saml2Authentication.class, (r) -> applyDetails(saml2));
Saml2ResponseAssertionAccessor assertion = Saml2ResponseAssertion.withResponseValue("response")
.nameId("name")
.sessionIndexes(List.of("id"))
.attributes(Map.of("key", List.of("value")))
.build();
generatorByClassName.put(Saml2ResponseAssertion.class, (r) -> assertion);
generatorByClassName.put(Saml2AssertionAuthentication.class, (r) -> applyDetails(
new Saml2AssertionAuthentication(assertion, authentication.getAuthorities(), "id")));
generatorByClassName.put(Saml2PostAuthenticationRequest.class, generatorByClassName.put(Saml2PostAuthenticationRequest.class,
(r) -> TestSaml2PostAuthenticationRequests.create()); (r) -> TestSaml2PostAuthenticationRequests.create());
generatorByClassName.put(Saml2RedirectAuthenticationRequest.class, generatorByClassName.put(Saml2RedirectAuthenticationRequest.class,

View File

@ -260,6 +260,12 @@ class SpringSecurityCoreVersionSerializableTests {
String version = System.getProperty("springSecurityVersion"); String version = System.getProperty("springSecurityVersion");
String[] parts = version.split("\\."); String[] parts = version.split("\\.");
parts[1] = String.valueOf(Integer.parseInt(parts[1]) - 1); parts[1] = String.valueOf(Integer.parseInt(parts[1]) - 1);
// FIXME: the 7 should not be hardcoded
if ("7".equals(parts[0]) && "-1".equals(parts[1])) {
// if it is version 7.0.x, the previous version is 6.5.x
parts[0] = String.valueOf(Integer.parseInt(parts[0]) - 1);
parts[1] = "5"; // FIXME: this should not be hard coded
}
parts[2] = "x"; parts[2] = "x";
return String.join(".", parts); return String.join(".", parts);
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -58,7 +58,7 @@ public class AuthorizationProxyConfigurationTests {
@Test @Test
public void proxyWhenNotPreAuthorizedThenDenies() { public void proxyWhenNotPreAuthorizedThenDenies() {
this.spring.register(DefaultsConfig.class).autowire(); this.spring.register(DefaultsConfig.class).autowire();
Toaster toaster = (Toaster) this.proxyFactory.proxy(new Toaster()); Toaster toaster = this.proxyFactory.proxy(new Toaster());
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(toaster::makeToast) assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(toaster::makeToast)
.withMessage("Access Denied"); .withMessage("Access Denied");
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(toaster::extractBread) assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(toaster::extractBread)
@ -69,7 +69,7 @@ public class AuthorizationProxyConfigurationTests {
@Test @Test
public void proxyWhenPreAuthorizedThenAllows() { public void proxyWhenPreAuthorizedThenAllows() {
this.spring.register(DefaultsConfig.class).autowire(); this.spring.register(DefaultsConfig.class).autowire();
Toaster toaster = (Toaster) this.proxyFactory.proxy(new Toaster()); Toaster toaster = this.proxyFactory.proxy(new Toaster());
toaster.makeToast(); toaster.makeToast();
assertThat(toaster.extractBread()).isEqualTo("yummy"); assertThat(toaster.extractBread()).isEqualTo("yummy");
} }
@ -77,7 +77,7 @@ public class AuthorizationProxyConfigurationTests {
@Test @Test
public void proxyReactiveWhenNotPreAuthorizedThenDenies() { public void proxyReactiveWhenNotPreAuthorizedThenDenies() {
this.spring.register(ReactiveDefaultsConfig.class).autowire(); this.spring.register(ReactiveDefaultsConfig.class).autowire();
Toaster toaster = (Toaster) this.proxyFactory.proxy(new Toaster()); Toaster toaster = this.proxyFactory.proxy(new Toaster());
Authentication user = TestAuthentication.authenticatedUser(); Authentication user = TestAuthentication.authenticatedUser();
StepVerifier StepVerifier
.create(toaster.reactiveMakeToast().contextWrite(ReactiveSecurityContextHolder.withAuthentication(user))) .create(toaster.reactiveMakeToast().contextWrite(ReactiveSecurityContextHolder.withAuthentication(user)))
@ -90,7 +90,7 @@ public class AuthorizationProxyConfigurationTests {
@Test @Test
public void proxyReactiveWhenPreAuthorizedThenAllows() { public void proxyReactiveWhenPreAuthorizedThenAllows() {
this.spring.register(ReactiveDefaultsConfig.class).autowire(); this.spring.register(ReactiveDefaultsConfig.class).autowire();
Toaster toaster = (Toaster) this.proxyFactory.proxy(new Toaster()); Toaster toaster = this.proxyFactory.proxy(new Toaster());
Authentication admin = TestAuthentication.authenticatedAdmin(); Authentication admin = TestAuthentication.authenticatedAdmin();
StepVerifier StepVerifier
.create(toaster.reactiveMakeToast().contextWrite(ReactiveSecurityContextHolder.withAuthentication(admin))) .create(toaster.reactiveMakeToast().contextWrite(ReactiveSecurityContextHolder.withAuthentication(admin)))

View File

@ -33,15 +33,16 @@ import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.ObservationTextPublisher; import io.micrometer.observation.ObservationTextPublisher;
import jakarta.annotation.security.DenyAll; import jakarta.annotation.security.DenyAll;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation; import org.aopalliance.intercept.MethodInvocation;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mockito;
import org.springframework.aop.Advisor; import org.springframework.aop.Advisor;
import org.springframework.aop.Pointcut;
import org.springframework.aop.config.AopConfigUtils; import org.springframework.aop.config.AopConfigUtils;
import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.JdkRegexpMethodPointcut; import org.springframework.aop.support.JdkRegexpMethodPointcut;
@ -61,9 +62,19 @@ import org.springframework.context.annotation.Role;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.AnnotationConfigurationException; import org.springframework.core.annotation.AnnotationConfigurationException;
import org.springframework.core.annotation.Order; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoPage;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.GeoResults;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode; import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.annotation.BusinessService; import org.springframework.security.access.annotation.BusinessService;
@ -95,6 +106,7 @@ import org.springframework.security.authorization.method.MethodAuthorizationDeni
import org.springframework.security.authorization.method.MethodInvocationResult; import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.PrePostTemplateDefaults; import org.springframework.security.authorization.method.PrePostTemplateDefaults;
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.core.GrantedAuthorityDefaults; import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.observation.SecurityObservationSettings; import org.springframework.security.config.observation.SecurityObservationSettings;
import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContext;
@ -106,13 +118,24 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.test.context.support.WithAnonymousUser; import org.springframework.security.test.context.support.WithAnonymousUser;
import org.springframework.security.test.context.support.WithMockUser; import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener; import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener;
import org.springframework.security.web.util.ThrowableAnalyzer;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@ -127,6 +150,9 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoInteractions;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/** /**
* Tests for {@link PrePostMethodSecurityConfiguration}. * Tests for {@link PrePostMethodSecurityConfiguration}.
@ -148,6 +174,9 @@ public class PrePostMethodSecurityConfigurationTests {
@Autowired(required = false) @Autowired(required = false)
BusinessService businessService; BusinessService businessService;
@Autowired(required = false)
MockMvc mvc;
@WithMockUser @WithMockUser
@Test @Test
public void customMethodSecurityPreAuthorizeAdminWhenRoleUserThenAccessDeniedException() { public void customMethodSecurityPreAuthorizeAdminWhenRoleUserThenAccessDeniedException() {
@ -733,6 +762,28 @@ public class PrePostMethodSecurityConfigurationTests {
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude); assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude);
} }
@Test
@WithMockUser(authorities = "airplane:read")
public void findGeoResultByIdWhenAuthorizedResultThenAuthorizes() {
this.spring.register(AuthorizeResultConfig.class).autowire();
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
GeoResult<Flight> geoResultFlight = flights.findGeoResultFlightById("1");
Flight flight = geoResultFlight.getContent();
assertThatNoException().isThrownBy(flight::getAltitude);
assertThatNoException().isThrownBy(flight::getSeats);
}
@Test
@WithMockUser(authorities = "seating:read")
public void findGeoResultByIdWhenUnauthorizedResultThenDenies() {
this.spring.register(AuthorizeResultConfig.class).autowire();
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
GeoResult<Flight> geoResultFlight = flights.findGeoResultFlightById("1");
Flight flight = geoResultFlight.getContent();
assertThatNoException().isThrownBy(flight::getSeats);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(flight::getAltitude);
}
@Test @Test
@WithMockUser(authorities = "airplane:read") @WithMockUser(authorities = "airplane:read")
public void findByIdWhenAuthorizedResponseEntityThenAuthorizes() { public void findByIdWhenAuthorizedResponseEntityThenAuthorizes() {
@ -804,6 +855,46 @@ public class PrePostMethodSecurityConfigurationTests {
.doesNotContain("Kevin Mitnick")); .doesNotContain("Kevin Mitnick"));
} }
@Test
@WithMockUser(authorities = "airplane:read")
public void findPageWhenPostFilterThenFilters() {
this.spring.register(AuthorizeResultConfig.class).autowire();
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
flights.findPage()
.forEach((flight) -> assertThat(flight.getPassengers()).extracting(Passenger::getName)
.doesNotContain("Kevin Mitnick"));
}
@Test
@WithMockUser(authorities = "airplane:read")
public void findSliceWhenPostFilterThenFilters() {
this.spring.register(AuthorizeResultConfig.class).autowire();
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
flights.findSlice()
.forEach((flight) -> assertThat(flight.getPassengers()).extracting(Passenger::getName)
.doesNotContain("Kevin Mitnick"));
}
@Test
@WithMockUser(authorities = "airplane:read")
public void findGeoPageWhenPostFilterThenFilters() {
this.spring.register(AuthorizeResultConfig.class).autowire();
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
flights.findGeoPage()
.forEach((flight) -> assertThat(flight.getContent().getPassengers()).extracting(Passenger::getName)
.doesNotContain("Kevin Mitnick"));
}
@Test
@WithMockUser(authorities = "airplane:read")
public void findGeoResultsWhenPostFilterThenFilters() {
this.spring.register(AuthorizeResultConfig.class).autowire();
FlightRepository flights = this.spring.getContext().getBean(FlightRepository.class);
flights.findGeoResults()
.forEach((flight) -> assertThat(flight.getContent().getPassengers()).extracting(Passenger::getName)
.doesNotContain("Kevin Mitnick"));
}
@Test @Test
@WithMockUser(authorities = "airplane:read") @WithMockUser(authorities = "airplane:read")
public void findAllWhenPreFilterThenFilters() { public void findAllWhenPreFilterThenFilters() {
@ -1181,6 +1272,97 @@ public class PrePostMethodSecurityConfigurationTests {
} }
} }
@Test
void getWhenPostAuthorizeAuthenticationNameMatchesThenRespondsWithOk() throws Exception {
this.spring.register(WebMvcMethodSecurityConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithUser = get("/authorized-person")
.param("name", "rob")
.with(user("rob"));
// @formatter:on
this.mvc.perform(requestWithUser).andExpect(status().isOk());
}
@Test
void getWhenPostAuthorizeAuthenticationNameNotMatchThenRespondsWithForbidden() throws Exception {
this.spring.register(WebMvcMethodSecurityConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithUser = get("/authorized-person")
.param("name", "john")
.with(user("rob"));
// @formatter:on
this.mvc.perform(requestWithUser).andExpect(status().isForbidden());
}
@Test
void getWhenPostAuthorizeWithinServiceAuthenticationNameMatchesThenRespondsWithOk() throws Exception {
this.spring.register(WebMvcMethodSecurityConfig.class, BasicController.class, BasicService.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithUser = get("/greetings/authorized-person")
.param("name", "rob")
.with(user("rob"));
// @formatter:on
MvcResult mvcResult = this.mvc.perform(requestWithUser).andExpect(status().isOk()).andReturn();
assertThat(mvcResult.getResponse().getContentAsString()).isEqualTo("Hello: rob");
}
@Test
void getWhenPostAuthorizeWithinServiceAuthenticationNameNotMatchThenCustomHandlerRespondsWithForbidden()
throws Exception {
this.spring
.register(WebMvcMethodSecurityConfig.class, BasicController.class, BasicService.class,
BasicControllerAdvice.class)
.autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithUser = get("/greetings/authorized-person")
.param("name", "john")
.with(user("rob"));
// @formatter:on
MvcResult mvcResult = this.mvc.perform(requestWithUser).andExpect(status().isForbidden()).andReturn();
assertThat(mvcResult.getResponse().getContentAsString()).isEqualTo("""
{"message":"Access Denied"}\
""");
}
@Test
void getWhenPostAuthorizeAuthenticationNameNotMatchThenCustomHandlerRespondsWithForbidden() throws Exception {
this.spring
.register(WebMvcMethodSecurityConfig.class, BasicController.class, BasicService.class,
BasicControllerAdvice.class)
.autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithUser = get("/authorized-person")
.param("name", "john")
.with(user("rob"));
// @formatter:on
MvcResult mvcResult = this.mvc.perform(requestWithUser).andExpect(status().isForbidden()).andReturn();
assertThat(mvcResult.getResponse().getContentAsString()).isEqualTo("""
{"message":"Could not write JSON: Access Denied"}\
""");
}
@Test
void getWhenCustomAdvisorAuthenticationNameMatchesThenRespondsWithOk() throws Exception {
this.spring.register(WebMvcMethodSecurityCustomAdvisorConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithUser = get("/authorized-person")
.param("name", "rob")
.with(user("rob"));
// @formatter:on
this.mvc.perform(requestWithUser).andExpect(status().isOk());
}
@Test
void getWhenCustomAdvisorAuthenticationNameNotMatchThenRespondsWithForbidden() throws Exception {
this.spring.register(WebMvcMethodSecurityCustomAdvisorConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithUser = get("/authorized-person")
.param("name", "john")
.with(user("rob"));
// @formatter:on
this.mvc.perform(requestWithUser).andExpect(status().isForbidden());
}
private static Consumer<ConfigurableWebApplicationContext> disallowBeanOverriding() { private static Consumer<ConfigurableWebApplicationContext> disallowBeanOverriding() {
return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false); return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false);
} }
@ -1648,14 +1830,6 @@ public class PrePostMethodSecurityConfigurationTests {
@Bean @Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE) @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Order(1)
static TargetVisitor mock() {
return Mockito.mock(TargetVisitor.class);
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Order(0)
static TargetVisitor skipValueTypes() { static TargetVisitor skipValueTypes() {
return TargetVisitor.defaultsSkipValueTypes(); return TargetVisitor.defaultsSkipValueTypes();
} }
@ -1688,10 +1862,39 @@ public class PrePostMethodSecurityConfigurationTests {
return this.flights.values().iterator(); return this.flights.values().iterator();
} }
Page<Flight> findPage() {
return new PageImpl<>(new ArrayList<>(this.flights.values()));
}
Slice<Flight> findSlice() {
return new SliceImpl<>(new ArrayList<>(this.flights.values()));
}
GeoPage<Flight> findGeoPage() {
List<GeoResult<Flight>> results = new ArrayList<>();
for (Flight flight : this.flights.values()) {
results.add(new GeoResult<>(flight, new Distance(flight.altitude)));
}
return new GeoPage<>(new GeoResults<>(results));
}
GeoResults<Flight> findGeoResults() {
List<GeoResult<Flight>> results = new ArrayList<>();
for (Flight flight : this.flights.values()) {
results.add(new GeoResult<>(flight, new Distance(flight.altitude)));
}
return new GeoResults<>(results);
}
Flight findById(String id) { Flight findById(String id) {
return this.flights.get(id); return this.flights.get(id);
} }
GeoResult<Flight> findGeoResultFlightById(String id) {
Flight flight = this.flights.get(id);
return new GeoResult<>(flight, new Distance(flight.altitude));
}
Flight save(Flight flight) { Flight save(Flight flight) {
this.flights.put(flight.getId(), flight); this.flights.put(flight.getId(), flight);
return flight; return flight;
@ -1919,4 +2122,118 @@ public class PrePostMethodSecurityConfigurationTests {
} }
@EnableWebMvc
@EnableWebSecurity
@EnableMethodSecurity
static class WebMvcMethodSecurityConfig {
}
@EnableWebMvc
@EnableWebSecurity
@EnableMethodSecurity
static class WebMvcMethodSecurityCustomAdvisorConfig {
@Bean
AuthorizationAdvisor customAdvisor(SecurityContextHolderStrategy strategy) {
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
pointcut.setPattern(".*AuthorizedPerson.*getName");
return new AuthorizationAdvisor() {
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
Authentication auth = strategy.getContext().getAuthentication();
Object result = mi.proceed();
if (auth.getName().equals(result)) {
return result;
}
throw new AccessDeniedException("Access Denied for User '" + auth.getName() + "'");
}
@Override
public Pointcut getPointcut() {
return pointcut;
}
@Override
public Advice getAdvice() {
return this;
}
@Override
public int getOrder() {
return AuthorizationInterceptorsOrder.POST_FILTER.getOrder() + 1;
}
};
}
}
@RestController
static class BasicController {
@Autowired(required = false)
BasicService service;
@GetMapping("/greetings/authorized-person")
String getAuthorizedPersonGreeting(@RequestParam String name) {
AuthorizedPerson authorizedPerson = this.service.getAuthorizedPerson(name);
return "Hello: " + authorizedPerson.getName();
}
@AuthorizeReturnObject
@GetMapping(value = "/authorized-person", produces = MediaType.APPLICATION_JSON_VALUE)
AuthorizedPerson getAuthorizedPerson(@RequestParam String name) {
return new AuthorizedPerson(name);
}
}
@ControllerAdvice
static class BasicControllerAdvice {
@ExceptionHandler(AccessDeniedException.class)
ResponseEntity<Map<String, String>> handleAccessDenied(AccessDeniedException ex) {
Map<String, String> responseBody = Map.of("message", ex.getMessage());
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(responseBody);
}
@ExceptionHandler(HttpMessageNotWritableException.class)
ResponseEntity<Map<String, String>> handleHttpMessageNotWritable(HttpMessageNotWritableException ex) {
ThrowableAnalyzer throwableAnalyzer = new ThrowableAnalyzer();
Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
Throwable t = throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
if (t != null) {
Map<String, String> responseBody = Map.of("message", ex.getMessage());
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(responseBody);
}
throw ex;
}
}
@Service
static class BasicService {
@AuthorizeReturnObject
AuthorizedPerson getAuthorizedPerson(String name) {
return new AuthorizedPerson(name);
}
}
public static class AuthorizedPerson {
final String name;
AuthorizedPerson(String name) {
this.name = name;
}
@PostAuthorize("returnObject == authentication.name")
public String getName() {
return this.name;
}
}
} }

View File

@ -47,8 +47,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector; import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -84,26 +82,6 @@ public class WebSecurityTests {
} }
} }
@Test
public void ignoringMvcMatcher() throws Exception {
loadConfig(MvcMatcherConfig.class, LegacyMvcMatchingConfig.class);
this.request.setRequestURI("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
setup();
this.request.setRequestURI("/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
setup();
this.request.setRequestURI("/path/");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
setup();
this.request.setRequestURI("/other");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
@Test @Test
public void requestRejectedHandlerInvoked() throws ServletException, IOException { public void requestRejectedHandlerInvoked() throws ServletException, IOException {
loadConfig(DefaultConfig.class); loadConfig(DefaultConfig.class);
@ -132,30 +110,6 @@ public class WebSecurityTests {
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST);
} }
@Test
public void ignoringMvcMatcherServletPath() throws Exception {
loadConfig(MvcMatcherServletPathConfig.class, LegacyMvcMatchingConfig.class);
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
setup();
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
setup();
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path/");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
setup();
this.request.setServletPath("/other");
this.request.setRequestURI("/other/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
public void loadConfig(Class<?>... configs) { public void loadConfig(Class<?>... configs) {
this.context = new AnnotationConfigWebApplicationContext(); this.context = new AnnotationConfigWebApplicationContext();
this.context.register(configs); this.context.register(configs);
@ -246,17 +200,6 @@ public class WebSecurityTests {
} }
@Configuration
static class LegacyMvcMatchingConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(true);
configurer.setUseTrailingSlashMatch(true);
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class RequestRejectedHandlerConfig { static class RequestRejectedHandlerConfig {

View File

@ -21,7 +21,6 @@ import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest; import okhttp3.mockwebserver.RecordedRequest;
import org.apache.commons.lang.StringUtils;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@ -39,6 +38,7 @@ import org.springframework.security.oauth2.server.resource.web.reactive.function
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient;
@ -197,7 +197,7 @@ public class SecurityReactorContextConfigurationResourceServerTests {
public MockResponse dispatch(RecordedRequest request) { public MockResponse dispatch(RecordedRequest request) {
MockResponse response = new MockResponse().setResponseCode(200); MockResponse response = new MockResponse().setResponseCode(200);
String header = request.getHeader("Authorization"); String header = request.getHeader("Authorization");
if (StringUtils.isBlank(header)) { if (!StringUtils.hasText(header)) {
return response; return response;
} }
return response.setBody(header); return response.setBody(header);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -85,6 +85,7 @@ import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.any; import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doCallRealMethod;
@ -153,6 +154,7 @@ public class AuthorizeHttpRequestsConfigurerTests {
@Test @Test
public void configureMvcMatcherAccessAuthorizationManagerWhenNotNullThenVerifyUse() throws Exception { public void configureMvcMatcherAccessAuthorizationManagerWhenNotNullThenVerifyUse() throws Exception {
CustomAuthorizationManagerConfig.authorizationManager = mock(AuthorizationManager.class); CustomAuthorizationManagerConfig.authorizationManager = mock(AuthorizationManager.class);
given(CustomAuthorizationManagerConfig.authorizationManager.authorize(any(), any())).willCallRealMethod();
this.spring.register(CustomAuthorizationManagerConfig.class, BasicController.class).autowire(); this.spring.register(CustomAuthorizationManagerConfig.class, BasicController.class).autowire();
this.mvc.perform(get("/")).andExpect(status().isOk()); this.mvc.perform(get("/")).andExpect(status().isOk());
verify(CustomAuthorizationManagerConfig.authorizationManager).check(any(), any()); verify(CustomAuthorizationManagerConfig.authorizationManager).check(any(), any());
@ -161,6 +163,8 @@ public class AuthorizeHttpRequestsConfigurerTests {
@Test @Test
public void configureNoParameterMvcMatcherAccessAuthorizationManagerWhenNotNullThenVerifyUse() throws Exception { public void configureNoParameterMvcMatcherAccessAuthorizationManagerWhenNotNullThenVerifyUse() throws Exception {
CustomAuthorizationManagerNoParameterConfig.authorizationManager = mock(AuthorizationManager.class); CustomAuthorizationManagerNoParameterConfig.authorizationManager = mock(AuthorizationManager.class);
given(CustomAuthorizationManagerNoParameterConfig.authorizationManager.authorize(any(), any()))
.willCallRealMethod();
this.spring.register(CustomAuthorizationManagerNoParameterConfig.class, BasicController.class).autowire(); this.spring.register(CustomAuthorizationManagerNoParameterConfig.class, BasicController.class).autowire();
this.mvc.perform(get("/")).andExpect(status().isOk()); this.mvc.perform(get("/")).andExpect(status().isOk());
verify(CustomAuthorizationManagerNoParameterConfig.authorizationManager).check(any(), any()); verify(CustomAuthorizationManagerNoParameterConfig.authorizationManager).check(any(), any());

View File

@ -48,8 +48,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector; import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -160,67 +158,6 @@ public class AuthorizeRequestsTests {
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
} }
@Test
public void mvcMatcher() throws Exception {
loadConfig(MvcMatcherConfig.class, LegacyMvcMatchingConfig.class);
this.request.setRequestURI("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setRequestURI("/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path/");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
@Test
public void requestWhenMvcMatcherDenyAllThenRespondsWithUnauthorized() throws Exception {
loadConfig(MvcMatcherInLambdaConfig.class, LegacyMvcMatchingConfig.class);
this.request.setRequestURI("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setRequestURI("/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path/");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
@Test
public void requestWhenMvcMatcherServletPathDenyAllThenMatchesOnServletPath() throws Exception {
loadConfig(MvcMatcherServletPathInLambdaConfig.class, LegacyMvcMatchingConfig.class);
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path/");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/foo");
this.request.setRequestURI("/foo/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
setup();
this.request.setServletPath("/");
this.request.setRequestURI("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
}
@Test @Test
public void mvcMatcherPathVariables() throws Exception { public void mvcMatcherPathVariables() throws Exception {
loadConfig(MvcMatcherPathVariablesConfig.class); loadConfig(MvcMatcherPathVariablesConfig.class);
@ -245,35 +182,6 @@ public class AuthorizeRequestsTests {
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
} }
@Test
public void mvcMatcherServletPath() throws Exception {
loadConfig(MvcMatcherServletPathConfig.class, LegacyMvcMatchingConfig.class);
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path/");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/foo");
this.request.setRequestURI("/foo/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
setup();
this.request.setServletPath("/");
this.request.setRequestURI("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
}
public void loadConfig(Class<?>... configs) { public void loadConfig(Class<?>... configs) {
this.context = new AnnotationConfigWebApplicationContext(); this.context = new AnnotationConfigWebApplicationContext();
this.context.register(configs); this.context.register(configs);
@ -639,15 +547,4 @@ public class AuthorizeRequestsTests {
} }
@Configuration
static class LegacyMvcMatchingConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(true);
configurer.setUseTrailingSlashMatch(true);
}
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -93,6 +93,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -613,6 +614,37 @@ public class CsrfConfigurerTests {
assertThat(cookies).isEmpty(); assertThat(cookies).isEmpty();
} }
@Test
public void spaConfigForbidden() throws Exception {
this.spring.register(CsrfSpaConfig.class, AllowHttpMethodsFirewallConfig.class, BasicController.class)
.autowire();
this.mvc.perform(post("/")).andExpect(status().isForbidden());
}
@Test
public void spaConfigOk() throws Exception {
this.spring.register(CsrfSpaConfig.class, AllowHttpMethodsFirewallConfig.class, BasicController.class)
.autowire();
this.mvc.perform(post("/").with(csrf())).andExpect(status().isOk());
}
@Test
public void spaConfigDoubleSubmit() throws Exception {
this.spring.register(CsrfSpaConfig.class, AllowHttpMethodsFirewallConfig.class, BasicController.class)
.autowire();
var token = this.mvc.perform(post("/"))
.andExpect(status().isForbidden())
.andExpect(cookie().exists("XSRF-TOKEN"))
.andReturn()
.getResponse()
.getCookie("XSRF-TOKEN");
this.mvc
.perform(post("/").header("X-XSRF-TOKEN", token.getValue())
.cookie(new Cookie("XSRF-TOKEN", token.getValue())))
.andExpect(status().isOk());
}
@Configuration @Configuration
static class AllowHttpMethodsFirewallConfig { static class AllowHttpMethodsFirewallConfig {
@ -1006,6 +1038,18 @@ public class CsrfConfigurerTests {
} }
@Configuration
@EnableWebSecurity
static class CsrfSpaConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(CsrfConfigurer::spa);
return http.build();
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class HttpBasicCsrfTokenRequestHandlerConfig { static class HttpBasicCsrfTokenRequestHandlerConfig {

View File

@ -41,8 +41,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector; import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -80,60 +78,12 @@ public class HttpSecurityRequestMatchersTests {
} }
} }
@Test
public void mvcMatcher() throws Exception {
loadConfig(MvcMatcherConfig.class, LegacyMvcMatchingConfig.class);
this.request.setServletPath("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path/");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
@Test @Test
public void mvcMatcherGetFiltersNoUnsupportedMethodExceptionFromDummyRequest() { public void mvcMatcherGetFiltersNoUnsupportedMethodExceptionFromDummyRequest() {
loadConfig(MvcMatcherConfig.class); loadConfig(MvcMatcherConfig.class);
assertThat(this.springSecurityFilterChain.getFilters("/path")).isNotEmpty(); assertThat(this.springSecurityFilterChain.getFilters("/path")).isNotEmpty();
} }
@Test
public void requestMatchersMvcMatcher() throws Exception {
loadConfig(RequestMatchersMvcMatcherConfig.class, LegacyMvcMatchingConfig.class);
this.request.setServletPath("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path/");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
@Test
public void requestMatchersWhenMvcMatcherInLambdaThenPathIsSecured() throws Exception {
loadConfig(RequestMatchersMvcMatcherInLambdaConfig.class, LegacyMvcMatchingConfig.class);
this.request.setServletPath("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path/");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
@Test @Test
public void requestMatchersMvcMatcherServletPath() throws Exception { public void requestMatchersMvcMatcherServletPath() throws Exception {
loadConfig(RequestMatchersMvcMatcherServeltPathConfig.class); loadConfig(RequestMatchersMvcMatcherServeltPathConfig.class);
@ -491,15 +441,4 @@ public class HttpSecurityRequestMatchersTests {
} }
@Configuration
static class LegacyMvcMatchingConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(true);
configurer.setUseTrailingSlashMatch(true);
}
}
} }

View File

@ -16,8 +16,6 @@
package org.springframework.security.config.annotation.web.configurers; package org.springframework.security.config.annotation.web.configurers;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -38,19 +36,14 @@ import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.servlet.MockServletContext; import org.springframework.security.web.servlet.MockServletContext;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector; import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -88,68 +81,12 @@ public class HttpSecuritySecurityMatchersTests {
} }
} }
@Test
public void securityMatcherWhenMvcThenMvcMatcher() throws Exception {
loadConfig(SecurityMatcherMvcConfig.class, LegacyMvcMatchingConfig.class);
this.request.setServletPath("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path/");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
@Test @Test
public void securityMatcherWhenMvcMatcherAndGetFiltersNoUnsupportedMethodExceptionFromDummyRequest() { public void securityMatcherWhenMvcMatcherAndGetFiltersNoUnsupportedMethodExceptionFromDummyRequest() {
loadConfig(SecurityMatcherMvcConfig.class); loadConfig(SecurityMatcherMvcConfig.class);
assertThat(this.springSecurityFilterChain.getFilters("/path")).isNotEmpty(); assertThat(this.springSecurityFilterChain.getFilters("/path")).isNotEmpty();
} }
@Test
public void securityMatchersWhenMvcThenMvcMatcher() throws Exception {
loadConfig(SecurityMatchersMvcMatcherConfig.class, LegacyMvcMatchingConfig.class);
this.request.setServletPath("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path/");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
List<RequestMatcher> requestMatchers = this.springSecurityFilterChain.getFilterChains()
.stream()
.map((chain) -> ((DefaultSecurityFilterChain) chain).getRequestMatcher())
.map((matcher) -> ReflectionTestUtils.getField(matcher, "requestMatchers"))
.map((matchers) -> (List<RequestMatcher>) matchers)
.findFirst()
.get();
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
assertThat(requestMatchers).hasOnlyElementsOfType(MvcRequestMatcher.class);
}
@Test
public void securityMatchersWhenMvcMatcherInLambdaThenPathIsSecured() throws Exception {
loadConfig(SecurityMatchersMvcMatcherInLambdaConfig.class, LegacyMvcMatchingConfig.class);
this.request.setServletPath("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path/");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
@Test @Test
public void securityMatchersMvcMatcherServletPath() throws Exception { public void securityMatchersMvcMatcherServletPath() throws Exception {
loadConfig(SecurityMatchersMvcMatcherServletPathConfig.class); loadConfig(SecurityMatchersMvcMatcherServletPathConfig.class);
@ -501,15 +438,4 @@ public class HttpSecuritySecurityMatchersTests {
} }
@Configuration
static class LegacyMvcMatchingConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(true);
configurer.setUseTrailingSlashMatch(true);
}
}
} }

View File

@ -46,8 +46,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector; import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -85,51 +83,6 @@ public class UrlAuthorizationConfigurerTests {
} }
} }
@Test
public void mvcMatcher() throws Exception {
loadConfig(MvcMatcherConfig.class, LegacyMvcMatchingConfig.class);
this.request.setRequestURI("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setRequestURI("/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path/");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
@Test
public void mvcMatcherServletPath() throws Exception {
loadConfig(MvcMatcherServletPathConfig.class, LegacyMvcMatchingConfig.class);
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/spring");
this.request.setRequestURI("/spring/path/");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/foo");
this.request.setRequestURI("/foo/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
setup();
this.request.setServletPath("/");
this.request.setRequestURI("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
}
@Test @Test
public void anonymousUrlAuthorization() { public void anonymousUrlAuthorization() {
loadConfig(AnonymousUrlAuthorizationConfig.class); loadConfig(AnonymousUrlAuthorizationConfig.class);
@ -258,17 +211,6 @@ public class UrlAuthorizationConfigurerTests {
} }
@Configuration
static class LegacyMvcMatchingConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(true);
configurer.setUseTrailingSlashMatch(true);
}
}
@EnableWebSecurity @EnableWebSecurity
@Configuration @Configuration
@EnableWebMvc @EnableWebMvc

View File

@ -43,7 +43,9 @@ import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor;
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter; import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
import org.springframework.security.web.authentication.preauth.x509.X509TestUtils;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -155,6 +157,28 @@ public class X509ConfigurerTests {
// @formatter:on // @formatter:on
} }
@Test
public void x509WhenSubjectX500PrincipalExtractor() throws Exception {
this.spring.register(SubjectX500PrincipalExtractorConfig.class).autowire();
X509Certificate certificate = loadCert("rod.cer");
// @formatter:off
this.mvc.perform(get("/").with(x509(certificate)))
.andExpect((result) -> assertThat(result.getRequest().getSession(false)).isNull())
.andExpect(authenticated().withUsername("rod"));
// @formatter:on
}
@Test
public void x509WhenSubjectX500PrincipalExtractorBean() throws Exception {
this.spring.register(SubjectX500PrincipalExtractorEmailConfig.class).autowire();
X509Certificate certificate = X509TestUtils.buildTestCertificate();
// @formatter:off
this.mvc.perform(get("/").with(x509(certificate)))
.andExpect((result) -> assertThat(result.getRequest().getSession(false)).isNull())
.andExpect(authenticated().withUsername("luke@monkeymachine"));
// @formatter:on
}
private <T extends Certificate> T loadCert(String location) { private <T extends Certificate> T loadCert(String location) {
try (InputStream is = new ClassPathResource(location).getInputStream()) { try (InputStream is = new ClassPathResource(location).getInputStream()) {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
@ -360,4 +384,60 @@ public class X509ConfigurerTests {
} }
@Configuration
@EnableWebSecurity
static class SubjectX500PrincipalExtractorConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.x509((x509) -> x509
.x509PrincipalExtractor(new SubjectX500PrincipalExtractor())
);
// @formatter:on
return http.build();
}
@Bean
UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("rod")
.password("password")
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(user);
}
}
@Configuration
@EnableWebSecurity
static class SubjectX500PrincipalExtractorEmailConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
SubjectX500PrincipalExtractor principalExtractor = new SubjectX500PrincipalExtractor();
principalExtractor.setExtractPrincipalNameFromEmail(true);
// @formatter:off
http
.x509((x509) -> x509
.x509PrincipalExtractor(principalExtractor)
);
// @formatter:on
return http.build();
}
@Bean
UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("luke@monkeymachine")
.password("password")
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(user);
}
}
} }

View File

@ -128,12 +128,14 @@ import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthen
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver; import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler; import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter; import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl; import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.ResultMatcher;
@ -760,13 +762,6 @@ public class OAuth2ResourceServerConfigurerTests {
assertThat(oauth2.getBearerTokenResolver()).isEqualTo(resolver); assertThat(oauth2.getBearerTokenResolver()).isEqualTo(resolver);
} }
@Test
public void getBearerTokenResolverWhenNoResolverSpecifiedThenTheDefaultIsUsed() {
ApplicationContext context = this.spring.context(new GenericWebApplicationContext()).getContext();
OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
assertThat(oauth2.getBearerTokenResolver()).isInstanceOf(DefaultBearerTokenResolver.class);
}
@Test @Test
public void requestWhenCustomAuthenticationDetailsSourceThenUsed() throws Exception { public void requestWhenCustomAuthenticationDetailsSourceThenUsed() throws Exception {
this.spring.register(CustomAuthenticationDetailsSource.class, JwtDecoderConfig.class, BasicController.class) this.spring.register(CustomAuthenticationDetailsSource.class, JwtDecoderConfig.class, BasicController.class)
@ -1416,6 +1411,47 @@ public class OAuth2ResourceServerConfigurerTests {
verify(authenticationConverter).convert(any(), any()); verify(authenticationConverter).convert(any(), any());
} }
@Test
public void getAuthenticationConverterWhenDuplicateConverterBeansAndAnotherOnTheDslThenTheDslOneIsUsed() {
AuthenticationConverter converter = mock(AuthenticationConverter.class);
AuthenticationConverter converterBean = mock(AuthenticationConverter.class);
GenericWebApplicationContext context = new GenericWebApplicationContext();
context.registerBean("converterOne", AuthenticationConverter.class, () -> converterBean);
context.registerBean("converterTwo", AuthenticationConverter.class, () -> converterBean);
this.spring.context(context).autowire();
OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
oauth2.authenticationConverter(converter);
assertThat(oauth2.getAuthenticationConverter()).isEqualTo(converter);
}
@Test
public void getAuthenticationConverterWhenConverterBeanAndAnotherOnTheDslThenTheDslOneIsUsed() {
AuthenticationConverter converter = mock(AuthenticationConverter.class);
AuthenticationConverter converterBean = mock(AuthenticationConverter.class);
GenericWebApplicationContext context = new GenericWebApplicationContext();
context.registerBean(AuthenticationConverter.class, () -> converterBean);
this.spring.context(context).autowire();
OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
oauth2.authenticationConverter(converter);
assertThat(oauth2.getAuthenticationConverter()).isEqualTo(converter);
}
@Test
public void getAuthenticationConverterWhenDuplicateConverterBeansThenWiringException() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(
() -> this.spring.register(MultipleAuthenticationConverterBeansConfig.class, JwtDecoderConfig.class)
.autowire())
.withRootCauseInstanceOf(NoUniqueBeanDefinitionException.class);
}
@Test
public void getAuthenticationConverterWhenNoConverterSpecifiedThenTheDefaultIsUsed() {
ApplicationContext context = this.spring.context(new GenericWebApplicationContext()).getContext();
OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
assertThat(oauth2.getAuthenticationConverter()).isInstanceOf(BearerTokenAuthenticationConverter.class);
}
private static <T> void registerMockBean(GenericApplicationContext context, String name, Class<T> clazz) { private static <T> void registerMockBean(GenericApplicationContext context, String name, Class<T> clazz) {
context.registerBean(name, clazz, () -> mock(clazz)); context.registerBean(name, clazz, () -> mock(clazz));
} }
@ -2517,6 +2553,43 @@ public class OAuth2ResourceServerConfigurerTests {
} }
@Configuration
@EnableWebSecurity
static class MultipleAuthenticationConverterBeansConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
// @formatter:on
}
@Bean
AuthenticationConverter authenticationConverterOne() {
DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver();
resolver.setAllowUriQueryParameter(true);
BearerTokenAuthenticationConverter authenticationConverter = new BearerTokenAuthenticationConverter();
authenticationConverter.setBearerTokenResolver(resolver);
return authenticationConverter;
}
@Bean
AuthenticationConverter authenticationConverterTwo() {
DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver();
resolver.setAllowUriQueryParameter(true);
BearerTokenAuthenticationConverter authenticationConverter = new BearerTokenAuthenticationConverter();
authenticationConverter.setBearerTokenResolver(resolver);
return authenticationConverter;
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
static class MultipleIssuersConfig { static class MultipleIssuersConfig {

View File

@ -253,7 +253,7 @@ public class Saml2LoginConfigurerTests {
public void authenticationRequestWhenAuthenticationRequestResolverBeanThenUses() throws Exception { public void authenticationRequestWhenAuthenticationRequestResolverBeanThenUses() throws Exception {
this.spring.register(CustomAuthenticationRequestResolverBean.class).autowire(); this.spring.register(CustomAuthenticationRequestResolverBean.class).autowire();
MvcResult result = this.mvc.perform(get("/saml2/authenticate/registration-id")).andReturn(); MvcResult result = this.mvc.perform(get("/saml2/authenticate/registration-id")).andReturn();
UriComponents components = UriComponentsBuilder.fromHttpUrl(result.getResponse().getRedirectedUrl()).build(); UriComponents components = UriComponentsBuilder.fromUriString(result.getResponse().getRedirectedUrl()).build();
String samlRequest = components.getQueryParams().getFirst("SAMLRequest"); String samlRequest = components.getQueryParams().getFirst("SAMLRequest");
String decoded = URLDecoder.decode(samlRequest, "UTF-8"); String decoded = URLDecoder.decode(samlRequest, "UTF-8");
String inflated = Saml2Utils.samlInflate(Saml2Utils.samlDecode(decoded)); String inflated = Saml2Utils.samlInflate(Saml2Utils.samlDecode(decoded));
@ -264,7 +264,7 @@ public class Saml2LoginConfigurerTests {
public void authenticationRequestWhenAuthenticationRequestResolverDslThenUses() throws Exception { public void authenticationRequestWhenAuthenticationRequestResolverDslThenUses() throws Exception {
this.spring.register(CustomAuthenticationRequestResolverDsl.class).autowire(); this.spring.register(CustomAuthenticationRequestResolverDsl.class).autowire();
MvcResult result = this.mvc.perform(get("/saml2/authenticate/registration-id")).andReturn(); MvcResult result = this.mvc.perform(get("/saml2/authenticate/registration-id")).andReturn();
UriComponents components = UriComponentsBuilder.fromHttpUrl(result.getResponse().getRedirectedUrl()).build(); UriComponents components = UriComponentsBuilder.fromUriString(result.getResponse().getRedirectedUrl()).build();
String samlRequest = components.getQueryParams().getFirst("SAMLRequest"); String samlRequest = components.getQueryParams().getFirst("SAMLRequest");
String decoded = URLDecoder.decode(samlRequest, "UTF-8"); String decoded = URLDecoder.decode(samlRequest, "UTF-8");
String inflated = Saml2Utils.samlInflate(Saml2Utils.samlDecode(decoded)); String inflated = Saml2Utils.samlInflate(Saml2Utils.samlDecode(decoded));

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -389,14 +389,14 @@ public class Saml2LogoutConfigurerTests {
} }
@Test @Test
public void saml2LogoutRequestWhenInvalidSamlRequestThen401() throws Exception { public void saml2LogoutRequestWhenInvalidSamlRequestThen302Redirect() throws Exception {
this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); this.spring.register(Saml2LogoutDefaultsConfig.class).autowire();
this.mvc this.mvc
.perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest) .perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest)
.param("RelayState", this.apLogoutRequestRelayState) .param("RelayState", this.apLogoutRequestRelayState)
.param("SigAlg", this.apLogoutRequestSigAlg) .param("SigAlg", this.apLogoutRequestSigAlg)
.with(authentication(this.user))) .with(authentication(this.user)))
.andExpect(status().isUnauthorized()); .andExpect(status().isFound());
verifyNoInteractions(getBean(LogoutHandler.class)); verifyNoInteractions(getBean(LogoutHandler.class));
} }

View File

@ -30,12 +30,12 @@ import java.util.TreeMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.apache.commons.lang.StringUtils;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.security.config.http.SecurityFiltersAssertions; import org.springframework.security.config.http.SecurityFiltersAssertions;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -65,7 +65,7 @@ public class XsdDocumentedTests {
String schema31xDocumentLocation = "org/springframework/security/config/spring-security-3.1.xsd"; String schema31xDocumentLocation = "org/springframework/security/config/spring-security-3.1.xsd";
String schemaDocumentLocation = "org/springframework/security/config/spring-security-6.5.xsd"; String schemaDocumentLocation = "org/springframework/security/config/spring-security-7.0.xsd";
XmlSupport xml = new XmlSupport(); XmlSupport xml = new XmlSupport();
@ -86,7 +86,7 @@ public class XsdDocumentedTests {
.flatMap(XmlNode::children) .flatMap(XmlNode::children)
.flatMap(XmlNode::children) .flatMap(XmlNode::children)
.map((node) -> node.attribute("value")) .map((node) -> node.attribute("value"))
.filter(StringUtils::isNotEmpty) .filter(StringUtils::hasText)
.collect(Collectors.toList()); .collect(Collectors.toList());
// @formatter:on // @formatter:on
SecurityFiltersAssertions.assertEquals(nodes); SecurityFiltersAssertions.assertEquals(nodes);
@ -129,7 +129,7 @@ public class XsdDocumentedTests {
.flatMap(XmlNode::children) .flatMap(XmlNode::children)
.flatMap(XmlNode::children) .flatMap(XmlNode::children)
.map((node) -> node.attribute("value")) .map((node) -> node.attribute("value"))
.filter(StringUtils::isNotEmpty) .filter(StringUtils::hasText)
.collect(Collectors.toList()); .collect(Collectors.toList());
// @formatter:on // @formatter:on
assertThat(nodes).isEqualTo(expected); assertThat(nodes).isEqualTo(expected);
@ -151,8 +151,8 @@ public class XsdDocumentedTests {
.list((dir, name) -> name.endsWith(".xsd")); .list((dir, name) -> name.endsWith(".xsd"));
// @formatter:on // @formatter:on
assertThat(schemas.length) assertThat(schemas.length)
.withFailMessage("the count is equal to 27, if not then schemaDocument needs updating") .withFailMessage("the count is equal to 28, if not then schemaDocument needs updating")
.isEqualTo(27); .isEqualTo(28);
} }
/** /**

View File

@ -254,8 +254,6 @@ public class InterceptUrlConfigTests {
public void requestWhenUsingMvcMatchersThenAuthorizesRequestsAccordingly() throws Exception { public void requestWhenUsingMvcMatchersThenAuthorizesRequestsAccordingly() throws Exception {
this.spring.configLocations(this.xml("MvcMatchers")).autowire(); this.spring.configLocations(this.xml("MvcMatchers")).autowire();
this.mvc.perform(get("/path")).andExpect(status().isUnauthorized()); this.mvc.perform(get("/path")).andExpect(status().isUnauthorized());
this.mvc.perform(get("/path.html")).andExpect(status().isUnauthorized());
this.mvc.perform(get("/path/")).andExpect(status().isUnauthorized());
} }
@Test @Test
@ -304,10 +302,6 @@ public class InterceptUrlConfigTests {
// @formatter:off // @formatter:off
this.mvc.perform(get("/spring/path").servletPath("/spring")) this.mvc.perform(get("/spring/path").servletPath("/spring"))
.andExpect(status().isUnauthorized()); .andExpect(status().isUnauthorized());
this.mvc.perform(get("/spring/path.html").servletPath("/spring"))
.andExpect(status().isUnauthorized());
this.mvc.perform(get("/spring/path/").servletPath("/spring"))
.andExpect(status().isUnauthorized());
// @formatter:on // @formatter:on
} }

View File

@ -21,6 +21,7 @@ import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.security.AccessController; import java.security.AccessController;
import java.security.Principal; import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
@ -91,6 +92,7 @@ import org.springframework.security.web.authentication.AnonymousAuthenticationFi
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter; import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
import org.springframework.security.web.authentication.preauth.x509.X509TestUtils;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter; import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter;
import org.springframework.security.web.authentication.ui.DefaultResourcesFilter; import org.springframework.security.web.authentication.ui.DefaultResourcesFilter;
@ -398,6 +400,27 @@ public class MiscHttpConfigTests {
.containsSubsequence(CsrfFilter.class, X509AuthenticationFilter.class, ExceptionTranslationFilter.class); .containsSubsequence(CsrfFilter.class, X509AuthenticationFilter.class, ExceptionTranslationFilter.class);
} }
@Test
public void getWhenUsingX509PrincipalExtractorRef() throws Exception {
this.spring.configLocations(xml("X509PrincipalExtractorRef")).autowire();
X509Certificate certificate = X509TestUtils.buildTestCertificate();
RequestPostProcessor x509 = x509(certificate);
// @formatter:off
this.mvc.perform(get("/protected").with(x509))
.andExpect(status().isOk());
// @formatter:on
}
@Test
public void getWhenUsingX509PrincipalExtractorRefAndSubjectPrincipalRegex() throws Exception {
String xmlResourceName = "X509PrincipalExtractorRefAndSubjectPrincipalRegex";
// @formatter:off
assertThatExceptionOfType(BeanDefinitionParsingException.class)
.isThrownBy(() -> this.spring.configLocations(xml(xmlResourceName)).autowire())
.withMessage("Configuration problem: The attribute 'principal-extractor-ref' cannot be used together with the 'subject-principal-regex' attribute within <x509>\n" + "Offending resource: class path resource [org/springframework/security/config/http/MiscHttpConfigTests-X509PrincipalExtractorRefAndSubjectPrincipalRegex.xml]");
// @formatter:on
}
@Test @Test
public void getWhenUsingX509AndPropertyPlaceholderThenSubjectPrincipalRegexIsConfigured() throws Exception { public void getWhenUsingX509AndPropertyPlaceholderThenSubjectPrincipalRegexIsConfigured() throws Exception {
System.setProperty("subject_principal_regex", "OU=(.*?)(?:,|$)"); System.setProperty("subject_principal_regex", "OU=(.*?)(?:,|$)");

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -25,7 +25,6 @@ import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -50,13 +49,11 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate; import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
import org.springframework.beans.factory.xml.ParserContext; import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.beans.factory.xml.XmlReaderContext; import org.springframework.beans.factory.xml.XmlReaderContext;
@ -85,12 +82,14 @@ import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.TestJwts; import org.springframework.security.oauth2.jwt.TestJwts;
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenAuthenticationConverter; import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.MvcResult;
@ -462,6 +461,24 @@ public class OAuth2ResourceServerBeanDefinitionParserTests {
verify(bearerTokenResolver).resolve(any(HttpServletRequest.class)); verify(bearerTokenResolver).resolve(any(HttpServletRequest.class));
} }
@Test
public void getWhenCustomAuthenticationConverterThenUses() throws Exception {
this.spring
.configLocations(xml("MockAuthenticationConverter"), xml("MockJwtDecoder"), xml("AuthenticationConverter"))
.autowire();
JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class);
given(decoder.decode("token")).willReturn(TestJwts.jwt().build());
AuthenticationConverter authenticationConverter = this.spring.getContext()
.getBean(AuthenticationConverter.class);
given(authenticationConverter.convert(any(HttpServletRequest.class)))
.willReturn(new BearerTokenAuthenticationToken("token"));
this.mvc.perform(get("/")).andExpect(status().isNotFound());
verify(decoder).decode("token");
verify(authenticationConverter).convert(any(HttpServletRequest.class));
}
@Test @Test
public void requestWhenBearerTokenResolverAllowsRequestBodyThenEitherHeaderOrRequestBodyIsAccepted() public void requestWhenBearerTokenResolverAllowsRequestBodyThenEitherHeaderOrRequestBodyIsAccepted()
throws Exception { throws Exception {
@ -521,14 +538,6 @@ public class OAuth2ResourceServerBeanDefinitionParserTests {
// @formatter:on // @formatter:on
} }
@Test
public void getBearerTokenResolverWhenNoResolverSpecifiedThenTheDefaultIsUsed() {
OAuth2ResourceServerBeanDefinitionParser oauth2 = new OAuth2ResourceServerBeanDefinitionParser(
mock(BeanReference.class), mock(List.class), mock(Map.class), mock(Map.class), mock(List.class),
mock(BeanMetadataElement.class));
assertThat(oauth2.getBearerTokenResolver(mock(Element.class))).isInstanceOf(RootBeanDefinition.class);
}
@Test @Test
public void requestWhenCustomJwtDecoderThenUsed() throws Exception { public void requestWhenCustomJwtDecoderThenUsed() throws Exception {
this.spring.configLocations(xml("MockJwtDecoder"), xml("Jwt")).autowire(); this.spring.configLocations(xml("MockJwtDecoder"), xml("Jwt")).autowire();
@ -545,6 +554,12 @@ public class OAuth2ResourceServerBeanDefinitionParserTests {
.isThrownBy(() -> this.spring.configLocations(xml("JwtDecoderAndJwkSetUri")).autowire()); .isThrownBy(() -> this.spring.configLocations(xml("JwtDecoderAndJwkSetUri")).autowire());
} }
@Test
public void configureWhenAuthenticationConverterAndJwkSetUriThenException() {
assertThatExceptionOfType(BeanDefinitionStoreException.class).isThrownBy(
() -> this.spring.configLocations(xml("AuthenticationConverterAndBearerTokenResolver")).autowire());
}
@Test @Test
public void requestWhenRealmNameConfiguredThenUsesOnUnauthenticated() throws Exception { public void requestWhenRealmNameConfiguredThenUsesOnUnauthenticated() throws Exception {
this.spring.configLocations(xml("MockJwtDecoder"), xml("AuthenticationEntryPoint")).autowire(); this.spring.configLocations(xml("MockJwtDecoder"), xml("AuthenticationEntryPoint")).autowire();

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -287,15 +287,16 @@ public class Saml2LogoutBeanDefinitionParserTests {
.andExpect(status().isBadRequest()); .andExpect(status().isBadRequest());
} }
// gh-14635
@Test @Test
public void saml2LogoutRequestWhenInvalidSamlRequestThen401() throws Exception { public void saml2LogoutRequestWhenInvalidSamlRequestThen302Redirect() throws Exception {
this.spring.configLocations(this.xml("Default")).autowire(); this.spring.configLocations(this.xml("Default")).autowire();
this.mvc this.mvc
.perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest) .perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest)
.param("RelayState", this.apLogoutRequestRelayState) .param("RelayState", this.apLogoutRequestRelayState)
.param("SigAlg", this.apLogoutRequestSigAlg) .param("SigAlg", this.apLogoutRequestSigAlg)
.with(authentication(this.saml2User))) .with(authentication(this.saml2User)))
.andExpect(status().isUnauthorized()); .andExpect(status().isFound());
} }
@Test @Test

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2020 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -115,6 +115,24 @@ public class CommonOAuth2ProviderTests {
assertThat(registration.getRegistrationId()).isEqualTo("123"); assertThat(registration.getRegistrationId()).isEqualTo("123");
} }
@Test
public void getBuilderWhenXShouldHaveXSettings() {
ClientRegistration registration = build(CommonOAuth2Provider.X);
ProviderDetails providerDetails = registration.getProviderDetails();
assertThat(providerDetails.getAuthorizationUri()).isEqualTo("https://x.com/i/oauth2/authorize");
assertThat(providerDetails.getTokenUri()).isEqualTo("https://api.x.com/2/oauth2/token");
assertThat(providerDetails.getUserInfoEndpoint().getUri()).isEqualTo("https://api.x.com/2/users/me");
assertThat(providerDetails.getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo("username");
assertThat(providerDetails.getJwkSetUri()).isNull();
assertThat(registration.getClientAuthenticationMethod())
.isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_POST);
assertThat(registration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
assertThat(registration.getRedirectUri()).isEqualTo(DEFAULT_REDIRECT_URL);
assertThat(registration.getScopes()).containsOnly("users.read", "tweet.read");
assertThat(registration.getClientName()).isEqualTo("X");
assertThat(registration.getRegistrationId()).isEqualTo("123");
}
private ClientRegistration build(CommonOAuth2Provider provider) { private ClientRegistration build(CommonOAuth2Provider provider) {
return builder(provider).build(); return builder(provider).build();
} }

View File

@ -18,8 +18,6 @@ package org.springframework.security.config.web.server;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -114,12 +112,13 @@ public class CorsSpecTests {
.exchange() .exchange()
.returnResult(String.class); .returnResult(String.class);
// @formatter:on // @formatter:on
Map<String, List<String>> responseHeaders = response.getResponseHeaders(); HttpHeaders responseHeaders = response.getResponseHeaders();
if (!this.expectedHeaders.isEmpty()) { if (!this.expectedHeaders.isEmpty()) {
assertThat(responseHeaders).describedAs(response.toString()).containsAllEntriesOf(this.expectedHeaders); this.expectedHeaders.forEach(
(headerName, headerValues) -> assertThat(responseHeaders.get(headerName)).isEqualTo(headerValues));
} }
if (!this.headerNamesNotPresent.isEmpty()) { if (!this.headerNamesNotPresent.isEmpty()) {
assertThat(responseHeaders.keySet()).doesNotContainAnyElementsOf(this.headerNamesNotPresent); assertThat(responseHeaders.headerNames()).doesNotContainAnyElementsOf(this.headerNamesNotPresent);
} }
} }

View File

@ -18,8 +18,6 @@ package org.springframework.security.config.web.server;
import java.time.Duration; import java.time.Duration;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -80,14 +78,14 @@ public class HeaderSpecTests {
@Test @Test
public void headersWhenDisableThenNoSecurityHeaders() { public void headersWhenDisableThenNoSecurityHeaders() {
new HashSet<>(this.expectedHeaders.keySet()).forEach(this::expectHeaderNamesNotPresent); new HashSet<>(this.expectedHeaders.headerNames()).forEach(this::expectHeaderNamesNotPresent);
this.http.headers().disable(); this.http.headers().disable();
assertHeaders(); assertHeaders();
} }
@Test @Test
public void headersWhenDisableInLambdaThenNoSecurityHeaders() { public void headersWhenDisableInLambdaThenNoSecurityHeaders() {
new HashSet<>(this.expectedHeaders.keySet()).forEach(this::expectHeaderNamesNotPresent); new HashSet<>(this.expectedHeaders.headerNames()).forEach(this::expectHeaderNamesNotPresent);
this.http.headers((headers) -> headers.disable()); this.http.headers((headers) -> headers.disable());
assertHeaders(); assertHeaders();
} }
@ -515,12 +513,13 @@ public class HeaderSpecTests {
.uri("https://example.com/") .uri("https://example.com/")
.exchange() .exchange()
.returnResult(String.class); .returnResult(String.class);
Map<String, List<String>> responseHeaders = response.getResponseHeaders(); HttpHeaders responseHeaders = response.getResponseHeaders();
if (!this.expectedHeaders.isEmpty()) { if (!this.expectedHeaders.isEmpty()) {
assertThat(responseHeaders).describedAs(response.toString()).containsAllEntriesOf(this.expectedHeaders); this.expectedHeaders.forEach(
(headerName, headerValues) -> assertThat(responseHeaders.get(headerName)).isEqualTo(headerValues));
} }
if (!this.headerNamesNotPresent.isEmpty()) { if (!this.headerNamesNotPresent.isEmpty()) {
assertThat(responseHeaders.keySet()).doesNotContainAnyElementsOf(this.headerNamesNotPresent); assertThat(responseHeaders.headerNames()).doesNotContainAnyElementsOf(this.headerNamesNotPresent);
} }
} }

View File

@ -945,7 +945,7 @@ public class OidcLogoutSpecTests {
private MockResponse toMockResponse(FluxExchangeResult<String> result) { private MockResponse toMockResponse(FluxExchangeResult<String> result) {
MockResponse response = new MockResponse(); MockResponse response = new MockResponse();
response.setResponseCode(result.getStatus().value()); response.setResponseCode(result.getStatus().value());
for (String name : result.getResponseHeaders().keySet()) { for (String name : result.getResponseHeaders().headerNames()) {
response.addHeader(name, result.getResponseHeaders().getFirst(name)); response.addHeader(name, result.getResponseHeaders().getFirst(name));
} }
String body = result.getResponseBody().blockFirst(); String body = result.getResponseBody().blockFirst();

View File

@ -85,7 +85,7 @@ final class HtmlUnitWebTestClient {
} }
return request; return request;
} }
return request.body(BodyInserters.fromObject(requestBody)); return request.body(BodyInserters.fromProducer(requestBody, String.class));
} }
private MultiValueMap<String, String> formData(List<NameValuePair> params) { private MultiValueMap<String, String> formData(List<NameValuePair> params) {
@ -161,7 +161,7 @@ final class HtmlUnitWebTestClient {
redirectUrl = scheme + "://" + host + location.toASCIIString(); redirectUrl = scheme + "://" + host + location.toASCIIString();
} }
// @formatter:off // @formatter:off
ClientRequest redirect = ClientRequest.method(HttpMethod.GET, URI.create(redirectUrl)) ClientRequest redirect = ClientRequest.create(HttpMethod.GET, URI.create(redirectUrl))
.headers((headers) -> headers.addAll(request.headers())) .headers((headers) -> headers.addAll(request.headers()))
.cookies((cookies) -> cookies.addAll(request.cookies())) .cookies((cookies) -> cookies.addAll(request.cookies()))
.attributes((attributes) -> attributes.putAll(request.attributes())) .attributes((attributes) -> attributes.putAll(request.attributes()))

View File

@ -150,26 +150,6 @@ class AuthorizeHttpRequestsDslTests {
} }
} }
@Test
fun `request when allowed by mvc then responds with OK`() {
this.spring.register(AuthorizeHttpRequestsByMvcConfig::class.java, LegacyMvcMatchingConfig::class.java).autowire()
this.mockMvc.get("/path")
.andExpect {
status { isOk() }
}
this.mockMvc.get("/path.html")
.andExpect {
status { isOk() }
}
this.mockMvc.get("/path/")
.andExpect {
status { isOk() }
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@EnableWebMvc @EnableWebMvc
@ -193,14 +173,6 @@ class AuthorizeHttpRequestsDslTests {
} }
} }
@Configuration
open class LegacyMvcMatchingConfig : WebMvcConfigurer {
override fun configurePathMatch(configurer: PathMatchConfigurer) {
configurer.setUseSuffixPatternMatch(true)
configurer.setUseTrailingSlashMatch(true)
}
}
@Test @Test
fun `request when secured by mvc path variables then responds based on path variable value`() { fun `request when secured by mvc path variables then responds based on path variable value`() {
this.spring.register(MvcMatcherPathVariablesConfig::class.java).autowire() this.spring.register(MvcMatcherPathVariablesConfig::class.java).autowire()

View File

@ -135,26 +135,6 @@ class AuthorizeRequestsDslTests {
} }
} }
@Test
fun `request when allowed by mvc then responds with OK`() {
this.spring.register(AuthorizeRequestsByMvcConfig::class.java, LegacyMvcMatchingConfig::class.java).autowire()
this.mockMvc.get("/path")
.andExpect {
status { isOk() }
}
this.mockMvc.get("/path.html")
.andExpect {
status { isOk() }
}
this.mockMvc.get("/path/")
.andExpect {
status { isOk() }
}
}
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@EnableWebMvc @EnableWebMvc
@ -179,14 +159,6 @@ class AuthorizeRequestsDslTests {
} }
} }
@Configuration
open class LegacyMvcMatchingConfig : WebMvcConfigurer {
override fun configurePathMatch(configurer: PathMatchConfigurer) {
configurer.setUseSuffixPatternMatch(true)
configurer.setUseTrailingSlashMatch(true)
}
}
@Test @Test
fun `request when secured by mvc path variables then responds based on path variable value`() { fun `request when secured by mvc path variables then responds based on path variable value`() {
this.spring.register(MvcMatcherPathVariablesConfig::class.java).autowire() this.spring.register(MvcMatcherPathVariablesConfig::class.java).autowire()

View File

@ -127,7 +127,7 @@ class ServerHttpsRedirectDslTests {
return http { return http {
redirectToHttps { redirectToHttps {
httpsRedirectWhen { httpsRedirectWhen {
it.request.headers.containsKey("X-Requires-Https") it.request.headers.headerNames().contains("X-Requires-Https")
} }
} }
} }
@ -165,7 +165,7 @@ class ServerHttpsRedirectDslTests {
redirectToHttps { redirectToHttps {
httpsRedirectWhen(PathPatternParserServerWebExchangeMatcher("/secure")) httpsRedirectWhen(PathPatternParserServerWebExchangeMatcher("/secure"))
httpsRedirectWhen { httpsRedirectWhen {
it.request.headers.containsKey("X-Requires-Https") it.request.headers.headerNames().contains("X-Requires-Https")
} }
} }
} }

View File

@ -7,8 +7,6 @@
<logger name="org.springframework.security" level="${sec.log.level:-WARN}"/> <logger name="org.springframework.security" level="${sec.log.level:-WARN}"/>
<logger name="org.apache.directory" level="ERROR"/>
<root level="${root.level:-WARN}"> <root level="${root.level:-WARN}">
<appender-ref ref="STDOUT" /> <appender-ref ref="STDOUT" />
</root> </root>

View File

@ -33,7 +33,7 @@
</http> </http>
<mvc:annotation-driven> <mvc:annotation-driven>
<mvc:path-matching suffix-pattern="true" trailing-slash="true"/> <mvc:path-matching />
</mvc:annotation-driven> </mvc:annotation-driven>
<b:bean name="path" class="org.springframework.security.config.http.InterceptUrlConfigTests.PathController"/> <b:bean name="path" class="org.springframework.security.config.http.InterceptUrlConfigTests.PathController"/>

View File

@ -33,7 +33,7 @@
</http> </http>
<mvc:annotation-driven> <mvc:annotation-driven>
<mvc:path-matching suffix-pattern="true"/> <mvc:path-matching />
</mvc:annotation-driven> </mvc:annotation-driven>
<b:bean name="path" class="org.springframework.security.config.http.InterceptUrlConfigTests.PathController"/> <b:bean name="path" class="org.springframework.security.config.http.InterceptUrlConfigTests.PathController"/>

View File

@ -33,7 +33,7 @@
</http> </http>
<mvc:annotation-driven> <mvc:annotation-driven>
<mvc:path-matching suffix-pattern="true" trailing-slash="true"/> <mvc:path-matching />
</mvc:annotation-driven> </mvc:annotation-driven>
<b:bean name="path" class="org.springframework.security.config.http.InterceptUrlConfigTests.PathController"/> <b:bean name="path" class="org.springframework.security.config.http.InterceptUrlConfigTests.PathController"/>

View File

@ -33,7 +33,7 @@
</http> </http>
<mvc:annotation-driven> <mvc:annotation-driven>
<mvc:path-matching suffix-pattern="true"/> <mvc:path-matching />
</mvc:annotation-driven> </mvc:annotation-driven>
<b:bean name="path" class="org.springframework.security.config.http.InterceptUrlConfigTests.PathController"/> <b:bean name="path" class="org.springframework.security.config.http.InterceptUrlConfigTests.PathController"/>

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2002-2018 the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<http>
<x509 principal-extractor-ref="principalExtractor"/>
<intercept-url pattern="/**" access="authenticated"/>
</http>
<user-service id="us">
<user name="luke@monkeymachine" password="{noop}password" authorities="ROLE_USER"/>
</user-service>
<b:bean name="principalExtractor" class="org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor"
p:extractPrincipalNameFromEmail="true"/>
<b:import resource="MiscHttpConfigTests-controllers.xml"/>
</b:beans>

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2002-2018 the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="
http://www.springframework.org/schema/security
https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<http>
<x509 principal-extractor-ref="principalExtractor" subject-principal-regex="(.*)"/>
<intercept-url pattern="/**" access="authenticated"/>
</http>
<user-service id="us">
<user name="luke@monkeymachine" password="{noop}password" authorities="ROLE_USER"/>
</user-service>
<b:bean name="principalExtractor" class="org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor"
p:extractPrincipalNameFromEmail="true"/>
<b:import resource="MiscHttpConfigTests-controllers.xml"/>
</b:beans>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2002-2020 the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<http>
<intercept-url pattern="/**" access="authenticated"/>
<oauth2-resource-server authentication-converter-ref="authenticationConverter">
<jwt decoder-ref="decoder"/>
</oauth2-resource-server>
</http>
<b:import resource="userservice.xml"/>
</b:beans>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2002-2020 the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<http>
<intercept-url pattern="/**" access="authenticated"/>
<oauth2-resource-server authentication-converter-ref="authenticationConverter" bearer-token-resolver-ref="bearerTokenResolver">
<jwt decoder-ref="decoder"/>
</oauth2-resource-server>
</http>
<b:import resource="userservice.xml"/>
</b:beans>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2002-2020 the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<b:bean name="authenticationConverter" class="org.mockito.Mockito" factory-method="mock">
<b:constructor-arg value="org.springframework.security.web.authentication.AuthenticationConverter" type="java.lang.Class"/>
</b:bean>
</b:beans>

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