Revert unnecessary merges on 6.1.x
This commit removes unnecessary main-branch merges starting from9f8db22b77
and adds the following needed commit(s) that were made afterward: -4d6ff49b9d
-ed6ff670d1
-c823b00794
-44fad21363
This commit is contained in:
commit
447f40949c
|
@ -1,92 +0,0 @@
|
|||
version: 2
|
||||
|
||||
registries:
|
||||
spring-milestones:
|
||||
type: maven-repository
|
||||
url: https://repo.spring.io/milestone
|
||||
|
||||
updates:
|
||||
|
||||
- package-ecosystem: "gradle"
|
||||
target-branch: "main"
|
||||
milestone: 319 # 6.2.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" # nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency
|
||||
- dependency-name: "org.python:jython" # jython updates break integration tests
|
||||
- dependency-name: "org.apache.directory.server:*" # ApacheDS version > 1.5.5 contains break changes
|
||||
- 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"
|
||||
target-branch: "6.1.x"
|
||||
milestone: 318 # 6.1.x
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "03:00"
|
||||
timezone: "Etc/UTC"
|
||||
labels: [ "type: dependency-upgrade" ]
|
||||
ignore:
|
||||
- dependency-name: "com.nimbusds:nimbus-jose-jwt" # nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency
|
||||
- dependency-name: "org.python:jython" # jython updates break integration tests
|
||||
- dependency-name: "org.apache.directory.server:*" # ApacheDS version > 1.5.5 contains break changes
|
||||
- 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"
|
||||
target-branch: "6.0.x"
|
||||
milestone: 143 # 6.0.x
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "03:00"
|
||||
timezone: "Etc/UTC"
|
||||
labels: [ "type: dependency-upgrade" ]
|
||||
ignore:
|
||||
- dependency-name: "com.nimbusds:nimbus-jose-jwt" # nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency
|
||||
- dependency-name: "org.python:jython" # jython updates break integration tests
|
||||
- dependency-name: "org.apache.directory.server:*" # ApacheDS version > 1.5.5 contains break changes
|
||||
- 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"
|
||||
target-branch: "5.8.x"
|
||||
milestone: 246 # 5.8.x
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
time: "03:00"
|
||||
timezone: "Etc/UTC"
|
||||
labels: [ "type: dependency-upgrade" ]
|
||||
ignore:
|
||||
- dependency-name: "com.nimbusds:nimbus-jose-jwt" # nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency
|
||||
- dependency-name: "org.python:jython" # jython updates break integration tests
|
||||
- dependency-name: "org.apache.directory.server:*" # ApacheDS version > 1.5.5 contains break changes
|
||||
- dependency-name: "io.mockk:mockk" # mockk updates break tests
|
||||
- dependency-name: "org.opensaml:*" # org.opensaml maintains two different versions, so it must be updated manually
|
||||
- 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" ]
|
|
@ -69,13 +69,6 @@ jobs:
|
|||
snapshot_tests:
|
||||
name: Test against snapshots
|
||||
needs: [prerequisites]
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- java-version: '21-ea'
|
||||
toolchain: '21'
|
||||
- java-version: '17'
|
||||
toolchain: '17'
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.prerequisites.outputs.runjobs
|
||||
steps:
|
||||
|
@ -83,14 +76,14 @@ jobs:
|
|||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
java-version: ${{ matrix.java-version }}
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
- name: Snapshot Tests
|
||||
run: |
|
||||
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
|
||||
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
|
||||
./gradlew test --refresh-dependencies -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion='6.1.+' -PreactorVersion='2023.0.+' -PspringDataVersion='2023.1.+' -PlocksDisabled --stacktrace
|
||||
./gradlew test --refresh-dependencies -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PspringFrameworkVersion='6.0.+' -PreactorVersion='2022.0.+' -PspringDataVersion='2022.0.+' -PlocksDisabled --stacktrace
|
||||
check_samples:
|
||||
name: Check Samples project
|
||||
needs: [prerequisites]
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
name: Execute Gradle Wrapper Upgrade
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 2 * * *' # 2am UTC
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
upgrade_wrapper:
|
||||
name: Execution
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Git configuration
|
||||
env:
|
||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
git config --global url."https://unused-username:${TOKEN}@github.com/".insteadOf "https://github.com/"
|
||||
git config --global user.name 'github-actions[bot]'
|
||||
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
- name: Set up Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
- name: Upgrade Wrappers
|
||||
run: ./gradlew clean upgradeGradleWrapperAll --continue -Porg.gradle.java.installations.auto-download=false
|
||||
env:
|
||||
WRAPPER_UPGRADE_GIT_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -11,7 +11,7 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
# List of active maintenance branches.
|
||||
branch: [ main, 6.1.x, 6.0.x, 5.8.x ]
|
||||
branch: [ main, 6.0.x, 5.8.x, 5.7.x ]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
|
|
@ -118,7 +118,8 @@ public class AclPermissionEvaluator implements PermissionEvaluator {
|
|||
if (permission instanceof Permission[]) {
|
||||
return Arrays.asList((Permission[]) permission);
|
||||
}
|
||||
if (permission instanceof String permString) {
|
||||
if (permission instanceof String) {
|
||||
String permString = (String) permission;
|
||||
Permission p = buildPermission(permString);
|
||||
if (p != null) {
|
||||
return Arrays.asList(p);
|
||||
|
|
|
@ -56,9 +56,10 @@ public abstract class AbstractPermission implements Permission {
|
|||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(obj instanceof Permission other)) {
|
||||
if (!(obj instanceof Permission)) {
|
||||
return false;
|
||||
}
|
||||
Permission other = (Permission) obj;
|
||||
return (this.mask == other.getMask());
|
||||
}
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@ public class JdbcAclServiceTests {
|
|||
given(this.jdbcOperations.query(anyString(), eq(args), any(RowMapper.class))).willReturn(result);
|
||||
ObjectIdentity objectIdentity = new ObjectIdentityImpl(MockLongIdDomainObject.class, 1L);
|
||||
List<ObjectIdentity> objectIdentities = this.aclService.findChildren(objectIdentity);
|
||||
assertThat(objectIdentities).hasSize(1);
|
||||
assertThat(objectIdentities.size()).isEqualTo(1);
|
||||
assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo("5577");
|
||||
}
|
||||
|
||||
|
@ -127,7 +127,7 @@ public class JdbcAclServiceTests {
|
|||
public void findChildrenWithoutIdType() {
|
||||
ObjectIdentity objectIdentity = new ObjectIdentityImpl(MockLongIdDomainObject.class, 4711L);
|
||||
List<ObjectIdentity> objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity);
|
||||
assertThat(objectIdentities).hasSize(1);
|
||||
assertThat(objectIdentities.size()).isEqualTo(1);
|
||||
assertThat(objectIdentities.get(0).getType()).isEqualTo(MockUntypedIdDomainObject.class.getName());
|
||||
assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo(5000L);
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ public class JdbcAclServiceTests {
|
|||
public void findChildrenOfIdTypeLong() {
|
||||
ObjectIdentity objectIdentity = new ObjectIdentityImpl("location", "US-PAL");
|
||||
List<ObjectIdentity> objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity);
|
||||
assertThat(objectIdentities).hasSize(2);
|
||||
assertThat(objectIdentities.size()).isEqualTo(2);
|
||||
assertThat(objectIdentities.get(0).getType()).isEqualTo(MockLongIdDomainObject.class.getName());
|
||||
assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo(4711L);
|
||||
assertThat(objectIdentities.get(1).getType()).isEqualTo(MockLongIdDomainObject.class.getName());
|
||||
|
@ -155,7 +155,7 @@ public class JdbcAclServiceTests {
|
|||
ObjectIdentity objectIdentity = new ObjectIdentityImpl("location", "US");
|
||||
this.aclServiceIntegration.setAclClassIdSupported(true);
|
||||
List<ObjectIdentity> objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity);
|
||||
assertThat(objectIdentities).hasSize(1);
|
||||
assertThat(objectIdentities.size()).isEqualTo(1);
|
||||
assertThat(objectIdentities.get(0).getType()).isEqualTo("location");
|
||||
assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo("US-PAL");
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ public class JdbcAclServiceTests {
|
|||
ObjectIdentity objectIdentity = new ObjectIdentityImpl(MockUntypedIdDomainObject.class, 5000L);
|
||||
this.aclServiceIntegration.setAclClassIdSupported(true);
|
||||
List<ObjectIdentity> objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity);
|
||||
assertThat(objectIdentities).hasSize(1);
|
||||
assertThat(objectIdentities.size()).isEqualTo(1);
|
||||
assertThat(objectIdentities.get(0).getType()).isEqualTo("costcenter");
|
||||
assertThat(objectIdentities.get(0).getIdentifier())
|
||||
.isEqualTo(UUID.fromString("25d93b3f-c3aa-4814-9d5e-c7c96ced7762"));
|
||||
|
@ -186,7 +186,7 @@ public class JdbcAclServiceTests {
|
|||
ObjectIdentity objectIdentity = new ObjectIdentityImpl("location", "US");
|
||||
this.aclServiceIntegration.setAclClassIdSupported(true);
|
||||
List<ObjectIdentity> objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity);
|
||||
assertThat(objectIdentities).hasSize(1);
|
||||
assertThat(objectIdentities.size()).isEqualTo(1);
|
||||
assertThat(objectIdentities.get(0).getType()).isEqualTo("location");
|
||||
assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo("prefix:US-PAL");
|
||||
}
|
||||
|
|
|
@ -454,7 +454,7 @@ public class JdbcMutableAclServiceTests {
|
|||
CustomSid customSid = new CustomSid("Custom sid");
|
||||
given(customJdbcMutableAclService.createOrRetrieveSidPrimaryKey("Custom sid", false, false)).willReturn(1L);
|
||||
Long result = customJdbcMutableAclService.createOrRetrieveSidPrimaryKey(customSid, false);
|
||||
assertThat(Long.valueOf(1L)).isEqualTo(result);
|
||||
assertThat(new Long(1L)).isEqualTo(result);
|
||||
}
|
||||
|
||||
protected Authentication getAuth() {
|
||||
|
|
|
@ -120,9 +120,9 @@ public class SidTests {
|
|||
PrincipalSid principalSid = new PrincipalSid(authentication);
|
||||
GrantedAuthority ga = new SimpleGrantedAuthority("ROLE_TEST");
|
||||
GrantedAuthoritySid gaSid = new GrantedAuthoritySid(ga);
|
||||
assertThat("johndoe").isEqualTo(principalSid.getPrincipal());
|
||||
assertThat("johndoe".equals(principalSid.getPrincipal())).isTrue();
|
||||
assertThat("scott".equals(principalSid.getPrincipal())).isFalse();
|
||||
assertThat("ROLE_TEST").isEqualTo(gaSid.getGrantedAuthority());
|
||||
assertThat("ROLE_TEST".equals(gaSid.getGrantedAuthority())).isTrue();
|
||||
assertThat("ROLE_TEST2".equals(gaSid.getGrantedAuthority())).isFalse();
|
||||
}
|
||||
|
||||
|
|
|
@ -29,4 +29,10 @@ dependencies {
|
|||
testAspect sourceSets.main.output
|
||||
}
|
||||
|
||||
sourceSets.main.aspectj.srcDir "src/main/java"
|
||||
sourceSets.main.java.srcDirs = files()
|
||||
|
||||
sourceSets.test.aspectj.srcDir "src/test/java"
|
||||
sourceSets.test.java.srcDirs = files()
|
||||
|
||||
compileAspectj.ajcOptions.outxmlfile = "META-INF/aop.xml"
|
||||
|
|
|
@ -143,8 +143,8 @@ public class AnnotationSecurityAspectTests {
|
|||
SecurityContextHolder.getContext().setAuthentication(this.anne);
|
||||
List<String> objects = this.prePostSecured.postFilterMethod();
|
||||
assertThat(objects).hasSize(2);
|
||||
assertThat(objects).contains("apple");
|
||||
assertThat(objects).contains("aubergine");
|
||||
assertThat(objects.contains("apple")).isTrue();
|
||||
assertThat(objects.contains("aubergine")).isTrue();
|
||||
}
|
||||
|
||||
private void configureForElAnnotations() {
|
||||
|
|
42
build.gradle
42
build.gradle
|
@ -14,10 +14,6 @@ buildscript {
|
|||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.org.gradle.wrapper.upgrade)
|
||||
}
|
||||
|
||||
apply plugin: 'io.spring.nohttp'
|
||||
apply plugin: 'locks'
|
||||
apply plugin: 's101'
|
||||
|
@ -39,7 +35,6 @@ ext.milestoneBuild = !(snapshotBuild || releaseBuild)
|
|||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url "https://repo.spring.io/milestone" }
|
||||
}
|
||||
|
||||
tasks.named("saganCreateRelease") {
|
||||
|
@ -91,31 +86,17 @@ tasks.named("dispatchGitHubWorkflow") {
|
|||
}
|
||||
}
|
||||
|
||||
def toolchainVersion() {
|
||||
if (project.hasProperty('testToolchain')) {
|
||||
return project.property('testToolchain').toString().toInteger()
|
||||
}
|
||||
return 17
|
||||
}
|
||||
|
||||
subprojects {
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(toolchainVersion())
|
||||
}
|
||||
plugins.withType(JavaPlugin) {
|
||||
project.sourceCompatibility=JavaVersion.VERSION_17
|
||||
}
|
||||
kotlin {
|
||||
jvmToolchain {
|
||||
languageVersion = JavaLanguageVersion.of(17)
|
||||
}
|
||||
}
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = "UTF-8"
|
||||
options.compilerArgs.add("-parameters")
|
||||
options.release.set(17)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
allprojects {
|
||||
if (!['spring-security-bom', 'spring-security-docs'].contains(project.name)) {
|
||||
apply plugin: 'io.spring.javaformat'
|
||||
|
@ -140,6 +121,12 @@ allprojects {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
javaCompiler = javaToolchains.compilerFor {
|
||||
languageVersion = JavaLanguageVersion.of(17)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasProperty('buildScan')) {
|
||||
|
@ -163,12 +150,3 @@ tasks.register('cloneSamples', IncludeRepoTask) {
|
|||
s101 {
|
||||
configurationDirectory = project.file("etc/s101")
|
||||
}
|
||||
|
||||
wrapperUpgrade {
|
||||
gradle {
|
||||
'spring-security' {
|
||||
repo = 'spring-projects/spring-security'
|
||||
baseBranch = '6.0.x' // runs only on 6.0.x and the update is merged forward to main
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ sourceCompatibility = JavaVersion.VERSION_17
|
|||
repositories {
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
maven { url 'https://repo.spring.io/milestone' }
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2016-2019 the original author or authors.
|
||||
* Copyright 2016-2023 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
|
||||
|
|
|
@ -4,6 +4,7 @@ import org.gradle.api.Action;
|
|||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin;
|
||||
|
||||
|
@ -26,8 +27,12 @@ public class AntoraVersionPlugin implements Plugin<Project> {
|
|||
project.getPlugins().withType(LifecycleBasePlugin.class, new Action<LifecycleBasePlugin>() {
|
||||
@Override
|
||||
public void execute(LifecycleBasePlugin lifecycleBasePlugin) {
|
||||
project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME)
|
||||
.configure(check -> check.dependsOn(antoraCheckVersion));
|
||||
project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME).configure(new Action<Task>() {
|
||||
@Override
|
||||
public void execute(Task check) {
|
||||
check.dependsOn(antoraCheckVersion);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
project.getTasks().register("antoraUpdateVersion", UpdateAntoraVersionTask.class, new Action<UpdateAntoraVersionTask>() {
|
||||
|
|
|
@ -35,7 +35,9 @@ public class CheckClasspathForProhibitedDependenciesPlugin implements Plugin<Pro
|
|||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().apply(CheckProhibitedDependenciesLifecyclePlugin.class);
|
||||
project.getPlugins().withType(JavaBasePlugin.class, javaBasePlugin -> configureProhibitedDependencyChecks(project));
|
||||
project.getPlugins().withType(JavaBasePlugin.class, javaBasePlugin -> {
|
||||
configureProhibitedDependencyChecks(project);
|
||||
});
|
||||
}
|
||||
|
||||
private void configureProhibitedDependencyChecks(Project project) {
|
||||
|
|
|
@ -34,6 +34,8 @@ public class CheckProhibitedDependenciesLifecyclePlugin implements Plugin<Projec
|
|||
task.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
|
||||
task.setDescription("Checks both the compile/runtime classpath of every SourceSet for prohibited dependencies");
|
||||
});
|
||||
project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME, checkTask -> checkTask.dependsOn(checkProhibitedDependencies));
|
||||
project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME, checkTask -> {
|
||||
checkTask.dependsOn(checkProhibitedDependencies);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.gradle.api.Task;
|
|||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.DependencySet;
|
||||
import org.gradle.api.artifacts.repositories.ExclusiveContentRepository;
|
||||
import org.gradle.api.artifacts.repositories.InclusiveRepositoryContentDescriptor;
|
||||
import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
|
||||
import org.gradle.api.artifacts.repositories.IvyPatternRepositoryLayout;
|
||||
import org.gradle.api.tasks.JavaExec;
|
||||
|
@ -90,7 +91,12 @@ public class GitHubChangelogPlugin implements Plugin<Project> {
|
|||
@Override
|
||||
public void execute(ExclusiveContentRepository exclusiveContentRepository) {
|
||||
exclusiveContentRepository.forRepositories(repository);
|
||||
exclusiveContentRepository.filter(descriptor -> descriptor.includeGroup("spring-io"));
|
||||
exclusiveContentRepository.filter(new Action<InclusiveRepositoryContentDescriptor>() {
|
||||
@Override
|
||||
public void execute(InclusiveRepositoryContentDescriptor descriptor) {
|
||||
descriptor.includeGroup("spring-io");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -114,11 +114,11 @@ public final class SpringReleaseTrainSpec {
|
|||
}
|
||||
|
||||
public Builder train(int train) {
|
||||
this.train = switch (train) {
|
||||
case 1 -> Train.ONE;
|
||||
case 2 -> Train.TWO;
|
||||
default -> throw new IllegalArgumentException("Invalid train: " + train);
|
||||
};
|
||||
switch (train) {
|
||||
case 1: this.train = Train.ONE; break;
|
||||
case 2: this.train = Train.TWO; break;
|
||||
default: throw new IllegalArgumentException("Invalid train: " + train);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -156,13 +156,13 @@ public final class SpringReleaseTrainSpec {
|
|||
}
|
||||
|
||||
public Builder weekOfMonth(int weekOfMonth) {
|
||||
this.weekOfMonth = switch (weekOfMonth) {
|
||||
case 1 -> WeekOfMonth.FIRST;
|
||||
case 2 -> WeekOfMonth.SECOND;
|
||||
case 3 -> WeekOfMonth.THIRD;
|
||||
case 4 -> WeekOfMonth.FOURTH;
|
||||
default -> throw new IllegalArgumentException("Invalid weekOfMonth: " + weekOfMonth);
|
||||
};
|
||||
switch (weekOfMonth) {
|
||||
case 1: this.weekOfMonth = WeekOfMonth.FIRST; break;
|
||||
case 2: this.weekOfMonth = WeekOfMonth.SECOND; break;
|
||||
case 3: this.weekOfMonth = WeekOfMonth.THIRD; break;
|
||||
case 4: this.weekOfMonth = WeekOfMonth.FOURTH; break;
|
||||
default: throw new IllegalArgumentException("Invalid weekOfMonth: " + weekOfMonth);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -172,14 +172,14 @@ public final class SpringReleaseTrainSpec {
|
|||
}
|
||||
|
||||
public Builder dayOfWeek(int dayOfWeek) {
|
||||
this.dayOfWeek = switch (dayOfWeek) {
|
||||
case 1 -> DayOfWeek.MONDAY;
|
||||
case 2 -> DayOfWeek.TUESDAY;
|
||||
case 3 -> DayOfWeek.WEDNESDAY;
|
||||
case 4 -> DayOfWeek.THURSDAY;
|
||||
case 5 -> DayOfWeek.FRIDAY;
|
||||
default -> throw new IllegalArgumentException("Invalid dayOfWeek: " + dayOfWeek);
|
||||
};
|
||||
switch (dayOfWeek) {
|
||||
case 1: this.dayOfWeek = DayOfWeek.MONDAY; break;
|
||||
case 2: this.dayOfWeek = DayOfWeek.TUESDAY; break;
|
||||
case 3: this.dayOfWeek = DayOfWeek.WEDNESDAY; break;
|
||||
case 4: this.dayOfWeek = DayOfWeek.THURSDAY; break;
|
||||
case 5: this.dayOfWeek = DayOfWeek.FRIDAY; break;
|
||||
default: throw new IllegalArgumentException("Invalid dayOfWeek: " + dayOfWeek);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.gradle.api.Plugin;
|
|||
import org.gradle.api.Project;
|
||||
import org.gradle.api.publish.Publication;
|
||||
import org.gradle.api.publish.PublishingExtension;
|
||||
import org.gradle.api.publish.plugins.PublishingPlugin;
|
||||
import org.gradle.plugins.signing.SigningExtension;
|
||||
import org.gradle.plugins.signing.SigningPlugin;
|
||||
|
||||
|
@ -43,7 +44,12 @@ public class SpringSigningPlugin implements Plugin<Project> {
|
|||
|
||||
private void sign(Project project) {
|
||||
SigningExtension signing = project.getExtensions().findByType(SigningExtension.class);
|
||||
signing.setRequired((Callable<Boolean>) () -> project.getGradle().getTaskGraph().hasTask("publishArtifacts"));
|
||||
signing.setRequired(new Callable<Boolean>() {
|
||||
@Override
|
||||
public Boolean call() throws Exception {
|
||||
return project.getGradle().getTaskGraph().hasTask("publishArtifacts");
|
||||
}
|
||||
});
|
||||
String signingKeyId = (String) project.findProperty("signingKeyId");
|
||||
String signingKey = (String) project.findProperty("signingKey");
|
||||
String signingPassword = (String) project.findProperty("signingPassword");
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2019-2020 the original author or authors.
|
||||
* Copyright 2019-2023 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.
|
||||
|
|
|
@ -38,7 +38,6 @@ import org.springframework.security.core.Authentication;
|
|||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||
|
@ -196,9 +195,6 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
|
|||
|
||||
private SecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository();
|
||||
|
||||
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
|
||||
.getContextHolderStrategy();
|
||||
|
||||
public CasAuthenticationFilter() {
|
||||
super("/login/cas");
|
||||
setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler());
|
||||
|
@ -215,10 +211,9 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
|
|||
}
|
||||
this.logger.debug(
|
||||
LogMessage.format("Authentication success. Updating SecurityContextHolder to contain: %s", authResult));
|
||||
|
||||
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
|
||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||
context.setAuthentication(authResult);
|
||||
this.securityContextHolderStrategy.setContext(context);
|
||||
SecurityContextHolder.setContext(context);
|
||||
this.securityContextRepository.saveContext(context, request, response);
|
||||
if (this.eventPublisher != null) {
|
||||
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
|
||||
|
|
|
@ -80,6 +80,7 @@ dependencies {
|
|||
testImplementation "org.hibernate.orm:hibernate-core"
|
||||
testImplementation 'org.hsqldb:hsqldb'
|
||||
testImplementation 'org.mockito:mockito-core'
|
||||
testImplementation "org.mockito:mockito-inline"
|
||||
testImplementation('org.seleniumhq.selenium:htmlunit-driver') {
|
||||
exclude group: 'commons-logging', module: 'commons-logging'
|
||||
exclude group: 'xml-apis', module: 'xml-apis'
|
||||
|
|
|
@ -294,7 +294,7 @@ public class RSocketMessageHandlerITests {
|
|||
|
||||
@MessageMapping({ "secure.send", "send" })
|
||||
Mono<Void> send(Mono<String> payload) {
|
||||
return payload.doOnNext(this::add).then(Mono.fromRunnable(this::doNotifyAll));
|
||||
return payload.doOnNext(this::add).then(Mono.fromRunnable(() -> doNotifyAll()));
|
||||
}
|
||||
|
||||
private synchronized void doNotifyAll() {
|
||||
|
|
|
@ -87,7 +87,7 @@ public class LdapUserServiceBeanDefinitionParserTests {
|
|||
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(ben.getAuthorities());
|
||||
assertThat(authorities).hasSize(3);
|
||||
assertThat(authorities).contains("ROLE_DEVELOPERS");
|
||||
assertThat(authorities.contains("ROLE_DEVELOPERS")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -128,7 +128,7 @@ public class LdapUserServiceBeanDefinitionParserTests {
|
|||
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(ben.getAuthorities());
|
||||
assertThat(authorities).hasSize(3);
|
||||
assertThat(authorities).contains("ROLE_DEVELOPER");
|
||||
assertThat(authorities.contains("ROLE_DEVELOPER")).isTrue();
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
|
|||
pc.getReaderContext()
|
||||
.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 "
|
||||
+ "with Spring Security 6.2. Please update your schema declarations to the 6.2 schema.",
|
||||
+ "with Spring Security 6.1. Please update your schema declarations to the 6.1 schema.",
|
||||
element);
|
||||
}
|
||||
String name = pc.getDelegate().getLocalName(element);
|
||||
|
@ -221,7 +221,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
|
|||
|
||||
private boolean matchesVersionInternal(Element element) {
|
||||
String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation");
|
||||
return schemaLocation.matches("(?m).*spring-security-6\\.2.*.xsd.*")
|
||||
return schemaLocation.matches("(?m).*spring-security-6\\.1.*.xsd.*")
|
||||
|| schemaLocation.matches("(?m).*spring-security.xsd.*")
|
||||
|| !schemaLocation.matches("(?m).*spring-security.*");
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2013 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.
|
||||
|
@ -27,7 +27,6 @@ import java.util.Map;
|
|||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.filter.DelegatingFilterProxy;
|
||||
|
@ -118,10 +117,7 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
|
|||
* @param configurer
|
||||
* @return the {@link SecurityConfigurerAdapter} for further customizations
|
||||
* @throws Exception
|
||||
* @deprecated For removal in 7.0. Use
|
||||
* {@link #with(SecurityConfigurerAdapter, Customizer)} instead.
|
||||
*/
|
||||
@Deprecated(since = "6.2", forRemoval = true)
|
||||
@SuppressWarnings("unchecked")
|
||||
public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer) throws Exception {
|
||||
configurer.addObjectPostProcessor(this.objectPostProcessor);
|
||||
|
@ -143,23 +139,6 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
|
|||
return configurer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a {@link SecurityConfigurerAdapter} to this {@link SecurityBuilder} and
|
||||
* invokes {@link SecurityConfigurerAdapter#setBuilder(SecurityBuilder)}.
|
||||
* @param configurer
|
||||
* @return the {@link SecurityBuilder} for further customizations
|
||||
* @throws Exception
|
||||
* @since 6.2
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <C extends SecurityConfigurerAdapter<O, B>> B with(C configurer, Customizer<C> customizer) throws Exception {
|
||||
configurer.addObjectPostProcessor(this.objectPostProcessor);
|
||||
configurer.setBuilder((B) this);
|
||||
add(configurer);
|
||||
customizer.customize(configurer);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an object that is shared by multiple {@link SecurityConfigurer}.
|
||||
* @param sharedType the Class to key the shared object by.
|
||||
|
|
|
@ -41,8 +41,7 @@ final class MethodSecuritySelector implements ImportSelector {
|
|||
|
||||
@Override
|
||||
public String[] selectImports(@NonNull AnnotationMetadata importMetadata) {
|
||||
if (!importMetadata.hasAnnotation(EnableMethodSecurity.class.getName())
|
||||
&& !importMetadata.hasMetaAnnotation(EnableMethodSecurity.class.getName())) {
|
||||
if (!importMetadata.hasAnnotation(EnableMethodSecurity.class.getName())) {
|
||||
return new String[0];
|
||||
}
|
||||
EnableMethodSecurity annotation = importMetadata.getAnnotations().get(EnableMethodSecurity.class).synthesize();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -26,7 +26,6 @@ import java.util.Map;
|
|||
import jakarta.servlet.DispatcherType;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.ServletRegistration;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
@ -204,30 +203,11 @@ public abstract class AbstractRequestMatcherRegistry<C> {
|
|||
if (!hasDispatcherServlet(registrations)) {
|
||||
return requestMatchers(RequestMatchers.antMatchersAsArray(method, patterns));
|
||||
}
|
||||
ServletRegistration dispatcherServlet = requireOneRootDispatcherServlet(registrations);
|
||||
if (dispatcherServlet != null) {
|
||||
if (registrations.size() == 1) {
|
||||
return requestMatchers(createMvcMatchers(method, patterns).toArray(RequestMatcher[]::new));
|
||||
}
|
||||
List<RequestMatcher> matchers = new ArrayList<>();
|
||||
for (String pattern : patterns) {
|
||||
AntPathRequestMatcher ant = new AntPathRequestMatcher(pattern, (method != null) ? method.name() : null);
|
||||
MvcRequestMatcher mvc = createMvcMatchers(method, pattern).get(0);
|
||||
matchers.add(new DispatcherServletDelegatingRequestMatcher(ant, mvc, servletContext));
|
||||
}
|
||||
return requestMatchers(matchers.toArray(new RequestMatcher[0]));
|
||||
if (registrations.size() > 1) {
|
||||
String errorMessage = computeErrorMessage(registrations.values());
|
||||
throw new IllegalArgumentException(errorMessage);
|
||||
}
|
||||
dispatcherServlet = requireOnlyPathMappedDispatcherServlet(registrations);
|
||||
if (dispatcherServlet != null) {
|
||||
String mapping = dispatcherServlet.getMappings().iterator().next();
|
||||
List<MvcRequestMatcher> matchers = createMvcMatchers(method, patterns);
|
||||
for (MvcRequestMatcher matcher : matchers) {
|
||||
matcher.setServletPath(mapping.substring(0, mapping.length() - 2));
|
||||
}
|
||||
return requestMatchers(matchers.toArray(new RequestMatcher[0]));
|
||||
}
|
||||
String errorMessage = computeErrorMessage(registrations.values());
|
||||
throw new IllegalArgumentException(errorMessage);
|
||||
return requestMatchers(createMvcMatchers(method, patterns).toArray(new RequestMatcher[0]));
|
||||
}
|
||||
|
||||
private Map<String, ? extends ServletRegistration> mappableServletRegistrations(ServletContext servletContext) {
|
||||
|
@ -245,66 +225,22 @@ public abstract class AbstractRequestMatcherRegistry<C> {
|
|||
if (registrations == null) {
|
||||
return false;
|
||||
}
|
||||
Class<?> dispatcherServlet = ClassUtils.resolveClassName("org.springframework.web.servlet.DispatcherServlet",
|
||||
null);
|
||||
for (ServletRegistration registration : registrations.values()) {
|
||||
if (isDispatcherServlet(registration)) {
|
||||
return true;
|
||||
try {
|
||||
Class<?> clazz = Class.forName(registration.getClassName());
|
||||
if (dispatcherServlet.isAssignableFrom(clazz)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private ServletRegistration requireOneRootDispatcherServlet(
|
||||
Map<String, ? extends ServletRegistration> registrations) {
|
||||
ServletRegistration rootDispatcherServlet = null;
|
||||
for (ServletRegistration registration : registrations.values()) {
|
||||
if (!isDispatcherServlet(registration)) {
|
||||
continue;
|
||||
}
|
||||
if (registration.getMappings().size() > 1) {
|
||||
return null;
|
||||
}
|
||||
if (!"/".equals(registration.getMappings().iterator().next())) {
|
||||
return null;
|
||||
}
|
||||
rootDispatcherServlet = registration;
|
||||
}
|
||||
return rootDispatcherServlet;
|
||||
}
|
||||
|
||||
private ServletRegistration requireOnlyPathMappedDispatcherServlet(
|
||||
Map<String, ? extends ServletRegistration> registrations) {
|
||||
ServletRegistration pathDispatcherServlet = null;
|
||||
for (ServletRegistration registration : registrations.values()) {
|
||||
if (!isDispatcherServlet(registration)) {
|
||||
return null;
|
||||
}
|
||||
if (registration.getMappings().size() > 1) {
|
||||
return null;
|
||||
}
|
||||
String mapping = registration.getMappings().iterator().next();
|
||||
if (!mapping.startsWith("/") || !mapping.endsWith("/*")) {
|
||||
return null;
|
||||
}
|
||||
if (pathDispatcherServlet != null) {
|
||||
return null;
|
||||
}
|
||||
pathDispatcherServlet = registration;
|
||||
}
|
||||
return pathDispatcherServlet;
|
||||
}
|
||||
|
||||
private boolean isDispatcherServlet(ServletRegistration registration) {
|
||||
Class<?> dispatcherServlet = ClassUtils.resolveClassName("org.springframework.web.servlet.DispatcherServlet",
|
||||
null);
|
||||
try {
|
||||
Class<?> clazz = Class.forName(registration.getClassName());
|
||||
return dispatcherServlet.isAssignableFrom(clazz);
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String computeErrorMessage(Collection<? extends ServletRegistration> registrations) {
|
||||
String template = "This method cannot decide whether these patterns are Spring MVC patterns or not. "
|
||||
+ "If this endpoint is a Spring MVC endpoint, please use requestMatchers(MvcRequestMatcher); "
|
||||
|
@ -444,55 +380,4 @@ public abstract class AbstractRequestMatcherRegistry<C> {
|
|||
|
||||
}
|
||||
|
||||
static class DispatcherServletDelegatingRequestMatcher implements RequestMatcher {
|
||||
|
||||
private final AntPathRequestMatcher ant;
|
||||
|
||||
private final MvcRequestMatcher mvc;
|
||||
|
||||
private final ServletContext servletContext;
|
||||
|
||||
DispatcherServletDelegatingRequestMatcher(AntPathRequestMatcher ant, MvcRequestMatcher mvc,
|
||||
ServletContext servletContext) {
|
||||
this.ant = ant;
|
||||
this.mvc = mvc;
|
||||
this.servletContext = servletContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(HttpServletRequest request) {
|
||||
String name = request.getHttpServletMapping().getServletName();
|
||||
ServletRegistration registration = this.servletContext.getServletRegistration(name);
|
||||
Assert.notNull(registration, "Failed to find servlet [" + name + "] in the servlet context");
|
||||
if (isDispatcherServlet(registration)) {
|
||||
return this.mvc.matches(request);
|
||||
}
|
||||
return this.ant.matches(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MatchResult matcher(HttpServletRequest request) {
|
||||
String name = request.getHttpServletMapping().getServletName();
|
||||
ServletRegistration registration = this.servletContext.getServletRegistration(name);
|
||||
Assert.notNull(registration, "Failed to find servlet [" + name + "] in the servlet context");
|
||||
if (isDispatcherServlet(registration)) {
|
||||
return this.mvc.matcher(request);
|
||||
}
|
||||
return this.ant.matcher(request);
|
||||
}
|
||||
|
||||
private boolean isDispatcherServlet(ServletRegistration registration) {
|
||||
Class<?> dispatcherServlet = ClassUtils
|
||||
.resolveClassName("org.springframework.web.servlet.DispatcherServlet", null);
|
||||
try {
|
||||
Class<?> clazz = Class.forName(registration.getClassName());
|
||||
return dispatcherServlet.isAssignableFrom(clazz);
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -70,7 +70,6 @@ import org.springframework.security.config.annotation.web.configurers.SessionMan
|
|||
import org.springframework.security.config.annotation.web.configurers.X509Configurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2ClientConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OidcLogoutConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.saml2.Saml2LoginConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.saml2.Saml2LogoutConfigurer;
|
||||
|
@ -2836,16 +2835,6 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
|||
return HttpSecurity.this;
|
||||
}
|
||||
|
||||
public OidcLogoutConfigurer<HttpSecurity> oidcLogout() throws Exception {
|
||||
return getOrApply(new OidcLogoutConfigurer<>());
|
||||
}
|
||||
|
||||
public HttpSecurity oidcLogout(Customizer<OidcLogoutConfigurer<HttpSecurity>> oidcLogoutCustomizer)
|
||||
throws Exception {
|
||||
oidcLogoutCustomizer.customize(getOrApply(new OidcLogoutConfigurer<>()));
|
||||
return HttpSecurity.this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures OAuth 2.0 Client support.
|
||||
* @return the {@link OAuth2ClientConfigurer} for further customizations
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -47,7 +47,6 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
|||
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
|
||||
import org.springframework.web.accept.ContentNegotiationStrategy;
|
||||
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
|
@ -125,18 +124,10 @@ class HttpSecurityConfiguration {
|
|||
.apply(new DefaultLoginPageConfigurer<>());
|
||||
http.logout(withDefaults());
|
||||
// @formatter:on
|
||||
applyCorsIfAvailable(http);
|
||||
applyDefaultConfigurers(http);
|
||||
return http;
|
||||
}
|
||||
|
||||
private void applyCorsIfAvailable(HttpSecurity http) throws Exception {
|
||||
String[] beanNames = this.context.getBeanNamesForType(CorsConfigurationSource.class);
|
||||
if (beanNames.length == 1) {
|
||||
http.cors(withDefaults());
|
||||
}
|
||||
}
|
||||
|
||||
private AuthenticationManager authenticationManager() throws Exception {
|
||||
return this.authenticationConfiguration.getAuthenticationManager();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -16,46 +16,19 @@
|
|||
|
||||
package org.springframework.security.config.annotation.web.configuration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.BeanInitializationException;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
||||
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.ImportSelector;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||
import org.springframework.security.oauth2.client.AuthorizationCodeOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.DelegatingOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.JwtBearerOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||
|
@ -75,8 +48,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|||
* @since 5.1
|
||||
* @see OAuth2ImportSelector
|
||||
*/
|
||||
@Import({ OAuth2ClientConfiguration.OAuth2ClientWebMvcImportSelector.class,
|
||||
OAuth2ClientConfiguration.OAuth2AuthorizedClientManagerConfiguration.class })
|
||||
@Import(OAuth2ClientConfiguration.OAuth2ClientWebMvcImportSelector.class)
|
||||
final class OAuth2ClientConfiguration {
|
||||
|
||||
private static final boolean webMvcPresent;
|
||||
|
@ -93,22 +65,8 @@ final class OAuth2ClientConfiguration {
|
|||
if (!webMvcPresent) {
|
||||
return new String[0];
|
||||
}
|
||||
return new String[] {
|
||||
OAuth2ClientConfiguration.class.getName() + ".OAuth2ClientWebMvcSecurityConfiguration" };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
* @since 6.2.0
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class OAuth2AuthorizedClientManagerConfiguration {
|
||||
|
||||
@Bean
|
||||
OAuth2AuthorizedClientManagerRegistrar authorizedClientManagerRegistrar() {
|
||||
return new OAuth2AuthorizedClientManagerRegistrar();
|
||||
return new String[] { "org.springframework.security.config.annotation.web.configuration."
|
||||
+ "OAuth2ClientConfiguration.OAuth2ClientWebMvcSecurityConfiguration" };
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -116,12 +74,16 @@ final class OAuth2ClientConfiguration {
|
|||
@Configuration(proxyBeanMethods = false)
|
||||
static class OAuth2ClientWebMvcSecurityConfiguration implements WebMvcConfigurer {
|
||||
|
||||
private ClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
private OAuth2AuthorizedClientRepository authorizedClientRepository;
|
||||
|
||||
private OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient;
|
||||
|
||||
private OAuth2AuthorizedClientManager authorizedClientManager;
|
||||
|
||||
private SecurityContextHolderStrategy securityContextHolderStrategy;
|
||||
|
||||
private OAuth2AuthorizedClientManagerRegistrar authorizedClientManagerRegistrar;
|
||||
|
||||
@Override
|
||||
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
|
||||
OAuth2AuthorizedClientManager authorizedClientManager = getAuthorizedClientManager();
|
||||
|
@ -135,6 +97,26 @@ final class OAuth2ClientConfiguration {
|
|||
}
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
void setClientRegistrationRepository(List<ClientRegistrationRepository> clientRegistrationRepositories) {
|
||||
if (clientRegistrationRepositories.size() == 1) {
|
||||
this.clientRegistrationRepository = clientRegistrationRepositories.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
void setAuthorizedClientRepository(List<OAuth2AuthorizedClientRepository> authorizedClientRepositories) {
|
||||
if (authorizedClientRepositories.size() == 1) {
|
||||
this.authorizedClientRepository = authorizedClientRepositories.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
void setAccessTokenResponseClient(
|
||||
OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient) {
|
||||
this.accessTokenResponseClient = accessTokenResponseClient;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
void setAuthorizedClientManager(List<OAuth2AuthorizedClientManager> authorizedClientManagers) {
|
||||
if (authorizedClientManagers.size() == 1) {
|
||||
|
@ -147,262 +129,35 @@ final class OAuth2ClientConfiguration {
|
|||
this.securityContextHolderStrategy = strategy;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
void setAuthorizedClientManagerRegistrar(
|
||||
OAuth2AuthorizedClientManagerRegistrar authorizedClientManagerRegistrar) {
|
||||
this.authorizedClientManagerRegistrar = authorizedClientManagerRegistrar;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientManager getAuthorizedClientManager() {
|
||||
if (this.authorizedClientManager != null) {
|
||||
return this.authorizedClientManager;
|
||||
}
|
||||
return this.authorizedClientManagerRegistrar.getAuthorizedClientManagerIfAvailable();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A registrar for registering the default {@link OAuth2AuthorizedClientManager} bean
|
||||
* definition, if not already present.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Steve Riesenberg
|
||||
* @since 6.2.0
|
||||
*/
|
||||
static final class OAuth2AuthorizedClientManagerRegistrar
|
||||
implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware {
|
||||
|
||||
// @formatter:off
|
||||
private static final Set<Class<?>> KNOWN_AUTHORIZED_CLIENT_PROVIDERS = Set.of(
|
||||
AuthorizationCodeOAuth2AuthorizedClientProvider.class,
|
||||
RefreshTokenOAuth2AuthorizedClientProvider.class,
|
||||
ClientCredentialsOAuth2AuthorizedClientProvider.class,
|
||||
PasswordOAuth2AuthorizedClientProvider.class,
|
||||
JwtBearerOAuth2AuthorizedClientProvider.class
|
||||
);
|
||||
// @formatter:on
|
||||
|
||||
private final AnnotationBeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
|
||||
|
||||
private ListableBeanFactory beanFactory;
|
||||
|
||||
@Override
|
||||
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
|
||||
if (getBeanNamesForType(OAuth2AuthorizedClientManager.class).length != 0
|
||||
|| getBeanNamesForType(ClientRegistrationRepository.class).length != 1
|
||||
|| getBeanNamesForType(OAuth2AuthorizedClientRepository.class).length != 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
BeanDefinition beanDefinition = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(OAuth2AuthorizedClientManager.class, this::getAuthorizedClientManager)
|
||||
.getBeanDefinition();
|
||||
|
||||
registry.registerBeanDefinition(this.beanNameGenerator.generateBeanName(beanDefinition, registry),
|
||||
beanDefinition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = (ListableBeanFactory) beanFactory;
|
||||
}
|
||||
|
||||
OAuth2AuthorizedClientManager getAuthorizedClientManagerIfAvailable() {
|
||||
if (getBeanNamesForType(ClientRegistrationRepository.class).length != 1
|
||||
|| getBeanNamesForType(OAuth2AuthorizedClientRepository.class).length != 1) {
|
||||
return null;
|
||||
}
|
||||
return getAuthorizedClientManager();
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientManager getAuthorizedClientManager() {
|
||||
ClientRegistrationRepository clientRegistrationRepository = BeanFactoryUtils
|
||||
.beanOfTypeIncludingAncestors(this.beanFactory, ClientRegistrationRepository.class, true, true);
|
||||
|
||||
OAuth2AuthorizedClientRepository authorizedClientRepository = BeanFactoryUtils
|
||||
.beanOfTypeIncludingAncestors(this.beanFactory, OAuth2AuthorizedClientRepository.class, true, true);
|
||||
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviderBeans = BeanFactoryUtils
|
||||
.beansOfTypeIncludingAncestors(this.beanFactory, OAuth2AuthorizedClientProvider.class, true, true)
|
||||
.values();
|
||||
|
||||
OAuth2AuthorizedClientProvider authorizedClientProvider;
|
||||
if (hasDelegatingAuthorizedClientProvider(authorizedClientProviderBeans)) {
|
||||
authorizedClientProvider = authorizedClientProviderBeans.iterator().next();
|
||||
}
|
||||
else {
|
||||
List<OAuth2AuthorizedClientProvider> authorizedClientProviders = new ArrayList<>();
|
||||
authorizedClientProviders
|
||||
.add(getAuthorizationCodeAuthorizedClientProvider(authorizedClientProviderBeans));
|
||||
authorizedClientProviders.add(getRefreshTokenAuthorizedClientProvider(authorizedClientProviderBeans));
|
||||
authorizedClientProviders
|
||||
.add(getClientCredentialsAuthorizedClientProvider(authorizedClientProviderBeans));
|
||||
authorizedClientProviders.add(getPasswordAuthorizedClientProvider(authorizedClientProviderBeans));
|
||||
|
||||
OAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider = getJwtBearerAuthorizedClientProvider(
|
||||
authorizedClientProviderBeans);
|
||||
if (jwtBearerAuthorizedClientProvider != null) {
|
||||
authorizedClientProviders.add(jwtBearerAuthorizedClientProvider);
|
||||
OAuth2AuthorizedClientManager authorizedClientManager = null;
|
||||
if (this.clientRegistrationRepository != null && this.authorizedClientRepository != null) {
|
||||
if (this.accessTokenResponseClient != null) {
|
||||
// @formatter:off
|
||||
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder
|
||||
.builder()
|
||||
.authorizationCode()
|
||||
.refreshToken()
|
||||
.clientCredentials((configurer) -> configurer.accessTokenResponseClient(this.accessTokenResponseClient))
|
||||
.password()
|
||||
.build();
|
||||
// @formatter:on
|
||||
DefaultOAuth2AuthorizedClientManager defaultAuthorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
|
||||
this.clientRegistrationRepository, this.authorizedClientRepository);
|
||||
defaultAuthorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
authorizedClientManager = defaultAuthorizedClientManager;
|
||||
}
|
||||
else {
|
||||
authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
|
||||
this.clientRegistrationRepository, this.authorizedClientRepository);
|
||||
}
|
||||
|
||||
authorizedClientProviders.addAll(getAdditionalAuthorizedClientProviders(authorizedClientProviderBeans));
|
||||
authorizedClientProvider = new DelegatingOAuth2AuthorizedClientProvider(authorizedClientProviders);
|
||||
}
|
||||
|
||||
DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository);
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
|
||||
Consumer<DefaultOAuth2AuthorizedClientManager> authorizedClientManagerConsumer = getBeanOfType(
|
||||
ResolvableType.forClassWithGenerics(Consumer.class, DefaultOAuth2AuthorizedClientManager.class));
|
||||
if (authorizedClientManagerConsumer != null) {
|
||||
authorizedClientManagerConsumer.accept(authorizedClientManager);
|
||||
}
|
||||
|
||||
return authorizedClientManager;
|
||||
}
|
||||
|
||||
private boolean hasDelegatingAuthorizedClientProvider(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
if (authorizedClientProviders.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
return authorizedClientProviders.iterator().next() instanceof DelegatingOAuth2AuthorizedClientProvider;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientProvider getAuthorizationCodeAuthorizedClientProvider(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
AuthorizationCodeOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
|
||||
authorizedClientProviders, AuthorizationCodeOAuth2AuthorizedClientProvider.class);
|
||||
if (authorizedClientProvider == null) {
|
||||
authorizedClientProvider = new AuthorizationCodeOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientProvider getRefreshTokenAuthorizedClientProvider(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
RefreshTokenOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
|
||||
authorizedClientProviders, RefreshTokenOAuth2AuthorizedClientProvider.class);
|
||||
if (authorizedClientProvider == null) {
|
||||
authorizedClientProvider = new RefreshTokenOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
|
||||
OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient = getBeanOfType(
|
||||
ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
|
||||
OAuth2RefreshTokenGrantRequest.class));
|
||||
if (accessTokenResponseClient != null) {
|
||||
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
|
||||
}
|
||||
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientProvider getClientCredentialsAuthorizedClientProvider(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
ClientCredentialsOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
|
||||
authorizedClientProviders, ClientCredentialsOAuth2AuthorizedClientProvider.class);
|
||||
if (authorizedClientProvider == null) {
|
||||
authorizedClientProvider = new ClientCredentialsOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
|
||||
OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient = getBeanOfType(
|
||||
ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
|
||||
OAuth2ClientCredentialsGrantRequest.class));
|
||||
if (accessTokenResponseClient != null) {
|
||||
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
|
||||
}
|
||||
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientProvider getPasswordAuthorizedClientProvider(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
PasswordOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
|
||||
authorizedClientProviders, PasswordOAuth2AuthorizedClientProvider.class);
|
||||
if (authorizedClientProvider == null) {
|
||||
authorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
|
||||
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient = getBeanOfType(
|
||||
ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
|
||||
OAuth2PasswordGrantRequest.class));
|
||||
if (accessTokenResponseClient != null) {
|
||||
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
|
||||
}
|
||||
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientProvider getJwtBearerAuthorizedClientProvider(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
|
||||
authorizedClientProviders, JwtBearerOAuth2AuthorizedClientProvider.class);
|
||||
|
||||
OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> accessTokenResponseClient = getBeanOfType(
|
||||
ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
|
||||
JwtBearerGrantRequest.class));
|
||||
if (accessTokenResponseClient != null) {
|
||||
if (authorizedClientProvider == null) {
|
||||
authorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
|
||||
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
|
||||
}
|
||||
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private List<OAuth2AuthorizedClientProvider> getAdditionalAuthorizedClientProviders(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
List<OAuth2AuthorizedClientProvider> additionalAuthorizedClientProviders = new ArrayList<>(
|
||||
authorizedClientProviders);
|
||||
additionalAuthorizedClientProviders
|
||||
.removeIf((provider) -> KNOWN_AUTHORIZED_CLIENT_PROVIDERS.contains(provider.getClass()));
|
||||
return additionalAuthorizedClientProviders;
|
||||
}
|
||||
|
||||
private <T extends OAuth2AuthorizedClientProvider> T getAuthorizedClientProviderByType(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders, Class<T> providerClass) {
|
||||
T authorizedClientProvider = null;
|
||||
for (OAuth2AuthorizedClientProvider current : authorizedClientProviders) {
|
||||
if (providerClass.isInstance(current)) {
|
||||
assertAuthorizedClientProviderIsNull(authorizedClientProvider);
|
||||
authorizedClientProvider = providerClass.cast(current);
|
||||
}
|
||||
}
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private static void assertAuthorizedClientProviderIsNull(
|
||||
OAuth2AuthorizedClientProvider authorizedClientProvider) {
|
||||
if (authorizedClientProvider != null) {
|
||||
// @formatter:off
|
||||
throw new BeanInitializationException(String.format(
|
||||
"Unable to create an %s bean. Expected one bean of type %s, but found multiple. " +
|
||||
"Please consider defining only a single bean of this type, or define an %s bean yourself.",
|
||||
OAuth2AuthorizedClientManager.class.getName(),
|
||||
authorizedClientProvider.getClass().getName(),
|
||||
OAuth2AuthorizedClientManager.class.getName()));
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
private <T> String[] getBeanNamesForType(Class<T> beanClass) {
|
||||
return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, beanClass, true, true);
|
||||
}
|
||||
|
||||
private <T> T getBeanOfType(ResolvableType resolvableType) {
|
||||
ObjectProvider<T> objectProvider = this.beanFactory.getBeanProvider(resolvableType, true);
|
||||
return objectProvider.getIfAvailable();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
abstract class AbstractRequestMatcherBuilderRegistry<C> extends AbstractRequestMatcherRegistry<C> {
|
||||
|
||||
private final RequestMatcherBuilder builder;
|
||||
|
||||
AbstractRequestMatcherBuilderRegistry(ApplicationContext context) {
|
||||
this(context, RequestMatcherBuilders.createDefault(context));
|
||||
}
|
||||
|
||||
AbstractRequestMatcherBuilderRegistry(ApplicationContext context, RequestMatcherBuilder builder) {
|
||||
setApplicationContext(context);
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final C requestMatchers(String... patterns) {
|
||||
return requestMatchers(null, patterns);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final C requestMatchers(HttpMethod method, String... patterns) {
|
||||
return requestMatchers(this.builder.matchers(method, patterns).toArray(RequestMatcher[]::new));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final C requestMatchers(HttpMethod method) {
|
||||
return requestMatchers(method, "/**");
|
||||
}
|
||||
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
|
||||
final class AntPathRequestMatcherBuilder implements RequestMatcherBuilder {
|
||||
|
||||
private final String servletPath;
|
||||
|
||||
private AntPathRequestMatcherBuilder(String servletPath) {
|
||||
this.servletPath = servletPath;
|
||||
}
|
||||
|
||||
static AntPathRequestMatcherBuilder absolute() {
|
||||
return new AntPathRequestMatcherBuilder(null);
|
||||
}
|
||||
|
||||
static AntPathRequestMatcherBuilder relativeTo(String path) {
|
||||
return new AntPathRequestMatcherBuilder(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AntPathRequestMatcher matcher(String pattern) {
|
||||
return matcher((String) null, pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AntPathRequestMatcher matcher(HttpMethod method, String pattern) {
|
||||
return matcher((method != null) ? method.name() : null, pattern);
|
||||
}
|
||||
|
||||
private AntPathRequestMatcher matcher(String method, String pattern) {
|
||||
return new AntPathRequestMatcher(prependServletPath(pattern), method);
|
||||
}
|
||||
|
||||
private String prependServletPath(String pattern) {
|
||||
if (this.servletPath == null) {
|
||||
return pattern;
|
||||
}
|
||||
return this.servletPath + pattern;
|
||||
}
|
||||
|
||||
}
|
|
@ -16,14 +16,10 @@
|
|||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import jakarta.servlet.http.HttpServletMapping;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
@ -36,22 +32,16 @@ import org.springframework.security.authorization.AuthorizationEventPublisher;
|
|||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.authorization.ObservationAuthorizationManager;
|
||||
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.core.GrantedAuthorityDefaults;
|
||||
import org.springframework.security.web.access.intercept.AuthorizationFilter;
|
||||
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
|
||||
import org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager;
|
||||
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcherEntry;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.function.SingletonSupplier;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
/**
|
||||
* Adds a URL based authorization using {@link AuthorizationManager}.
|
||||
|
@ -72,8 +62,6 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
|
||||
private final Supplier<RoleHierarchy> roleHierarchy;
|
||||
|
||||
private String rolePrefix = "ROLE_";
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
* @param context the {@link ApplicationContext} to use
|
||||
|
@ -88,11 +76,6 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
}
|
||||
this.roleHierarchy = SingletonSupplier.of(() -> (context.getBeanNamesForType(RoleHierarchy.class).length > 0)
|
||||
? context.getBean(RoleHierarchy.class) : new NullRoleHierarchy());
|
||||
String[] grantedAuthorityDefaultsBeanNames = context.getBeanNamesForType(GrantedAuthorityDefaults.class);
|
||||
if (grantedAuthorityDefaultsBeanNames.length > 0) {
|
||||
GrantedAuthorityDefaults grantedAuthorityDefaults = context.getBean(GrantedAuthorityDefaults.class);
|
||||
this.rolePrefix = grantedAuthorityDefaults.getRolePrefix();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -146,62 +129,41 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
* @author Evgeniy Cheban
|
||||
*/
|
||||
public final class AuthorizationManagerRequestMatcherRegistry
|
||||
extends AbstractRequestMatcherBuilderRegistry<AuthorizedUrl> {
|
||||
extends AbstractRequestMatcherRegistry<AuthorizedUrl> {
|
||||
|
||||
private final RequestMatcherDelegatingAuthorizationManager.Builder managerBuilder = RequestMatcherDelegatingAuthorizationManager
|
||||
.builder();
|
||||
|
||||
List<RequestMatcher> unmappedMatchers;
|
||||
private List<RequestMatcher> unmappedMatchers;
|
||||
|
||||
private int mappingCount;
|
||||
|
||||
private boolean shouldFilterAllDispatcherTypes = true;
|
||||
|
||||
private final Map<String, AuthorizationManagerServletRequestMatcherRegistry> servletPattern = new LinkedHashMap<>();
|
||||
|
||||
AuthorizationManagerRequestMatcherRegistry(ApplicationContext context) {
|
||||
super(context);
|
||||
private AuthorizationManagerRequestMatcherRegistry(ApplicationContext context) {
|
||||
setApplicationContext(context);
|
||||
}
|
||||
|
||||
private void addMapping(RequestMatcher matcher, AuthorizationManager<RequestAuthorizationContext> manager) {
|
||||
Assert.isTrue(this.servletPattern.isEmpty(),
|
||||
"Since you have used forServletPattern, all request matchers must be configured using forServletPattern; alternatively, you can use requestMatchers(RequestMatcher) for all requests.");
|
||||
this.unmappedMatchers = null;
|
||||
this.managerBuilder.add(matcher, manager);
|
||||
this.mappingCount++;
|
||||
}
|
||||
|
||||
private void addFirst(RequestMatcher matcher, AuthorizationManager<RequestAuthorizationContext> manager) {
|
||||
Assert.isTrue(this.servletPattern.isEmpty(),
|
||||
"Since you have used forServletPattern, all request matchers must be configured using forServletPattern; alternatively, you can use requestMatchers(RequestMatcher) for all requests.");
|
||||
this.unmappedMatchers = null;
|
||||
this.managerBuilder.mappings((m) -> m.add(0, new RequestMatcherEntry<>(matcher, manager)));
|
||||
this.mappingCount++;
|
||||
}
|
||||
|
||||
private AuthorizationManager<HttpServletRequest> servletAuthorizationManager() {
|
||||
for (Map.Entry<String, AuthorizationManagerServletRequestMatcherRegistry> entry : this.servletPattern
|
||||
.entrySet()) {
|
||||
AuthorizationManagerServletRequestMatcherRegistry registry = entry.getValue();
|
||||
this.managerBuilder.add(new ServletPatternRequestMatcher(entry.getKey()),
|
||||
registry.authorizationManager());
|
||||
}
|
||||
return postProcess(this.managerBuilder.build());
|
||||
}
|
||||
|
||||
private AuthorizationManager<HttpServletRequest> authorizationManager() {
|
||||
private AuthorizationManager<HttpServletRequest> createAuthorizationManager() {
|
||||
Assert.state(this.unmappedMatchers == null,
|
||||
() -> "An incomplete mapping was found for " + this.unmappedMatchers
|
||||
+ ". Try completing it with something like requestUrls().<something>.hasRole('USER')");
|
||||
Assert.state(this.mappingCount > 0,
|
||||
"At least one mapping is required (for example, authorizeHttpRequests().anyRequest().authenticated())");
|
||||
return postProcess(this.managerBuilder.build());
|
||||
}
|
||||
|
||||
private AuthorizationManager<HttpServletRequest> createAuthorizationManager() {
|
||||
AuthorizationManager<HttpServletRequest> manager = (this.servletPattern.isEmpty()) ? authorizationManager()
|
||||
: servletAuthorizationManager();
|
||||
ObservationRegistry registry = getObservationRegistry();
|
||||
RequestMatcherDelegatingAuthorizationManager manager = postProcess(this.managerBuilder.build());
|
||||
if (registry.isNoop()) {
|
||||
return manager;
|
||||
}
|
||||
|
@ -211,74 +173,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
@Override
|
||||
protected AuthorizedUrl chainRequestMatchers(List<RequestMatcher> requestMatchers) {
|
||||
this.unmappedMatchers = requestMatchers;
|
||||
return new AuthorizedUrl(
|
||||
(manager) -> AuthorizeHttpRequestsConfigurer.this.addMapping(requestMatchers, manager));
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin registering {@link RequestMatcher}s based on the type of the servlet
|
||||
* mapped to {@code pattern}. Each registered request matcher will additionally
|
||||
* check {@link HttpServletMapping#getPattern} against the provided
|
||||
* {@code pattern}.
|
||||
*
|
||||
* <p>
|
||||
* If the corresponding servlet is of type {@link DispatcherServlet}, then use a
|
||||
* {@link AuthorizationManagerServletRequestMatcherRegistry} that registers
|
||||
* {@link MvcRequestMatcher}s.
|
||||
*
|
||||
* <p>
|
||||
* Otherwise, use a configurer that registers {@link AntPathRequestMatcher}s.
|
||||
*
|
||||
* <p>
|
||||
* When doing a path-based pattern, like `/path/*`, registered URIs should leave
|
||||
* out the matching path. For example, if the target URI is `/path/resource/3`,
|
||||
* then the configuration should look like this: <code>
|
||||
* .forServletPattern("/path/*", (path) -> path
|
||||
* .requestMatchers("/resource/3").hasAuthority(...)
|
||||
* )
|
||||
* </code>
|
||||
*
|
||||
* <p>
|
||||
* Or, if the pattern is `/path/subpath/*`, and the URI is
|
||||
* `/path/subpath/resource/3`, then the configuration should look like this:
|
||||
* <code>
|
||||
* .forServletPattern("/path/subpath/*", (path) -> path
|
||||
* .requestMatchers("/resource/3").hasAuthority(...)
|
||||
* )
|
||||
* </code>
|
||||
*
|
||||
* <p>
|
||||
* For all other patterns, please supply the URI in absolute terms. For example,
|
||||
* if the target URI is `/js/**` and it matches to the default servlet, then the
|
||||
* configuration should look like this: <code>
|
||||
* .forServletPattern("/", (root) -> root
|
||||
* .requestMatchers("/js/**").hasAuthority(...)
|
||||
* )
|
||||
* </code>
|
||||
*
|
||||
* <p>
|
||||
* Or, if the target URI is `/views/**`, and it matches to a `*.jsp` extension
|
||||
* servlet, then the configuration should look like this: <code>
|
||||
* .forServletPattern("*.jsp", (jsp) -> jsp
|
||||
* .requestMatchers("/views/**").hasAuthority(...)
|
||||
* )
|
||||
* </code>
|
||||
* @param customizer a customizer that uses a
|
||||
* {@link AuthorizationManagerServletRequestMatcherRegistry} for URIs mapped to
|
||||
* the provided servlet
|
||||
* @return an {@link AuthorizationManagerServletRequestMatcherRegistry} for
|
||||
* further configurations
|
||||
* @since 6.2
|
||||
*/
|
||||
public AuthorizationManagerRequestMatcherRegistry forServletPattern(String pattern,
|
||||
Customizer<AuthorizationManagerServletRequestMatcherRegistry> customizer) {
|
||||
ApplicationContext context = getApplicationContext();
|
||||
RequestMatcherBuilder builder = RequestMatcherBuilders.createForServletPattern(context, pattern);
|
||||
AuthorizationManagerServletRequestMatcherRegistry registry = new AuthorizationManagerServletRequestMatcherRegistry(
|
||||
builder);
|
||||
customizer.customize(registry);
|
||||
this.servletPattern.put(pattern, registry);
|
||||
return this;
|
||||
return new AuthorizedUrl(requestMatchers);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -334,265 +229,6 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
return AuthorizeHttpRequestsConfigurer.this.and();
|
||||
}
|
||||
|
||||
/**
|
||||
* A decorator class for registering {@link RequestMatcher} instances based on the
|
||||
* type of servlet. If the servlet is {@link DispatcherServlet}, then it will use
|
||||
* a {@link MvcRequestMatcher}; otherwise, it will use a
|
||||
* {@link AntPathRequestMatcher}.
|
||||
*
|
||||
* <p>
|
||||
* This class is designed primarily for use with the {@link HttpSecurity} DSL. For
|
||||
* that reason, please use {@link HttpSecurity#authorizeHttpRequests} instead as
|
||||
* it exposes this class fluently alongside related DSL configurations.
|
||||
*
|
||||
* <p>
|
||||
* NOTE: In many cases, which kind of request matcher is needed is apparent by the
|
||||
* servlet configuration, and so you should generally use the methods found in
|
||||
* {@link AbstractRequestMatcherRegistry} instead of this these. Use this class
|
||||
* when you want or need to indicate which request matcher URIs belong to which
|
||||
* servlet.
|
||||
*
|
||||
* <p>
|
||||
* In all cases, though, you may arrange your request matchers by servlet pattern
|
||||
* with the {@link AuthorizationManagerRequestMatcherRegistry#forServletPattern}
|
||||
* method in the {@link HttpSecurity#authorizeHttpRequests} DSL.
|
||||
*
|
||||
* <p>
|
||||
* Consider, for example, the circumstance where you have Spring MVC configured
|
||||
* and also Spring Boot H2 Console. Spring MVC registers a servlet of type
|
||||
* {@link DispatcherServlet} as the default servlet and Spring Boot registers a
|
||||
* servlet of its own as well at `/h2-console/*`.
|
||||
*
|
||||
* <p>
|
||||
* Such might have a configuration like this in Spring Security: <code>
|
||||
* http
|
||||
* .authorizeHttpRequests((authorize) -> authorize
|
||||
* .requestMatchers("/js/**", "/css/**").permitAll()
|
||||
* .requestMatchers("/my/controller/**").hasAuthority("CONTROLLER")
|
||||
* .requestMatchers("/h2-console/**").hasAuthority("H2")
|
||||
* )
|
||||
* // ...
|
||||
* </code>
|
||||
*
|
||||
* <p>
|
||||
* Spring Security by default addresses the above configuration on its own.
|
||||
*
|
||||
* <p>
|
||||
* However, consider the same situation, but where {@link DispatcherServlet} is
|
||||
* mapped to a path like `/mvc/*`. In this case, the above configuration is
|
||||
* ambiguous, and you should use this class to clarify the rest of each MVC URI
|
||||
* like so: <code>
|
||||
* http
|
||||
* .authorizeHttpRequests((authorize) -> authorize
|
||||
* .forServletPattern("/", (root) -> root
|
||||
* .requestMatchers("/js/**", "/css/**").permitAll()
|
||||
* )
|
||||
* .forServletPattern("/mvc/*", (mvc) -> mvc
|
||||
* .requestMatchers("/my/controller/**").hasAuthority("CONTROLLER")
|
||||
* )
|
||||
* .forServletPattern("/h2-console/*", (h2) -> h2
|
||||
* .anyRequest().hasAuthority("OTHER")
|
||||
* )
|
||||
* )
|
||||
* // ...
|
||||
* </code>
|
||||
*
|
||||
* <p>
|
||||
* In the above configuration, it's now clear to Spring Security that the
|
||||
* following matchers map to these corresponding URIs:
|
||||
*
|
||||
* <ul>
|
||||
* <li><default> + <strong>`/js/**`</strong> ==> `/js/**`</li>
|
||||
* <li><default> + <strong>`/css/**`</strong> ==> `/css/**`</li>
|
||||
* <li>`/mvc` + <strong>`/my/controller/**`</strong> ==>
|
||||
* `/mvc/my/controller/**`</li>
|
||||
* <li>`/h2-console` + <strong><any request></strong> ==>
|
||||
* `/h2-console/**`</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
* @see AbstractRequestMatcherRegistry
|
||||
* @see AuthorizeHttpRequestsConfigurer
|
||||
*/
|
||||
public final class AuthorizationManagerServletRequestMatcherRegistry
|
||||
extends AbstractRequestMatcherBuilderRegistry<ServletAuthorizedUrl> {
|
||||
|
||||
private final RequestMatcherDelegatingAuthorizationManager.Builder managerBuilder = RequestMatcherDelegatingAuthorizationManager
|
||||
.builder();
|
||||
|
||||
private List<RequestMatcher> unmappedMatchers;
|
||||
|
||||
AuthorizationManagerServletRequestMatcherRegistry(RequestMatcherBuilder builder) {
|
||||
super(AuthorizationManagerRequestMatcherRegistry.this.getApplicationContext(), builder);
|
||||
}
|
||||
|
||||
AuthorizationManager<RequestAuthorizationContext> authorizationManager() {
|
||||
Assert.state(this.unmappedMatchers == null,
|
||||
() -> "An incomplete mapping was found for " + this.unmappedMatchers
|
||||
+ ". Try completing it with something like requestUrls().<something>.hasRole('USER')");
|
||||
AuthorizationManager<HttpServletRequest> request = this.managerBuilder.build();
|
||||
return (authentication, context) -> request.check(authentication, context.getRequest());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServletAuthorizedUrl chainRequestMatchers(List<RequestMatcher> requestMatchers) {
|
||||
this.unmappedMatchers = requestMatchers;
|
||||
return new ServletAuthorizedUrl((manager) -> addMapping(requestMatchers, manager));
|
||||
}
|
||||
|
||||
private AuthorizationManagerServletRequestMatcherRegistry addMapping(List<RequestMatcher> matchers,
|
||||
AuthorizationManager<RequestAuthorizationContext> manager) {
|
||||
this.unmappedMatchers = null;
|
||||
for (RequestMatcher matcher : matchers) {
|
||||
this.managerBuilder.add(matcher, manager);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* An object that allows configuring the {@link AuthorizationManager} for
|
||||
* {@link RequestMatcher}s.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
*/
|
||||
public final class ServletAuthorizedUrl {
|
||||
|
||||
private final Function<AuthorizationManager<RequestAuthorizationContext>, AuthorizationManagerServletRequestMatcherRegistry> registrar;
|
||||
|
||||
ServletAuthorizedUrl(
|
||||
Function<AuthorizationManager<RequestAuthorizationContext>, AuthorizationManagerServletRequestMatcherRegistry> registrar) {
|
||||
this.registrar = registrar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that URLs are allowed by anyone.
|
||||
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customizations
|
||||
*/
|
||||
public AuthorizationManagerServletRequestMatcherRegistry permitAll() {
|
||||
return access(permitAllAuthorizationManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that URLs are not allowed by anyone.
|
||||
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customizations
|
||||
*/
|
||||
public AuthorizationManagerServletRequestMatcherRegistry denyAll() {
|
||||
return access((a, o) -> new AuthorizationDecision(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a user requires a role.
|
||||
* @param role the role that should be required which is prepended with ROLE_
|
||||
* automatically (i.e. USER, ADMIN, etc). It should not start with ROLE_
|
||||
* @return {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customizations
|
||||
*/
|
||||
public AuthorizationManagerServletRequestMatcherRegistry hasRole(String role) {
|
||||
return access(withRoleHierarchy(AuthorityAuthorizationManager
|
||||
.hasAnyRole(AuthorizeHttpRequestsConfigurer.this.rolePrefix, new String[] { role })));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that a user requires one of many roles.
|
||||
* @param roles the roles that the user should have at least one of (i.e.
|
||||
* ADMIN, USER, etc). Each role should not start with ROLE_ since it is
|
||||
* automatically prepended already
|
||||
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customizations
|
||||
*/
|
||||
public AuthorizationManagerServletRequestMatcherRegistry hasAnyRole(String... roles) {
|
||||
return access(withRoleHierarchy(AuthorityAuthorizationManager
|
||||
.hasAnyRole(AuthorizeHttpRequestsConfigurer.this.rolePrefix, roles)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a user requires an authority.
|
||||
* @param authority the authority that should be required
|
||||
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customizations
|
||||
*/
|
||||
public AuthorizationManagerServletRequestMatcherRegistry hasAuthority(String authority) {
|
||||
return access(withRoleHierarchy(AuthorityAuthorizationManager.hasAuthority(authority)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that a user requires one of many authorities.
|
||||
* @param authorities the authorities that the user should have at least one
|
||||
* of (i.e. ROLE_USER, ROLE_ADMIN, etc)
|
||||
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customizations
|
||||
*/
|
||||
public AuthorizationManagerServletRequestMatcherRegistry hasAnyAuthority(String... authorities) {
|
||||
return access(withRoleHierarchy(AuthorityAuthorizationManager.hasAnyAuthority(authorities)));
|
||||
}
|
||||
|
||||
private AuthorityAuthorizationManager<RequestAuthorizationContext> withRoleHierarchy(
|
||||
AuthorityAuthorizationManager<RequestAuthorizationContext> manager) {
|
||||
manager.setRoleHierarchy(AuthorizeHttpRequestsConfigurer.this.roleHierarchy.get());
|
||||
return manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that URLs are allowed by any authenticated user.
|
||||
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customizations
|
||||
*/
|
||||
public AuthorizationManagerServletRequestMatcherRegistry authenticated() {
|
||||
return access(AuthenticatedAuthorizationManager.authenticated());
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that URLs are allowed by users who have authenticated and were not
|
||||
* "remembered".
|
||||
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customization
|
||||
* @see RememberMeConfigurer
|
||||
*/
|
||||
public AuthorizationManagerServletRequestMatcherRegistry fullyAuthenticated() {
|
||||
return access(AuthenticatedAuthorizationManager.fullyAuthenticated());
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that URLs are allowed by users that have been remembered.
|
||||
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customization
|
||||
* @since 5.8
|
||||
* @see RememberMeConfigurer
|
||||
*/
|
||||
public AuthorizationManagerServletRequestMatcherRegistry rememberMe() {
|
||||
return access(AuthenticatedAuthorizationManager.rememberMe());
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that URLs are allowed by anonymous users.
|
||||
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customization
|
||||
* @since 5.8
|
||||
*/
|
||||
public AuthorizationManagerServletRequestMatcherRegistry anonymous() {
|
||||
return access(AuthenticatedAuthorizationManager.anonymous());
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows specifying a custom {@link AuthorizationManager}.
|
||||
* @param manager the {@link AuthorizationManager} to use
|
||||
* @return the {@link AuthorizationManagerRequestMatcherRegistry} for further
|
||||
* customizations
|
||||
*/
|
||||
public AuthorizationManagerServletRequestMatcherRegistry access(
|
||||
AuthorizationManager<RequestAuthorizationContext> manager) {
|
||||
Assert.notNull(manager, "manager cannot be null");
|
||||
return this.registrar.apply(manager);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -603,11 +239,18 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
*/
|
||||
public class AuthorizedUrl {
|
||||
|
||||
private final Function<AuthorizationManager<RequestAuthorizationContext>, AuthorizationManagerRequestMatcherRegistry> registrar;
|
||||
private final List<? extends RequestMatcher> matchers;
|
||||
|
||||
AuthorizedUrl(
|
||||
Function<AuthorizationManager<RequestAuthorizationContext>, AuthorizationManagerRequestMatcherRegistry> registrar) {
|
||||
this.registrar = registrar;
|
||||
/**
|
||||
* Creates an instance.
|
||||
* @param matchers the {@link RequestMatcher} instances to map
|
||||
*/
|
||||
AuthorizedUrl(List<? extends RequestMatcher> matchers) {
|
||||
this.matchers = matchers;
|
||||
}
|
||||
|
||||
protected List<? extends RequestMatcher> getMatchers() {
|
||||
return this.matchers;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -636,8 +279,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
* customizations
|
||||
*/
|
||||
public AuthorizationManagerRequestMatcherRegistry hasRole(String role) {
|
||||
return access(withRoleHierarchy(AuthorityAuthorizationManager
|
||||
.hasAnyRole(AuthorizeHttpRequestsConfigurer.this.rolePrefix, new String[] { role })));
|
||||
return access(withRoleHierarchy(AuthorityAuthorizationManager.hasRole(role)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -649,8 +291,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
* customizations
|
||||
*/
|
||||
public AuthorizationManagerRequestMatcherRegistry hasAnyRole(String... roles) {
|
||||
return access(withRoleHierarchy(
|
||||
AuthorityAuthorizationManager.hasAnyRole(AuthorizeHttpRequestsConfigurer.this.rolePrefix, roles)));
|
||||
return access(withRoleHierarchy(AuthorityAuthorizationManager.hasAnyRole(roles)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -731,7 +372,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
public AuthorizationManagerRequestMatcherRegistry access(
|
||||
AuthorizationManager<RequestAuthorizationContext> manager) {
|
||||
Assert.notNull(manager, "manager cannot be null");
|
||||
return this.registrar.apply(manager);
|
||||
return AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, manager);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ import org.springframework.security.web.csrf.CsrfLogoutHandler;
|
|||
import org.springframework.security.web.csrf.CsrfTokenRepository;
|
||||
import org.springframework.security.web.csrf.CsrfTokenRequestHandler;
|
||||
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
|
||||
import org.springframework.security.web.csrf.LazyCsrfTokenRepository;
|
||||
import org.springframework.security.web.csrf.MissingCsrfTokenException;
|
||||
import org.springframework.security.web.session.InvalidSessionAccessDeniedHandler;
|
||||
import org.springframework.security.web.session.InvalidSessionStrategy;
|
||||
|
@ -82,7 +83,7 @@ import org.springframework.util.Assert;
|
|||
public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
extends AbstractHttpConfigurer<CsrfConfigurer<H>, H> {
|
||||
|
||||
private CsrfTokenRepository csrfTokenRepository = new HttpSessionCsrfTokenRepository();
|
||||
private CsrfTokenRepository csrfTokenRepository = new LazyCsrfTokenRepository(new HttpSessionCsrfTokenRepository());
|
||||
|
||||
private RequestMatcher requireCsrfProtectionMatcher = CsrfFilter.DEFAULT_CSRF_MATCHER;
|
||||
|
||||
|
@ -104,7 +105,7 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
|
||||
/**
|
||||
* Specify the {@link CsrfTokenRepository} to use. The default is an
|
||||
* {@link HttpSessionCsrfTokenRepository}.
|
||||
* {@link HttpSessionCsrfTokenRepository} wrapped by {@link LazyCsrfTokenRepository}.
|
||||
* @param csrfTokenRepository the {@link CsrfTokenRepository} to use
|
||||
* @return the {@link CsrfConfigurer} for further customizations
|
||||
*/
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
final class DispatcherServletDelegatingRequestMatcherBuilder implements RequestMatcherBuilder {
|
||||
|
||||
final MvcRequestMatcherBuilder mvc;
|
||||
|
||||
final AntPathRequestMatcherBuilder ant;
|
||||
|
||||
final ServletRegistrationCollection registrations;
|
||||
|
||||
DispatcherServletDelegatingRequestMatcherBuilder(MvcRequestMatcherBuilder mvc, AntPathRequestMatcherBuilder ant,
|
||||
ServletRegistrationCollection registrations) {
|
||||
this.mvc = mvc;
|
||||
this.ant = ant;
|
||||
this.registrations = registrations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestMatcher matcher(String pattern) {
|
||||
MvcRequestMatcher mvc = this.mvc.matcher(pattern);
|
||||
AntPathRequestMatcher ant = this.ant.matcher(pattern);
|
||||
return new DispatcherServletDelegatingRequestMatcher(mvc, ant, this.registrations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestMatcher matcher(HttpMethod method, String pattern) {
|
||||
MvcRequestMatcher mvc = this.mvc.matcher(method, pattern);
|
||||
AntPathRequestMatcher ant = this.ant.matcher(method, pattern);
|
||||
return new DispatcherServletDelegatingRequestMatcher(mvc, ant, this.registrations);
|
||||
}
|
||||
|
||||
static final class DispatcherServletDelegatingRequestMatcher implements RequestMatcher {
|
||||
|
||||
private final MvcRequestMatcher mvc;
|
||||
|
||||
private final AntPathRequestMatcher ant;
|
||||
|
||||
private final ServletRegistrationCollection registrations;
|
||||
|
||||
private DispatcherServletDelegatingRequestMatcher(MvcRequestMatcher mvc, AntPathRequestMatcher ant,
|
||||
ServletRegistrationCollection registrations) {
|
||||
this.mvc = mvc;
|
||||
this.ant = ant;
|
||||
this.registrations = registrations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(HttpServletRequest request) {
|
||||
String name = request.getHttpServletMapping().getServletName();
|
||||
ServletRegistrationCollection.Registration registration = this.registrations.registrationByName(name);
|
||||
Assert.notNull(registration,
|
||||
String.format("Could not find %s in servlet configuration %s", name, this.registrations));
|
||||
if (registration.isDispatcherServlet()) {
|
||||
return this.mvc.matches(request);
|
||||
}
|
||||
return this.ant.matches(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MatchResult matcher(HttpServletRequest request) {
|
||||
String name = request.getHttpServletMapping().getServletName();
|
||||
ServletRegistrationCollection.Registration registration = this.registrations.registrationByName(name);
|
||||
Assert.notNull(registration,
|
||||
String.format("Could not find %s in servlet configuration %s", name, this.registrations));
|
||||
if (registration.isDispatcherServlet()) {
|
||||
return this.mvc.matcher(request);
|
||||
}
|
||||
return this.ant.matcher(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("DispatcherServlet [mvc=[%s], ant=[%s], servlet=[%s]]", this.mvc, this.ant,
|
||||
this.registrations);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||
|
||||
final class MvcRequestMatcherBuilder implements RequestMatcherBuilder {
|
||||
|
||||
private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
|
||||
|
||||
private final HandlerMappingIntrospector introspector;
|
||||
|
||||
private final ObjectPostProcessor<Object> objectPostProcessor;
|
||||
|
||||
private final String servletPath;
|
||||
|
||||
private MvcRequestMatcherBuilder(ApplicationContext context, String servletPath) {
|
||||
if (!context.containsBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
|
||||
throw new NoSuchBeanDefinitionException("A Bean named " + HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME
|
||||
+ " of type " + HandlerMappingIntrospector.class.getName()
|
||||
+ " is required to use MvcRequestMatcher. Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext.");
|
||||
}
|
||||
this.introspector = context.getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, HandlerMappingIntrospector.class);
|
||||
this.objectPostProcessor = context.getBean(ObjectPostProcessor.class);
|
||||
this.servletPath = servletPath;
|
||||
}
|
||||
|
||||
static MvcRequestMatcherBuilder absolute(ApplicationContext context) {
|
||||
return new MvcRequestMatcherBuilder(context, null);
|
||||
}
|
||||
|
||||
static MvcRequestMatcherBuilder relativeTo(ApplicationContext context, String path) {
|
||||
return new MvcRequestMatcherBuilder(context, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MvcRequestMatcher matcher(String pattern) {
|
||||
MvcRequestMatcher matcher = new MvcRequestMatcher(this.introspector, pattern);
|
||||
this.objectPostProcessor.postProcess(matcher);
|
||||
if (this.servletPath != null) {
|
||||
matcher.setServletPath(this.servletPath);
|
||||
}
|
||||
return matcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MvcRequestMatcher matcher(HttpMethod method, String pattern) {
|
||||
MvcRequestMatcher matcher = new MvcRequestMatcher(this.introspector, pattern);
|
||||
this.objectPostProcessor.postProcess(matcher);
|
||||
matcher.setMethod(method);
|
||||
if (this.servletPath != null) {
|
||||
matcher.setServletPath(this.servletPath);
|
||||
}
|
||||
return matcher;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
/**
|
||||
* An interface that abstracts how matchers are created
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
*/
|
||||
interface RequestMatcherBuilder {
|
||||
|
||||
/**
|
||||
* Create a request matcher for the given pattern.
|
||||
*
|
||||
* <p>
|
||||
* For example, you might do something like the following: <code>
|
||||
* builder.matcher("/controller/**")
|
||||
* </code>
|
||||
* @param pattern the pattern to use, typically an Ant path
|
||||
* @return a {@link RequestMatcher} that matches on the given {@code pattern}
|
||||
*/
|
||||
RequestMatcher matcher(String pattern);
|
||||
|
||||
/**
|
||||
* Create a request matcher for the given pattern.
|
||||
*
|
||||
* <p>
|
||||
* For example, you might do something like the following: <code>
|
||||
* builder.matcher(HttpMethod.GET, "/controller/**")
|
||||
* </code>
|
||||
* @param method the HTTP method to use
|
||||
* @param pattern the pattern to use, typically an Ant path
|
||||
* @return a {@link RequestMatcher} that matches on the given HTTP {@code method} and
|
||||
* {@code pattern}
|
||||
*/
|
||||
RequestMatcher matcher(HttpMethod method, String pattern);
|
||||
|
||||
/**
|
||||
* Create a request matcher that matches any request
|
||||
* @return a {@link RequestMatcher} that matches any request
|
||||
*/
|
||||
default RequestMatcher any() {
|
||||
return AnyRequestMatcher.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array request matchers, one for each of the given patterns.
|
||||
*
|
||||
* <p>
|
||||
* For example, you might do something like the following: <code>
|
||||
* builder.matcher("/controller-one/**", "/controller-two/**")
|
||||
* </code>
|
||||
* @param patterns the patterns to use, typically Ant paths
|
||||
* @return a list of {@link RequestMatcher} that match on the given {@code pattern}
|
||||
*/
|
||||
default List<RequestMatcher> matchers(String... patterns) {
|
||||
List<RequestMatcher> matchers = new ArrayList<>();
|
||||
for (String pattern : patterns) {
|
||||
matchers.add(matcher(pattern));
|
||||
}
|
||||
return matchers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array request matchers, one for each of the given patterns.
|
||||
*
|
||||
* <p>
|
||||
* For example, you might do something like the following: <code>
|
||||
* builder.matcher(HttpMethod.POST, "/controller-one/**", "/controller-two/**")
|
||||
* </code>
|
||||
* @param method the HTTP method to use
|
||||
* @param patterns the patterns to use, typically Ant paths
|
||||
* @return a list of {@link RequestMatcher} that match on the given HTTP
|
||||
* {@code method} and {@code pattern}
|
||||
*/
|
||||
default List<RequestMatcher> matchers(HttpMethod method, String... patterns) {
|
||||
List<RequestMatcher> matchers = new ArrayList<>();
|
||||
for (String pattern : patterns) {
|
||||
matchers.add(matcher(method, pattern));
|
||||
}
|
||||
return matchers;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,215 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
/**
|
||||
* A factory for constructing {@link RequestMatcherBuilder} instances
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
*/
|
||||
final class RequestMatcherBuilders {
|
||||
|
||||
private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
|
||||
|
||||
private static final String HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector";
|
||||
|
||||
private static final boolean mvcPresent;
|
||||
|
||||
static {
|
||||
mvcPresent = ClassUtils.isPresent(HANDLER_MAPPING_INTROSPECTOR, RequestMatcherBuilders.class.getClassLoader());
|
||||
}
|
||||
|
||||
private static final Log logger = LogFactory.getLog(RequestMatcherBuilders.class);
|
||||
|
||||
private RequestMatcherBuilders() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the default {@link RequestMatcherBuilder} for use by Spring Security DSLs.
|
||||
*
|
||||
* <p>
|
||||
* If Spring MVC is not present on the classpath or if there is no
|
||||
* {@link DispatcherServlet}, this method will return an Ant-based builder.
|
||||
*
|
||||
* <p>
|
||||
* If the servlet configuration has only {@link DispatcherServlet} with a single
|
||||
* mapping (for example `/` or `/path/*`), then this method will return an MVC-based
|
||||
* builder.
|
||||
*
|
||||
* <p>
|
||||
* If the servlet configuration maps {@link DispatcherServlet} to a path and also has
|
||||
* other servlets, this will throw an exception. In that case, an application should
|
||||
* instead use the {@link RequestMatcherBuilders#createForServletPattern} ideally with
|
||||
* the associated
|
||||
* {@link org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer}
|
||||
* to create builders by servlet path.
|
||||
*
|
||||
* <p>
|
||||
* Otherwise, (namely if {@link DispatcherServlet} is root), this method will return a
|
||||
* builder that delegates to an Ant or Mvc builder at runtime.
|
||||
* @param context the application context
|
||||
* @return the appropriate {@link RequestMatcherBuilder} based on application
|
||||
* configuration
|
||||
*/
|
||||
static RequestMatcherBuilder createDefault(ApplicationContext context) {
|
||||
if (!mvcPresent) {
|
||||
logger.trace("Defaulting to Ant matching since Spring MVC is not on the classpath");
|
||||
return AntPathRequestMatcherBuilder.absolute();
|
||||
}
|
||||
if (!context.containsBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
|
||||
logger.trace("Defaulting to Ant matching since Spring MVC is not fully configured");
|
||||
return AntPathRequestMatcherBuilder.absolute();
|
||||
}
|
||||
ServletRegistrationCollection registrations = ServletRegistrationCollection.registrations(context);
|
||||
if (registrations.isEmpty()) {
|
||||
logger.trace("Defaulting to MVC matching since Spring MVC is on the class path and no servlet "
|
||||
+ "information is available");
|
||||
return AntPathRequestMatcherBuilder.absolute();
|
||||
}
|
||||
ServletRegistrationCollection dispatcherServlets = registrations.dispatcherServlets();
|
||||
if (dispatcherServlets.isEmpty()) {
|
||||
logger.trace("Defaulting to Ant matching since there is no DispatcherServlet configured");
|
||||
return AntPathRequestMatcherBuilder.absolute();
|
||||
}
|
||||
ServletRegistrationCollection.ServletPath servletPath = registrations.deduceOneServletPath();
|
||||
if (servletPath != null) {
|
||||
String message = "Defaulting to MVC matching since DispatcherServlet [%s] is the only servlet mapping";
|
||||
logger.trace(String.format(message, servletPath.path()));
|
||||
return MvcRequestMatcherBuilder.relativeTo(context, servletPath.path());
|
||||
}
|
||||
servletPath = dispatcherServlets.deduceOneServletPath();
|
||||
if (servletPath == null) {
|
||||
logger.trace("Did not choose a default since there is more than one DispatcherServlet mapping");
|
||||
String message = String.format("""
|
||||
This method cannot decide whether these patterns are Spring MVC patterns or not
|
||||
since your servlet configuration has multiple Spring MVC servlet mappings.
|
||||
|
||||
For your reference, here is your servlet configuration: %s
|
||||
|
||||
To address this, you need to specify the servlet path for each endpoint.
|
||||
You can use .forServletPattern in conjunction with requestMatchers do to this
|
||||
like so:
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.forServletPattern("/mvc-one/*", (one) -> one
|
||||
.requestMatchers("/controller/**", "/endpoints/**"
|
||||
)...
|
||||
.forServletPattern("/mvc-two/*", (two) -> two
|
||||
.requestMatchers("/other/**", "/controllers/**")...
|
||||
)
|
||||
.forServletPattern("/h2-console/*", (h2) -> h2
|
||||
.requestMatchers("/**")...
|
||||
)
|
||||
)
|
||||
// ...
|
||||
return http.build();
|
||||
}
|
||||
""", registrations);
|
||||
return new ErrorRequestMatcherBuilder(message);
|
||||
}
|
||||
if (servletPath.path() != null) {
|
||||
logger.trace("Did not choose a default since there is a non-root DispatcherServlet mapping");
|
||||
String message = String.format("""
|
||||
This method cannot decide whether these patterns are Spring MVC patterns or not
|
||||
since your Spring MVC mapping is mapped to a path and you have other servlet mappings.
|
||||
|
||||
For your reference, here is your servlet configuration: %s
|
||||
|
||||
To address this, you need to specify the servlet path for each endpoint.
|
||||
You can use .forServletPattern in conjunction with requestMatchers do to this
|
||||
like so:
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.forServletPattern("/mvc/*", (mvc) -> mvc
|
||||
.requestMatchers("/controller/**", "/endpoints/**")...
|
||||
)
|
||||
.forServletPattern("/h2-console/*", (h2) -> h2
|
||||
.requestMatchers("/**")...
|
||||
)
|
||||
)
|
||||
// ...
|
||||
return http.build();
|
||||
}
|
||||
""", registrations);
|
||||
return new ErrorRequestMatcherBuilder(message);
|
||||
}
|
||||
logger.trace("Defaulting to request-time checker since DispatcherServlet is mapped to root, but there are also "
|
||||
+ "other servlet mappings");
|
||||
return new DispatcherServletDelegatingRequestMatcherBuilder(MvcRequestMatcherBuilder.absolute(context),
|
||||
AntPathRequestMatcherBuilder.absolute(), registrations);
|
||||
}
|
||||
|
||||
static RequestMatcherBuilder createForServletPattern(ApplicationContext context, String pattern) {
|
||||
Assert.notNull(pattern, "pattern cannot be null");
|
||||
ServletRegistrationCollection registrations = ServletRegistrationCollection.registrations(context);
|
||||
ServletRegistrationCollection.Registration registration = registrations.registrationByMapping(pattern);
|
||||
Assert.notNull(registration, () -> String
|
||||
.format("The given pattern %s doesn't seem to match any configured servlets: %s", pattern, registrations));
|
||||
boolean isPathPattern = pattern.startsWith("/") && pattern.endsWith("/*");
|
||||
if (isPathPattern) {
|
||||
String path = pattern.substring(0, pattern.length() - 2);
|
||||
return (registration.isDispatcherServlet()) ? MvcRequestMatcherBuilder.relativeTo(context, path)
|
||||
: AntPathRequestMatcherBuilder.relativeTo(path);
|
||||
}
|
||||
return (registration.isDispatcherServlet()) ? MvcRequestMatcherBuilder.absolute(context)
|
||||
: AntPathRequestMatcherBuilder.absolute();
|
||||
}
|
||||
|
||||
private static class ErrorRequestMatcherBuilder implements RequestMatcherBuilder {
|
||||
|
||||
private final String errorMessage;
|
||||
|
||||
ErrorRequestMatcherBuilder(String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestMatcher matcher(String pattern) {
|
||||
throw new IllegalArgumentException(this.errorMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestMatcher matcher(HttpMethod method, String pattern) {
|
||||
throw new IllegalArgumentException(this.errorMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestMatcher any() {
|
||||
throw new IllegalArgumentException(this.errorMessage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
final class ServletPatternRequestMatcher implements RequestMatcher {
|
||||
|
||||
final String pattern;
|
||||
|
||||
ServletPatternRequestMatcher(String pattern) {
|
||||
Assert.notNull(pattern, "pattern cannot be null");
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(HttpServletRequest request) {
|
||||
return this.pattern.equals(request.getHttpServletMapping().getPattern());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("ServletPattern [pattern='%s']", this.pattern);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.ServletRegistration;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
final class ServletRegistrationCollection {
|
||||
|
||||
private List<Registration> registrations;
|
||||
|
||||
private ServletRegistrationCollection() {
|
||||
this.registrations = Collections.emptyList();
|
||||
}
|
||||
|
||||
private ServletRegistrationCollection(List<Registration> registrations) {
|
||||
this.registrations = registrations;
|
||||
}
|
||||
|
||||
static ServletRegistrationCollection registrations(ApplicationContext context) {
|
||||
if (!(context instanceof WebApplicationContext web)) {
|
||||
return new ServletRegistrationCollection();
|
||||
}
|
||||
ServletContext servletContext = web.getServletContext();
|
||||
if (servletContext == null) {
|
||||
return new ServletRegistrationCollection();
|
||||
}
|
||||
Map<String, ? extends ServletRegistration> registrations = servletContext.getServletRegistrations();
|
||||
if (registrations == null) {
|
||||
return new ServletRegistrationCollection();
|
||||
}
|
||||
List<Registration> filtered = new ArrayList<>();
|
||||
for (ServletRegistration registration : registrations.values()) {
|
||||
Collection<String> mappings = registration.getMappings();
|
||||
if (!CollectionUtils.isEmpty(mappings)) {
|
||||
filtered.add(new Registration(registration));
|
||||
}
|
||||
}
|
||||
return new ServletRegistrationCollection(filtered);
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return this.registrations.isEmpty();
|
||||
}
|
||||
|
||||
Registration registrationByName(String name) {
|
||||
for (Registration registration : this.registrations) {
|
||||
if (registration.registration().getName().equals(name)) {
|
||||
return registration;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Registration registrationByMapping(String target) {
|
||||
for (Registration registration : this.registrations) {
|
||||
for (String mapping : registration.registration().getMappings()) {
|
||||
if (target.equals(mapping)) {
|
||||
return registration;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
ServletRegistrationCollection dispatcherServlets() {
|
||||
List<Registration> dispatcherServlets = new ArrayList<>();
|
||||
for (Registration registration : this.registrations) {
|
||||
if (registration.isDispatcherServlet()) {
|
||||
dispatcherServlets.add(registration);
|
||||
}
|
||||
}
|
||||
return new ServletRegistrationCollection(dispatcherServlets);
|
||||
}
|
||||
|
||||
ServletPath deduceOneServletPath() {
|
||||
if (this.registrations.size() > 1) {
|
||||
return null;
|
||||
}
|
||||
ServletRegistration registration = this.registrations.iterator().next().registration();
|
||||
if (registration.getMappings().size() > 1) {
|
||||
return null;
|
||||
}
|
||||
String mapping = registration.getMappings().iterator().next();
|
||||
if ("/".equals(mapping)) {
|
||||
return new ServletPath();
|
||||
}
|
||||
if (mapping.endsWith("/*")) {
|
||||
return new ServletPath(mapping.substring(0, mapping.length() - 2));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
Map<String, Collection<String>> mappings = new LinkedHashMap<>();
|
||||
for (Registration registration : this.registrations) {
|
||||
mappings.put(registration.registration().getClassName(), registration.registration().getMappings());
|
||||
}
|
||||
return mappings.toString();
|
||||
}
|
||||
|
||||
record Registration(ServletRegistration registration) {
|
||||
boolean isDispatcherServlet() {
|
||||
Class<?> dispatcherServlet = ClassUtils
|
||||
.resolveClassName("org.springframework.web.servlet.DispatcherServlet", null);
|
||||
try {
|
||||
Class<?> clazz = Class.forName(this.registration.getClassName());
|
||||
if (dispatcherServlet.isAssignableFrom(clazz)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
record ServletPath(String path) {
|
||||
ServletPath() {
|
||||
this(null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -296,7 +296,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
|
|||
* @param sessionAuthenticationStrategy
|
||||
* @return the {@link SessionManagementConfigurer} for further customizations
|
||||
*/
|
||||
public SessionManagementConfigurer<H> addSessionAuthenticationStrategy(
|
||||
SessionManagementConfigurer<H> addSessionAuthenticationStrategy(
|
||||
SessionAuthenticationStrategy sessionAuthenticationStrategy) {
|
||||
this.sessionAuthenticationStrategies.add(sessionAuthenticationStrategy);
|
||||
return this;
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
|
||||
|
||||
final class DefaultOidcLogoutTokenValidatorFactory implements Function<ClientRegistration, OAuth2TokenValidator<Jwt>> {
|
||||
|
||||
@Override
|
||||
public OAuth2TokenValidator<Jwt> apply(ClientRegistration clientRegistration) {
|
||||
return new DelegatingOAuth2TokenValidator<>(new JwtTimestampValidator(),
|
||||
new OidcBackChannelLogoutTokenValidator(clientRegistration));
|
||||
}
|
||||
|
||||
}
|
|
@ -16,8 +16,6 @@
|
|||
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
|
@ -309,22 +307,7 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
if (this.accessTokenResponseClient != null) {
|
||||
return this.accessTokenResponseClient;
|
||||
}
|
||||
ResolvableType resolvableType = ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
|
||||
OAuth2AuthorizationCodeGrantRequest.class);
|
||||
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> bean = getBeanOrNull(resolvableType);
|
||||
return (bean != null) ? bean : new DefaultAuthorizationCodeTokenResponseClient();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getBeanOrNull(ResolvableType type) {
|
||||
ApplicationContext context = getBuilder().getSharedObject(ApplicationContext.class);
|
||||
if (context != null) {
|
||||
String[] names = context.getBeanNamesForType(type);
|
||||
if (names.length == 1) {
|
||||
return (T) context.getBean(names[0]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return new DefaultAuthorizationCodeTokenResponseClient();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,8 +25,6 @@ import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
|||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.oidc.session.InMemoryOidcSessionRegistry;
|
||||
import org.springframework.security.oauth2.client.oidc.session.OidcSessionRegistry;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||
|
@ -114,13 +112,4 @@ final class OAuth2ClientConfigurerUtils {
|
|||
return (!authorizedClientServiceMap.isEmpty() ? authorizedClientServiceMap.values().iterator().next() : null);
|
||||
}
|
||||
|
||||
static <B extends HttpSecurityBuilder<B>> OidcSessionRegistry getOidcSessionRegistry(B builder) {
|
||||
OidcSessionRegistry sessionRegistry = builder.getSharedObject(OidcSessionRegistry.class);
|
||||
if (sessionRegistry == null) {
|
||||
sessionRegistry = new InMemoryOidcSessionRegistry();
|
||||
builder.setSharedObject(OidcSessionRegistry.class, sessionRegistry);
|
||||
}
|
||||
return sessionRegistry;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,18 +22,9 @@ import java.util.HashMap;
|
|||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.GenericApplicationListenerAdapter;
|
||||
import org.springframework.context.event.SmartApplicationListener;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.config.Customizer;
|
||||
|
@ -41,14 +32,9 @@ import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
|||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
|
||||
import org.springframework.security.context.DelegatingApplicationListener;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||
import org.springframework.security.core.session.AbstractSessionEvent;
|
||||
import org.springframework.security.core.session.SessionDestroyedEvent;
|
||||
import org.springframework.security.core.session.SessionIdChangedEvent;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
|
||||
|
@ -56,9 +42,6 @@ import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationC
|
|||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.client.oidc.session.InMemoryOidcSessionRegistry;
|
||||
import org.springframework.security.oauth2.client.oidc.session.OidcSessionInformation;
|
||||
import org.springframework.security.oauth2.client.oidc.session.OidcSessionRegistry;
|
||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
|
@ -84,10 +67,7 @@ import org.springframework.security.web.AuthenticationEntryPoint;
|
|||
import org.springframework.security.web.RedirectStrategy;
|
||||
import org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.authentication.session.SessionAuthenticationException;
|
||||
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
||||
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
|
||||
import org.springframework.security.web.csrf.CsrfToken;
|
||||
import org.springframework.security.web.savedrequest.RequestCache;
|
||||
import org.springframework.security.web.util.matcher.AndRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
|
@ -144,7 +124,6 @@ import org.springframework.util.ReflectionUtils;
|
|||
* <li>{@link DefaultLoginPageGeneratingFilter} - if {@link #loginPage(String)} is not
|
||||
* configured and {@code DefaultLoginPageGeneratingFilter} is available, then a default
|
||||
* login page will be made available</li>
|
||||
* <li>{@link OidcSessionRegistry}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Joe Grandja
|
||||
|
@ -223,18 +202,6 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the registry for managing the OIDC client-provider session link
|
||||
* @param oidcSessionRegistry the {@link OidcSessionRegistry} to use
|
||||
* @return the {@link OAuth2LoginConfigurer} for further configuration
|
||||
* @since 6.2
|
||||
*/
|
||||
public OAuth2LoginConfigurer<B> oidcSessionRegistry(OidcSessionRegistry oidcSessionRegistry) {
|
||||
Assert.notNull(oidcSessionRegistry, "oidcSessionRegistry cannot be null");
|
||||
getBuilder().setSharedObject(OidcSessionRegistry.class, oidcSessionRegistry);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link AuthorizationEndpointConfig} for configuring the Authorization
|
||||
* Server's Authorization Endpoint.
|
||||
|
@ -363,7 +330,10 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
super.init(http);
|
||||
}
|
||||
}
|
||||
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient = getAccessTokenResponseClient();
|
||||
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient = this.tokenEndpointConfig.accessTokenResponseClient;
|
||||
if (accessTokenResponseClient == null) {
|
||||
accessTokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
|
||||
}
|
||||
OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService = getOAuth2UserService();
|
||||
OAuth2LoginAuthenticationProvider oauth2LoginAuthenticationProvider = new OAuth2LoginAuthenticationProvider(
|
||||
accessTokenResponseClient, oauth2UserService);
|
||||
|
@ -430,7 +400,6 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
authenticationFilter
|
||||
.setAuthorizationRequestRepository(this.authorizationEndpointConfig.authorizationRequestRepository);
|
||||
}
|
||||
configureOidcSessionRegistry(http);
|
||||
super.configure(http);
|
||||
}
|
||||
|
||||
|
@ -473,16 +442,6 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
return (!grantedAuthoritiesMapperMap.isEmpty() ? grantedAuthoritiesMapperMap.values().iterator().next() : null);
|
||||
}
|
||||
|
||||
private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> getAccessTokenResponseClient() {
|
||||
if (this.tokenEndpointConfig.accessTokenResponseClient != null) {
|
||||
return this.tokenEndpointConfig.accessTokenResponseClient;
|
||||
}
|
||||
ResolvableType resolvableType = ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
|
||||
OAuth2AuthorizationCodeGrantRequest.class);
|
||||
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> bean = getBeanOrNull(resolvableType);
|
||||
return (bean != null) ? bean : new DefaultAuthorizationCodeTokenResponseClient();
|
||||
}
|
||||
|
||||
private OAuth2UserService<OidcUserRequest, OidcUser> getOidcUserService() {
|
||||
if (this.userInfoEndpointConfig.oidcUserService != null) {
|
||||
return this.userInfoEndpointConfig.oidcUserService;
|
||||
|
@ -581,29 +540,6 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
return AnyRequestMatcher.INSTANCE;
|
||||
}
|
||||
|
||||
private void configureOidcSessionRegistry(B http) {
|
||||
OidcSessionRegistry sessionRegistry = OAuth2ClientConfigurerUtils.getOidcSessionRegistry(http);
|
||||
SessionManagementConfigurer<B> sessionConfigurer = http.getConfigurer(SessionManagementConfigurer.class);
|
||||
if (sessionConfigurer != null) {
|
||||
OidcSessionRegistryAuthenticationStrategy sessionAuthenticationStrategy = new OidcSessionRegistryAuthenticationStrategy();
|
||||
sessionAuthenticationStrategy.setSessionRegistry(sessionRegistry);
|
||||
sessionConfigurer.addSessionAuthenticationStrategy(sessionAuthenticationStrategy);
|
||||
}
|
||||
OidcClientSessionEventListener listener = new OidcClientSessionEventListener();
|
||||
listener.setSessionRegistry(sessionRegistry);
|
||||
registerDelegateApplicationListener(listener);
|
||||
}
|
||||
|
||||
private void registerDelegateApplicationListener(ApplicationListener<?> delegate) {
|
||||
DelegatingApplicationListener delegating = getBeanOrNull(
|
||||
ResolvableType.forType(DelegatingApplicationListener.class));
|
||||
if (delegating == null) {
|
||||
return;
|
||||
}
|
||||
SmartApplicationListener smartListener = new GenericApplicationListenerAdapter(delegate);
|
||||
delegating.addListener(smartListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration options for the Authorization Server's Authorization Endpoint.
|
||||
*/
|
||||
|
@ -851,88 +787,4 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
|
||||
}
|
||||
|
||||
private static final class OidcClientSessionEventListener implements ApplicationListener<AbstractSessionEvent> {
|
||||
|
||||
private final Log logger = LogFactory.getLog(OidcClientSessionEventListener.class);
|
||||
|
||||
private OidcSessionRegistry sessionRegistry = new InMemoryOidcSessionRegistry();
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onApplicationEvent(AbstractSessionEvent event) {
|
||||
if (event instanceof SessionDestroyedEvent destroyed) {
|
||||
this.logger.debug("Received SessionDestroyedEvent");
|
||||
this.sessionRegistry.removeSessionInformation(destroyed.getId());
|
||||
return;
|
||||
}
|
||||
if (event instanceof SessionIdChangedEvent changed) {
|
||||
this.logger.debug("Received SessionIdChangedEvent");
|
||||
OidcSessionInformation information = this.sessionRegistry
|
||||
.removeSessionInformation(changed.getOldSessionId());
|
||||
if (information == null) {
|
||||
this.logger
|
||||
.debug("Failed to register new session id since old session id was not found in registry");
|
||||
return;
|
||||
}
|
||||
this.sessionRegistry.saveSessionInformation(information.withSessionId(changed.getNewSessionId()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The registry where OIDC Provider sessions are linked to the Client session.
|
||||
* Defaults to in-memory storage.
|
||||
* @param sessionRegistry the {@link OidcSessionRegistry} to use
|
||||
*/
|
||||
void setSessionRegistry(OidcSessionRegistry sessionRegistry) {
|
||||
Assert.notNull(sessionRegistry, "sessionRegistry cannot be null");
|
||||
this.sessionRegistry = sessionRegistry;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class OidcSessionRegistryAuthenticationStrategy implements SessionAuthenticationStrategy {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private OidcSessionRegistry sessionRegistry = new InMemoryOidcSessionRegistry();
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onAuthentication(Authentication authentication, HttpServletRequest request,
|
||||
HttpServletResponse response) throws SessionAuthenticationException {
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
if (!(authentication.getPrincipal() instanceof OidcUser user)) {
|
||||
return;
|
||||
}
|
||||
String sessionId = session.getId();
|
||||
CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
|
||||
Map<String, String> headers = (csrfToken != null) ? Map.of(csrfToken.getHeaderName(), csrfToken.getToken())
|
||||
: Collections.emptyMap();
|
||||
OidcSessionInformation registration = new OidcSessionInformation(sessionId, headers, user);
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger
|
||||
.trace(String.format("Linking a provider [%s] session to this client's session", user.getIssuer()));
|
||||
}
|
||||
this.sessionRegistry.saveSessionInformation(registration);
|
||||
}
|
||||
|
||||
/**
|
||||
* The registration for linking OIDC Provider Session information to the Client's
|
||||
* session. Defaults to in-memory storage.
|
||||
* @param sessionRegistry the {@link OidcSessionRegistry} to use
|
||||
*/
|
||||
void setSessionRegistry(OidcSessionRegistry sessionRegistry) {
|
||||
Assert.notNull(sessionRegistry, "sessionRegistry cannot be null");
|
||||
this.sessionRegistry = sessionRegistry;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken;
|
||||
|
||||
/**
|
||||
* An {@link org.springframework.security.core.Authentication} implementation that
|
||||
* represents the result of authenticating an OIDC Logout token for the purposes of
|
||||
* performing Back-Channel Logout.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
* @see OidcLogoutAuthenticationToken
|
||||
* @see <a target="_blank" href=
|
||||
* "https://openid.net/specs/openid-connect-backchannel-1_0.html">OIDC Back-Channel
|
||||
* Logout</a>
|
||||
*/
|
||||
class OidcBackChannelLogoutAuthentication extends AbstractAuthenticationToken {
|
||||
|
||||
private final OidcLogoutToken logoutToken;
|
||||
|
||||
/**
|
||||
* Construct an {@link OidcBackChannelLogoutAuthentication}
|
||||
* @param logoutToken a deserialized, verified OIDC Logout Token
|
||||
*/
|
||||
OidcBackChannelLogoutAuthentication(OidcLogoutToken logoutToken) {
|
||||
super(Collections.emptyList());
|
||||
this.logoutToken = logoutToken;
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public OidcLogoutToken getPrincipal() {
|
||||
return this.logoutToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public OidcLogoutToken getCredentials() {
|
||||
return this.logoutToken;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory;
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.jwt.BadJwtException;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link AuthenticationProvider} that authenticates an OIDC Logout Token; namely
|
||||
* deserializing it, verifying its signature, and validating its claims.
|
||||
*
|
||||
* <p>
|
||||
* Intended to be included in a
|
||||
* {@link org.springframework.security.authentication.ProviderManager}
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
* @see OidcLogoutAuthenticationToken
|
||||
* @see org.springframework.security.authentication.ProviderManager
|
||||
* @see <a target="_blank" href=
|
||||
* "https://openid.net/specs/openid-connect-backchannel-1_0.html">OIDC Back-Channel
|
||||
* Logout</a>
|
||||
*/
|
||||
final class OidcBackChannelLogoutAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private JwtDecoderFactory<ClientRegistration> logoutTokenDecoderFactory;
|
||||
|
||||
/**
|
||||
* Construct an {@link OidcBackChannelLogoutAuthenticationProvider}
|
||||
*/
|
||||
OidcBackChannelLogoutAuthenticationProvider() {
|
||||
OidcIdTokenDecoderFactory logoutTokenDecoderFactory = new OidcIdTokenDecoderFactory();
|
||||
logoutTokenDecoderFactory.setJwtValidatorFactory(new DefaultOidcLogoutTokenValidatorFactory());
|
||||
this.logoutTokenDecoderFactory = logoutTokenDecoderFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
if (!(authentication instanceof OidcLogoutAuthenticationToken token)) {
|
||||
return null;
|
||||
}
|
||||
String logoutToken = token.getLogoutToken();
|
||||
ClientRegistration registration = token.getClientRegistration();
|
||||
Jwt jwt = decode(registration, logoutToken);
|
||||
OidcLogoutToken oidcLogoutToken = OidcLogoutToken.withTokenValue(logoutToken)
|
||||
.claims((claims) -> claims.putAll(jwt.getClaims()))
|
||||
.build();
|
||||
return new OidcBackChannelLogoutAuthentication(oidcLogoutToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return OidcLogoutAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
private Jwt decode(ClientRegistration registration, String token) {
|
||||
JwtDecoder logoutTokenDecoder = this.logoutTokenDecoderFactory.createDecoder(registration);
|
||||
try {
|
||||
return logoutTokenDecoder.decode(token);
|
||||
}
|
||||
catch (BadJwtException failed) {
|
||||
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST, failed.getMessage(),
|
||||
"https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation");
|
||||
throw new OAuth2AuthenticationException(error, failed);
|
||||
}
|
||||
catch (Exception failed) {
|
||||
throw new AuthenticationServiceException(failed.getMessage(), failed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@link JwtDecoderFactory} to generate {@link JwtDecoder}s that correspond
|
||||
* to the {@link ClientRegistration} associated with the OIDC logout token.
|
||||
* @param logoutTokenDecoderFactory the {@link JwtDecoderFactory} to use
|
||||
*/
|
||||
void setLogoutTokenDecoderFactory(JwtDecoderFactory<ClientRegistration> logoutTokenDecoderFactory) {
|
||||
Assert.notNull(logoutTokenDecoderFactory, "logoutTokenDecoderFactory cannot be null");
|
||||
this.logoutTokenDecoderFactory = logoutTokenDecoderFactory;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.logout.LogoutHandler;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
/**
|
||||
* A filter for the Client-side OIDC Back-Channel Logout endpoint
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
* @see <a target="_blank" href=
|
||||
* "https://openid.net/specs/openid-connect-backchannel-1_0.html">OIDC Back-Channel Logout
|
||||
* Spec</a>
|
||||
*/
|
||||
class OidcBackChannelLogoutFilter extends OncePerRequestFilter {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final AuthenticationConverter authenticationConverter;
|
||||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
|
||||
private final OAuth2ErrorHttpMessageConverter errorHttpMessageConverter = new OAuth2ErrorHttpMessageConverter();
|
||||
|
||||
private LogoutHandler logoutHandler = new OidcBackChannelLogoutHandler();
|
||||
|
||||
/**
|
||||
* Construct an {@link OidcBackChannelLogoutFilter}
|
||||
* @param authenticationConverter the {@link AuthenticationConverter} for deriving
|
||||
* Logout Token authentication
|
||||
* @param authenticationManager the {@link AuthenticationManager} for authenticating
|
||||
* Logout Tokens
|
||||
*/
|
||||
OidcBackChannelLogoutFilter(AuthenticationConverter authenticationConverter,
|
||||
AuthenticationManager authenticationManager) {
|
||||
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
|
||||
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
|
||||
this.authenticationConverter = authenticationConverter;
|
||||
this.authenticationManager = authenticationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||
throws ServletException, IOException {
|
||||
Authentication token;
|
||||
try {
|
||||
token = this.authenticationConverter.convert(request);
|
||||
}
|
||||
catch (AuthenticationServiceException ex) {
|
||||
this.logger.debug("Failed to process OIDC Back-Channel Logout", ex);
|
||||
throw ex;
|
||||
}
|
||||
catch (AuthenticationException ex) {
|
||||
handleAuthenticationFailure(response, ex);
|
||||
return;
|
||||
}
|
||||
if (token == null) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
Authentication authentication;
|
||||
try {
|
||||
authentication = this.authenticationManager.authenticate(token);
|
||||
}
|
||||
catch (AuthenticationServiceException ex) {
|
||||
this.logger.debug("Failed to process OIDC Back-Channel Logout", ex);
|
||||
throw ex;
|
||||
}
|
||||
catch (AuthenticationException ex) {
|
||||
handleAuthenticationFailure(response, ex);
|
||||
return;
|
||||
}
|
||||
this.logoutHandler.logout(request, response, authentication);
|
||||
}
|
||||
|
||||
private void handleAuthenticationFailure(HttpServletResponse response, Exception ex) throws IOException {
|
||||
this.logger.debug("Failed to process OIDC Back-Channel Logout", ex);
|
||||
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||
this.errorHttpMessageConverter.write(oauth2Error(ex), null, new ServletServerHttpResponse(response));
|
||||
}
|
||||
|
||||
private OAuth2Error oauth2Error(Exception ex) {
|
||||
if (ex instanceof OAuth2AuthenticationException oauth2) {
|
||||
return oauth2.getError();
|
||||
}
|
||||
return new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST, ex.getMessage(),
|
||||
"https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation");
|
||||
}
|
||||
|
||||
/**
|
||||
* The strategy for expiring all Client sessions indicated by the logout request.
|
||||
* Defaults to {@link OidcBackChannelLogoutHandler}.
|
||||
* @param logoutHandler the {@link LogoutHandler} to use
|
||||
*/
|
||||
void setLogoutHandler(LogoutHandler logoutHandler) {
|
||||
Assert.notNull(logoutHandler, "logoutHandler cannot be null");
|
||||
this.logoutHandler = logoutHandler;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,177 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken;
|
||||
import org.springframework.security.oauth2.client.oidc.session.InMemoryOidcSessionRegistry;
|
||||
import org.springframework.security.oauth2.client.oidc.session.OidcSessionInformation;
|
||||
import org.springframework.security.oauth2.client.oidc.session.OidcSessionRegistry;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
|
||||
import org.springframework.security.web.authentication.logout.LogoutHandler;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* A {@link LogoutHandler} that locates the sessions associated with a given OIDC
|
||||
* Back-Channel Logout Token and invalidates each one.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
* @see <a target="_blank" href=
|
||||
* "https://openid.net/specs/openid-connect-backchannel-1_0.html">OIDC Back-Channel Logout
|
||||
* Spec</a>
|
||||
*/
|
||||
final class OidcBackChannelLogoutHandler implements LogoutHandler {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private OidcSessionRegistry sessionRegistry = new InMemoryOidcSessionRegistry();
|
||||
|
||||
private RestOperations restOperations = new RestTemplate();
|
||||
|
||||
private String logoutEndpointName = "/logout";
|
||||
|
||||
private String sessionCookieName = "JSESSIONID";
|
||||
|
||||
private final OAuth2ErrorHttpMessageConverter errorHttpMessageConverter = new OAuth2ErrorHttpMessageConverter();
|
||||
|
||||
@Override
|
||||
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
|
||||
if (!(authentication instanceof OidcBackChannelLogoutAuthentication token)) {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
String message = "Did not perform OIDC Back-Channel Logout since authentication [%s] was of the wrong type";
|
||||
this.logger.debug(String.format(message, authentication.getClass().getSimpleName()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
Iterable<OidcSessionInformation> sessions = this.sessionRegistry.removeSessionInformation(token.getPrincipal());
|
||||
Collection<String> errors = new ArrayList<>();
|
||||
int totalCount = 0;
|
||||
int invalidatedCount = 0;
|
||||
for (OidcSessionInformation session : sessions) {
|
||||
totalCount++;
|
||||
try {
|
||||
eachLogout(request, session);
|
||||
invalidatedCount++;
|
||||
}
|
||||
catch (RestClientException ex) {
|
||||
this.logger.debug("Failed to invalidate session", ex);
|
||||
errors.add(ex.getMessage());
|
||||
this.sessionRegistry.saveSessionInformation(session);
|
||||
}
|
||||
}
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger.trace(String.format("Invalidated %d out of %d sessions", invalidatedCount, totalCount));
|
||||
}
|
||||
if (!errors.isEmpty()) {
|
||||
handleLogoutFailure(response, oauth2Error(errors));
|
||||
}
|
||||
}
|
||||
|
||||
private void eachLogout(HttpServletRequest request, OidcSessionInformation session) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add(HttpHeaders.COOKIE, this.sessionCookieName + "=" + session.getSessionId());
|
||||
for (Map.Entry<String, String> credential : session.getAuthorities().entrySet()) {
|
||||
headers.add(credential.getKey(), credential.getValue());
|
||||
}
|
||||
String url = request.getRequestURL().toString();
|
||||
String logout = UriComponentsBuilder.fromHttpUrl(url)
|
||||
.replacePath(this.logoutEndpointName)
|
||||
.build()
|
||||
.toUriString();
|
||||
HttpEntity<?> entity = new HttpEntity<>(null, headers);
|
||||
this.restOperations.postForEntity(logout, entity, Object.class);
|
||||
}
|
||||
|
||||
private OAuth2Error oauth2Error(Collection<String> errors) {
|
||||
return new OAuth2Error("partial_logout", "not all sessions were terminated: " + errors,
|
||||
"https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation");
|
||||
}
|
||||
|
||||
private void handleLogoutFailure(HttpServletResponse response, OAuth2Error error) {
|
||||
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||
try {
|
||||
this.errorHttpMessageConverter.write(error, null, new ServletServerHttpResponse(response));
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@link OidcSessionRegistry} to identify sessions to invalidate. Note that
|
||||
* this class uses
|
||||
* {@link OidcSessionRegistry#removeSessionInformation(OidcLogoutToken)} to identify
|
||||
* sessions.
|
||||
* @param sessionRegistry the {@link OidcSessionRegistry} to use
|
||||
*/
|
||||
void setSessionRegistry(OidcSessionRegistry sessionRegistry) {
|
||||
Assert.notNull(sessionRegistry, "sessionRegistry cannot be null");
|
||||
this.sessionRegistry = sessionRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@link RestOperations} to perform the per-session back-channel logout
|
||||
* @param restOperations the {@link RestOperations} to use
|
||||
*/
|
||||
void setRestOperations(RestOperations restOperations) {
|
||||
Assert.notNull(restOperations, "restOperations cannot be null");
|
||||
this.restOperations = restOperations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this logout URI for performing per-session logout. Defaults to {@code /logout}
|
||||
* since that is the default URI for
|
||||
* {@link org.springframework.security.web.authentication.logout.LogoutFilter}.
|
||||
* @param logoutUri the URI to use
|
||||
*/
|
||||
void setLogoutUri(String logoutUri) {
|
||||
Assert.hasText(logoutUri, "logoutUri cannot be empty");
|
||||
this.logoutEndpointName = logoutUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this cookie name for the session identifier. Defaults to {@code JSESSIONID}.
|
||||
*
|
||||
* <p>
|
||||
* Note that if you are using Spring Session, this likely needs to change to SESSION.
|
||||
* @param sessionCookieName the cookie name to use
|
||||
*/
|
||||
void setSessionCookieName(String sessionCookieName) {
|
||||
Assert.hasText(sessionCookieName, "clientSessionCookieName cannot be empty");
|
||||
this.sessionCookieName = sessionCookieName;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.logout.LogoutTokenClaimAccessor;
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
|
||||
/**
|
||||
* A {@link OAuth2TokenValidator} that validates OIDC Logout Token claims in conformance
|
||||
* with the OIDC Back-Channel Logout Spec.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
* @see OidcLogoutToken
|
||||
* @see <a target="_blank" href=
|
||||
* "https://openid.net/specs/openid-connect-backchannel-1_0.html#LogoutToken">Logout
|
||||
* Token</a>
|
||||
* @see <a target="blank" href=
|
||||
* "https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation">the OIDC
|
||||
* Back-Channel Logout spec</a>
|
||||
*/
|
||||
final class OidcBackChannelLogoutTokenValidator implements OAuth2TokenValidator<Jwt> {
|
||||
|
||||
private static final String LOGOUT_VALIDATION_URL = "https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation";
|
||||
|
||||
private static final String BACK_CHANNEL_LOGOUT_EVENT = "http://schemas.openid.net/event/backchannel-logout";
|
||||
|
||||
private final String audience;
|
||||
|
||||
private final String issuer;
|
||||
|
||||
OidcBackChannelLogoutTokenValidator(ClientRegistration clientRegistration) {
|
||||
this.audience = clientRegistration.getClientId();
|
||||
this.issuer = clientRegistration.getProviderDetails().getIssuerUri();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2TokenValidatorResult validate(Jwt jwt) {
|
||||
Collection<OAuth2Error> errors = new ArrayList<>();
|
||||
|
||||
LogoutTokenClaimAccessor logoutClaims = jwt::getClaims;
|
||||
Map<String, Object> events = logoutClaims.getEvents();
|
||||
if (events == null) {
|
||||
errors.add(invalidLogoutToken("events claim must not be null"));
|
||||
}
|
||||
else if (events.get(BACK_CHANNEL_LOGOUT_EVENT) == null) {
|
||||
errors.add(invalidLogoutToken("events claim map must contain \"" + BACK_CHANNEL_LOGOUT_EVENT + "\" key"));
|
||||
}
|
||||
|
||||
String issuer = logoutClaims.getIssuer().toExternalForm();
|
||||
if (issuer == null) {
|
||||
errors.add(invalidLogoutToken("iss claim must not be null"));
|
||||
}
|
||||
else if (!this.issuer.equals(issuer)) {
|
||||
errors.add(invalidLogoutToken(
|
||||
"iss claim value must match `ClientRegistration#getProviderDetails#getIssuerUri`"));
|
||||
}
|
||||
|
||||
List<String> audience = logoutClaims.getAudience();
|
||||
if (audience == null) {
|
||||
errors.add(invalidLogoutToken("aud claim must not be null"));
|
||||
}
|
||||
else if (!audience.contains(this.audience)) {
|
||||
errors.add(invalidLogoutToken("aud claim value must include `ClientRegistration#getClientId`"));
|
||||
}
|
||||
|
||||
Instant issuedAt = logoutClaims.getIssuedAt();
|
||||
if (issuedAt == null) {
|
||||
errors.add(invalidLogoutToken("iat claim must not be null"));
|
||||
}
|
||||
|
||||
String jwtId = logoutClaims.getId();
|
||||
if (jwtId == null) {
|
||||
errors.add(invalidLogoutToken("jti claim must not be null"));
|
||||
}
|
||||
|
||||
if (logoutClaims.getSubject() == null && logoutClaims.getSessionId() == null) {
|
||||
errors.add(invalidLogoutToken("sub and sid claims must not both be null"));
|
||||
}
|
||||
|
||||
if (logoutClaims.getClaim("nonce") != null) {
|
||||
errors.add(invalidLogoutToken("nonce claim must not be present"));
|
||||
}
|
||||
|
||||
return OAuth2TokenValidatorResult.failure(errors);
|
||||
}
|
||||
|
||||
private static OAuth2Error invalidLogoutToken(String description) {
|
||||
return new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, description, LOGOUT_VALIDATION_URL);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link AuthenticationConverter} that extracts the OIDC Logout Token authentication
|
||||
* request
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
*/
|
||||
final class OidcLogoutAuthenticationConverter implements AuthenticationConverter {
|
||||
|
||||
private static final String DEFAULT_LOGOUT_URI = "/logout/connect/back-channel/{registrationId}";
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final ClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
private RequestMatcher requestMatcher = new AntPathRequestMatcher(DEFAULT_LOGOUT_URI, "POST");
|
||||
|
||||
OidcLogoutAuthenticationConverter(ClientRegistrationRepository clientRegistrationRepository) {
|
||||
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
|
||||
this.clientRegistrationRepository = clientRegistrationRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication convert(HttpServletRequest request) {
|
||||
RequestMatcher.MatchResult result = this.requestMatcher.matcher(request);
|
||||
if (!result.isMatch()) {
|
||||
return null;
|
||||
}
|
||||
String registrationId = result.getVariables().get("registrationId");
|
||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
|
||||
if (clientRegistration == null) {
|
||||
this.logger.debug("Did not process OIDC Back-Channel Logout since no ClientRegistration was found");
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
|
||||
}
|
||||
String logoutToken = request.getParameter("logout_token");
|
||||
if (logoutToken == null) {
|
||||
this.logger.debug("Failed to process OIDC Back-Channel Logout since no logout token was found");
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
|
||||
}
|
||||
return new OidcLogoutAuthenticationToken(logoutToken, clientRegistration);
|
||||
}
|
||||
|
||||
/**
|
||||
* The logout endpoint. Defaults to
|
||||
* {@code /logout/connect/back-channel/{registrationId}}.
|
||||
* @param requestMatcher the {@link RequestMatcher} to use
|
||||
*/
|
||||
void setRequestMatcher(RequestMatcher requestMatcher) {
|
||||
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
|
||||
this.requestMatcher = requestMatcher;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
|
||||
/**
|
||||
* An {@link org.springframework.security.core.Authentication} instance that represents a
|
||||
* request to authenticate an OIDC Logout Token.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
*/
|
||||
class OidcLogoutAuthenticationToken extends AbstractAuthenticationToken {
|
||||
|
||||
private final String logoutToken;
|
||||
|
||||
private final ClientRegistration clientRegistration;
|
||||
|
||||
/**
|
||||
* Construct an {@link OidcLogoutAuthenticationToken}
|
||||
* @param logoutToken a signed, serialized OIDC Logout token
|
||||
* @param clientRegistration the {@link ClientRegistration client} associated with
|
||||
* this token; this is usually derived from material in the logout HTTP request
|
||||
*/
|
||||
OidcLogoutAuthenticationToken(String logoutToken, ClientRegistration clientRegistration) {
|
||||
super(AuthorityUtils.NO_AUTHORITIES);
|
||||
this.logoutToken = logoutToken;
|
||||
this.clientRegistration = clientRegistration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String getCredentials() {
|
||||
return this.logoutToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String getPrincipal() {
|
||||
return this.logoutToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the signed, serialized OIDC Logout token
|
||||
* @return the logout token
|
||||
*/
|
||||
String getLogoutToken() {
|
||||
return this.logoutToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link ClientRegistration} associated with this logout token
|
||||
* @return the {@link ClientRegistration}
|
||||
*/
|
||||
ClientRegistration getClientRegistration() {
|
||||
return this.clientRegistration;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,159 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.ProviderManager;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.oauth2.client.oidc.session.OidcSessionRegistry;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.logout.LogoutHandler;
|
||||
import org.springframework.security.web.csrf.CsrfFilter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link AbstractHttpConfigurer} for OIDC Logout flows
|
||||
*
|
||||
* <p>
|
||||
* OIDC Logout provides an application with the capability to have users log out by using
|
||||
* their existing account at an OAuth 2.0 or OpenID Connect 1.0 Provider.
|
||||
*
|
||||
*
|
||||
* <h2>Security Filters</h2>
|
||||
*
|
||||
* The following {@code Filter} is populated:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link OidcBackChannelLogoutFilter}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Shared Objects Used</h2>
|
||||
*
|
||||
* The following shared objects are used:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link ClientRegistrationRepository}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
* @see HttpSecurity#oidcLogout()
|
||||
* @see OidcBackChannelLogoutFilter
|
||||
* @see ClientRegistrationRepository
|
||||
*/
|
||||
public final class OidcLogoutConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
extends AbstractHttpConfigurer<OidcLogoutConfigurer<B>, B> {
|
||||
|
||||
private BackChannelLogoutConfigurer backChannel;
|
||||
|
||||
/**
|
||||
* Sets the repository of client registrations.
|
||||
* @param clientRegistrationRepository the repository of client registrations
|
||||
* @return the {@link OAuth2LoginConfigurer} for further configuration
|
||||
*/
|
||||
public OidcLogoutConfigurer<B> clientRegistrationRepository(
|
||||
ClientRegistrationRepository clientRegistrationRepository) {
|
||||
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
|
||||
this.getBuilder().setSharedObject(ClientRegistrationRepository.class, clientRegistrationRepository);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the registry for managing the OIDC client-provider session link
|
||||
* @param oidcSessionRegistry the {@link OidcSessionRegistry} to use
|
||||
* @return the {@link OAuth2LoginConfigurer} for further configuration
|
||||
*/
|
||||
public OidcLogoutConfigurer<B> oidcSessionRegistry(OidcSessionRegistry oidcSessionRegistry) {
|
||||
Assert.notNull(oidcSessionRegistry, "oidcSessionRegistry cannot be null");
|
||||
getBuilder().setSharedObject(OidcSessionRegistry.class, oidcSessionRegistry);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure OIDC Back-Channel Logout using the provided {@link Consumer}
|
||||
* @return the {@link OidcLogoutConfigurer} for further configuration
|
||||
*/
|
||||
public OidcLogoutConfigurer<B> backChannel(Customizer<BackChannelLogoutConfigurer> backChannelLogoutConfigurer) {
|
||||
if (this.backChannel == null) {
|
||||
this.backChannel = new BackChannelLogoutConfigurer();
|
||||
}
|
||||
backChannelLogoutConfigurer.customize(this.backChannel);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true, since = "6.2")
|
||||
public B and() {
|
||||
return getBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(B builder) throws Exception {
|
||||
if (this.backChannel != null) {
|
||||
this.backChannel.configure(builder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A configurer for configuring OIDC Back-Channel Logout
|
||||
*/
|
||||
public final class BackChannelLogoutConfigurer {
|
||||
|
||||
private AuthenticationConverter authenticationConverter;
|
||||
|
||||
private final AuthenticationManager authenticationManager = new ProviderManager(
|
||||
new OidcBackChannelLogoutAuthenticationProvider());
|
||||
|
||||
private LogoutHandler logoutHandler;
|
||||
|
||||
private AuthenticationConverter authenticationConverter(B http) {
|
||||
if (this.authenticationConverter == null) {
|
||||
ClientRegistrationRepository clientRegistrationRepository = OAuth2ClientConfigurerUtils
|
||||
.getClientRegistrationRepository(http);
|
||||
this.authenticationConverter = new OidcLogoutAuthenticationConverter(clientRegistrationRepository);
|
||||
}
|
||||
return this.authenticationConverter;
|
||||
}
|
||||
|
||||
private AuthenticationManager authenticationManager() {
|
||||
return this.authenticationManager;
|
||||
}
|
||||
|
||||
private LogoutHandler logoutHandler(B http) {
|
||||
if (this.logoutHandler == null) {
|
||||
OidcBackChannelLogoutHandler logoutHandler = new OidcBackChannelLogoutHandler();
|
||||
logoutHandler.setSessionRegistry(OAuth2ClientConfigurerUtils.getOidcSessionRegistry(http));
|
||||
this.logoutHandler = logoutHandler;
|
||||
}
|
||||
return this.logoutHandler;
|
||||
}
|
||||
|
||||
void configure(B http) {
|
||||
OidcBackChannelLogoutFilter filter = new OidcBackChannelLogoutFilter(authenticationConverter(http),
|
||||
authenticationManager());
|
||||
filter.setLogoutHandler(logoutHandler(http));
|
||||
http.addFilterBefore(filter, CsrfFilter.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -52,7 +52,6 @@ import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
|
|||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatchers;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
|
@ -115,9 +114,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
|
||||
private Saml2AuthenticationRequestResolver authenticationRequestResolver;
|
||||
|
||||
private RequestMatcher loginProcessingUrl = RequestMatchers.anyOf(
|
||||
new AntPathRequestMatcher(Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI),
|
||||
new AntPathRequestMatcher("/login/saml2/sso"));
|
||||
private String loginProcessingUrl = Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI;
|
||||
|
||||
private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
|
||||
|
||||
|
@ -217,7 +214,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
@Override
|
||||
public Saml2LoginConfigurer<B> loginProcessingUrl(String loginProcessingUrl) {
|
||||
Assert.hasText(loginProcessingUrl, "loginProcessingUrl cannot be empty");
|
||||
this.loginProcessingUrl = new AntPathRequestMatcher(loginProcessingUrl);
|
||||
this.loginProcessingUrl = loginProcessingUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -243,11 +240,12 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
public void init(B http) throws Exception {
|
||||
registerDefaultCsrfOverride(http);
|
||||
relyingPartyRegistrationRepository(http);
|
||||
this.saml2WebSsoAuthenticationFilter = new Saml2WebSsoAuthenticationFilter(getAuthenticationConverter(http));
|
||||
this.saml2WebSsoAuthenticationFilter = new Saml2WebSsoAuthenticationFilter(getAuthenticationConverter(http),
|
||||
this.loginProcessingUrl);
|
||||
this.saml2WebSsoAuthenticationFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
|
||||
this.saml2WebSsoAuthenticationFilter.setRequiresAuthenticationRequestMatcher(this.loginProcessingUrl);
|
||||
setAuthenticationRequestRepository(http, this.saml2WebSsoAuthenticationFilter);
|
||||
setAuthenticationFilter(this.saml2WebSsoAuthenticationFilter);
|
||||
super.loginProcessingUrl(this.loginProcessingUrl);
|
||||
if (StringUtils.hasText(this.loginPage)) {
|
||||
// Set custom login page
|
||||
super.loginPage(this.loginPage);
|
||||
|
@ -354,7 +352,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
OpenSamlAuthenticationTokenConverter converter = new OpenSamlAuthenticationTokenConverter(
|
||||
this.relyingPartyRegistrationRepository);
|
||||
converter.setAuthenticationRequestRepository(getAuthenticationRequestRepository(http));
|
||||
converter.setRequestMatcher(this.loginProcessingUrl);
|
||||
converter.setRequestMatcher(createLoginProcessingUrlMatcher(this.loginProcessingUrl));
|
||||
return converter;
|
||||
}
|
||||
return authenticationConverterBean;
|
||||
|
@ -372,7 +370,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
|||
if (csrf == null) {
|
||||
return;
|
||||
}
|
||||
csrf.ignoringRequestMatchers(this.loginProcessingUrl);
|
||||
csrf.ignoringRequestMatchers(new AntPathRequestMatcher(this.loginProcessingUrl));
|
||||
}
|
||||
|
||||
private void initDefaultLoginFilter(B http) {
|
||||
|
|
|
@ -68,7 +68,8 @@ public class RsaKeyConversionServicePostProcessor implements BeanFactoryPostProc
|
|||
return;
|
||||
}
|
||||
ConversionService service = beanFactory.getConversionService();
|
||||
if (service instanceof ConverterRegistry registry) {
|
||||
if (service instanceof ConverterRegistry) {
|
||||
ConverterRegistry registry = (ConverterRegistry) service;
|
||||
registry.addConverter(String.class, RSAPrivateKey.class, this.pkcs8);
|
||||
registry.addConverter(String.class, RSAPublicKey.class, this.x509);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -421,8 +421,6 @@ final class AuthenticationConfigBuilder {
|
|||
this.pc.getReaderContext()
|
||||
.registerWithGeneratedName(new RootBeanDefinition(OAuth2ClientWebMvcSecurityPostProcessor.class));
|
||||
}
|
||||
this.pc.getReaderContext()
|
||||
.registerWithGeneratedName(new RootBeanDefinition(OAuth2AuthorizedClientManagerRegistrar.class));
|
||||
}
|
||||
|
||||
private void createSaml2LoginFilter(BeanReference authenticationManager,
|
||||
|
|
|
@ -42,12 +42,19 @@ public final class ChannelAttributeFactory {
|
|||
}
|
||||
|
||||
public static List<ConfigAttribute> createChannelAttributes(String requiredChannel) {
|
||||
String channelConfigAttribute = switch (requiredChannel) {
|
||||
case OPT_REQUIRES_HTTPS -> "REQUIRES_SECURE_CHANNEL";
|
||||
case OPT_REQUIRES_HTTP -> "REQUIRES_INSECURE_CHANNEL";
|
||||
case OPT_ANY_CHANNEL -> ChannelDecisionManagerImpl.ANY_CHANNEL;
|
||||
default -> throw new BeanCreationException("Unknown channel attribute " + requiredChannel);
|
||||
};
|
||||
String channelConfigAttribute;
|
||||
if (requiredChannel.equals(OPT_REQUIRES_HTTPS)) {
|
||||
channelConfigAttribute = "REQUIRES_SECURE_CHANNEL";
|
||||
}
|
||||
else if (requiredChannel.equals(OPT_REQUIRES_HTTP)) {
|
||||
channelConfigAttribute = "REQUIRES_INSECURE_CHANNEL";
|
||||
}
|
||||
else if (requiredChannel.equals(OPT_ANY_CHANNEL)) {
|
||||
channelConfigAttribute = ChannelDecisionManagerImpl.ANY_CHANNEL;
|
||||
}
|
||||
else {
|
||||
throw new BeanCreationException("Unknown channel attribute " + requiredChannel);
|
||||
}
|
||||
return SecurityConfig.createList(channelConfigAttribute);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -43,6 +43,7 @@ import org.springframework.security.web.csrf.CsrfAuthenticationStrategy;
|
|||
import org.springframework.security.web.csrf.CsrfFilter;
|
||||
import org.springframework.security.web.csrf.CsrfLogoutHandler;
|
||||
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
|
||||
import org.springframework.security.web.csrf.LazyCsrfTokenRepository;
|
||||
import org.springframework.security.web.csrf.MissingCsrfTokenException;
|
||||
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
|
||||
import org.springframework.security.web.session.InvalidSessionAccessDeniedHandler;
|
||||
|
@ -108,12 +109,13 @@ public class CsrfBeanDefinitionParser implements BeanDefinitionParser {
|
|||
this.requestHandlerRef = element.getAttribute(ATT_REQUEST_HANDLER);
|
||||
}
|
||||
if (!StringUtils.hasText(this.csrfRepositoryRef)) {
|
||||
BeanDefinitionBuilder httpSessionCsrfTokenRepository = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(HttpSessionCsrfTokenRepository.class);
|
||||
this.csrfRepositoryRef = pc.getReaderContext()
|
||||
.generateBeanName(httpSessionCsrfTokenRepository.getBeanDefinition());
|
||||
pc.registerBeanComponent(new BeanComponentDefinition(httpSessionCsrfTokenRepository.getBeanDefinition(),
|
||||
this.csrfRepositoryRef));
|
||||
RootBeanDefinition csrfTokenRepository = new RootBeanDefinition(HttpSessionCsrfTokenRepository.class);
|
||||
BeanDefinitionBuilder lazyTokenRepository = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(LazyCsrfTokenRepository.class);
|
||||
lazyTokenRepository.addConstructorArgValue(csrfTokenRepository);
|
||||
this.csrfRepositoryRef = pc.getReaderContext().generateBeanName(lazyTokenRepository.getBeanDefinition());
|
||||
pc.registerBeanComponent(
|
||||
new BeanComponentDefinition(lazyTokenRepository.getBeanDefinition(), this.csrfRepositoryRef));
|
||||
}
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(CsrfFilter.class);
|
||||
builder.addConstructorArgReference(this.csrfRepositoryRef);
|
||||
|
|
|
@ -1,287 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.http;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.BeanInitializationException;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
||||
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.security.oauth2.client.AuthorizationCodeOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.DelegatingOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.JwtBearerOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||
|
||||
/**
|
||||
* A registrar for registering the default {@link OAuth2AuthorizedClientManager} bean
|
||||
* definition, if not already present.
|
||||
* <p>
|
||||
* Note: This class is a direct copy of
|
||||
* {@link org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration.OAuth2AuthorizedClientManagerRegistrar}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Steve Riesenberg
|
||||
* @since 6.2.0
|
||||
*/
|
||||
final class OAuth2AuthorizedClientManagerRegistrar implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware {
|
||||
|
||||
// @formatter:off
|
||||
private static final Set<Class<?>> KNOWN_AUTHORIZED_CLIENT_PROVIDERS = Set.of(
|
||||
AuthorizationCodeOAuth2AuthorizedClientProvider.class,
|
||||
RefreshTokenOAuth2AuthorizedClientProvider.class,
|
||||
ClientCredentialsOAuth2AuthorizedClientProvider.class,
|
||||
PasswordOAuth2AuthorizedClientProvider.class,
|
||||
JwtBearerOAuth2AuthorizedClientProvider.class
|
||||
);
|
||||
// @formatter:on
|
||||
|
||||
private final AnnotationBeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
|
||||
|
||||
private ListableBeanFactory beanFactory;
|
||||
|
||||
@Override
|
||||
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
|
||||
if (getBeanNamesForType(OAuth2AuthorizedClientManager.class).length != 0
|
||||
|| getBeanNamesForType(ClientRegistrationRepository.class).length != 1
|
||||
|| getBeanNamesForType(OAuth2AuthorizedClientRepository.class).length != 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
BeanDefinition beanDefinition = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(OAuth2AuthorizedClientManager.class, this::getAuthorizedClientManager)
|
||||
.getBeanDefinition();
|
||||
|
||||
registry.registerBeanDefinition(this.beanNameGenerator.generateBeanName(beanDefinition, registry),
|
||||
beanDefinition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = (ListableBeanFactory) beanFactory;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientManager getAuthorizedClientManager() {
|
||||
ClientRegistrationRepository clientRegistrationRepository = BeanFactoryUtils
|
||||
.beanOfTypeIncludingAncestors(this.beanFactory, ClientRegistrationRepository.class, true, true);
|
||||
|
||||
OAuth2AuthorizedClientRepository authorizedClientRepository = BeanFactoryUtils
|
||||
.beanOfTypeIncludingAncestors(this.beanFactory, OAuth2AuthorizedClientRepository.class, true, true);
|
||||
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviderBeans = BeanFactoryUtils
|
||||
.beansOfTypeIncludingAncestors(this.beanFactory, OAuth2AuthorizedClientProvider.class, true, true)
|
||||
.values();
|
||||
|
||||
OAuth2AuthorizedClientProvider authorizedClientProvider;
|
||||
if (hasDelegatingAuthorizedClientProvider(authorizedClientProviderBeans)) {
|
||||
authorizedClientProvider = authorizedClientProviderBeans.iterator().next();
|
||||
}
|
||||
else {
|
||||
List<OAuth2AuthorizedClientProvider> authorizedClientProviders = new ArrayList<>();
|
||||
authorizedClientProviders.add(getAuthorizationCodeAuthorizedClientProvider(authorizedClientProviderBeans));
|
||||
authorizedClientProviders.add(getRefreshTokenAuthorizedClientProvider(authorizedClientProviderBeans));
|
||||
authorizedClientProviders.add(getClientCredentialsAuthorizedClientProvider(authorizedClientProviderBeans));
|
||||
authorizedClientProviders.add(getPasswordAuthorizedClientProvider(authorizedClientProviderBeans));
|
||||
|
||||
OAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider = getJwtBearerAuthorizedClientProvider(
|
||||
authorizedClientProviderBeans);
|
||||
if (jwtBearerAuthorizedClientProvider != null) {
|
||||
authorizedClientProviders.add(jwtBearerAuthorizedClientProvider);
|
||||
}
|
||||
|
||||
authorizedClientProviders.addAll(getAdditionalAuthorizedClientProviders(authorizedClientProviderBeans));
|
||||
authorizedClientProvider = new DelegatingOAuth2AuthorizedClientProvider(authorizedClientProviders);
|
||||
}
|
||||
|
||||
DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
|
||||
clientRegistrationRepository, authorizedClientRepository);
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
|
||||
Consumer<DefaultOAuth2AuthorizedClientManager> authorizedClientManagerConsumer = getBeanOfType(
|
||||
ResolvableType.forClassWithGenerics(Consumer.class, DefaultOAuth2AuthorizedClientManager.class));
|
||||
if (authorizedClientManagerConsumer != null) {
|
||||
authorizedClientManagerConsumer.accept(authorizedClientManager);
|
||||
}
|
||||
|
||||
return authorizedClientManager;
|
||||
}
|
||||
|
||||
private boolean hasDelegatingAuthorizedClientProvider(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
if (authorizedClientProviders.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
return authorizedClientProviders.iterator().next() instanceof DelegatingOAuth2AuthorizedClientProvider;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientProvider getAuthorizationCodeAuthorizedClientProvider(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
AuthorizationCodeOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
|
||||
authorizedClientProviders, AuthorizationCodeOAuth2AuthorizedClientProvider.class);
|
||||
if (authorizedClientProvider == null) {
|
||||
authorizedClientProvider = new AuthorizationCodeOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientProvider getRefreshTokenAuthorizedClientProvider(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
RefreshTokenOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
|
||||
authorizedClientProviders, RefreshTokenOAuth2AuthorizedClientProvider.class);
|
||||
if (authorizedClientProvider == null) {
|
||||
authorizedClientProvider = new RefreshTokenOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
|
||||
OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient = getBeanOfType(
|
||||
ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
|
||||
OAuth2RefreshTokenGrantRequest.class));
|
||||
if (accessTokenResponseClient != null) {
|
||||
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
|
||||
}
|
||||
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientProvider getClientCredentialsAuthorizedClientProvider(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
ClientCredentialsOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
|
||||
authorizedClientProviders, ClientCredentialsOAuth2AuthorizedClientProvider.class);
|
||||
if (authorizedClientProvider == null) {
|
||||
authorizedClientProvider = new ClientCredentialsOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
|
||||
OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient = getBeanOfType(
|
||||
ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
|
||||
OAuth2ClientCredentialsGrantRequest.class));
|
||||
if (accessTokenResponseClient != null) {
|
||||
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
|
||||
}
|
||||
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientProvider getPasswordAuthorizedClientProvider(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
PasswordOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
|
||||
authorizedClientProviders, PasswordOAuth2AuthorizedClientProvider.class);
|
||||
if (authorizedClientProvider == null) {
|
||||
authorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
|
||||
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient = getBeanOfType(
|
||||
ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
|
||||
OAuth2PasswordGrantRequest.class));
|
||||
if (accessTokenResponseClient != null) {
|
||||
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
|
||||
}
|
||||
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizedClientProvider getJwtBearerAuthorizedClientProvider(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
|
||||
authorizedClientProviders, JwtBearerOAuth2AuthorizedClientProvider.class);
|
||||
|
||||
OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> accessTokenResponseClient = getBeanOfType(ResolvableType
|
||||
.forClassWithGenerics(OAuth2AccessTokenResponseClient.class, JwtBearerGrantRequest.class));
|
||||
if (accessTokenResponseClient != null) {
|
||||
if (authorizedClientProvider == null) {
|
||||
authorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider();
|
||||
}
|
||||
|
||||
authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
|
||||
}
|
||||
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private List<OAuth2AuthorizedClientProvider> getAdditionalAuthorizedClientProviders(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
|
||||
List<OAuth2AuthorizedClientProvider> additionalAuthorizedClientProviders = new ArrayList<>(
|
||||
authorizedClientProviders);
|
||||
additionalAuthorizedClientProviders
|
||||
.removeIf((provider) -> KNOWN_AUTHORIZED_CLIENT_PROVIDERS.contains(provider.getClass()));
|
||||
return additionalAuthorizedClientProviders;
|
||||
}
|
||||
|
||||
private <T extends OAuth2AuthorizedClientProvider> T getAuthorizedClientProviderByType(
|
||||
Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders, Class<T> providerClass) {
|
||||
T authorizedClientProvider = null;
|
||||
for (OAuth2AuthorizedClientProvider current : authorizedClientProviders) {
|
||||
if (providerClass.isInstance(current)) {
|
||||
assertAuthorizedClientProviderIsNull(authorizedClientProvider);
|
||||
authorizedClientProvider = providerClass.cast(current);
|
||||
}
|
||||
}
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
private static void assertAuthorizedClientProviderIsNull(OAuth2AuthorizedClientProvider authorizedClientProvider) {
|
||||
if (authorizedClientProvider != null) {
|
||||
// @formatter:off
|
||||
throw new BeanInitializationException(String.format(
|
||||
"Unable to create an %s bean. Expected one bean of type %s, but found multiple. " +
|
||||
"Please consider defining only a single bean of this type, or define an %s bean yourself.",
|
||||
OAuth2AuthorizedClientManager.class.getName(),
|
||||
authorizedClientProvider.getClass().getName(),
|
||||
OAuth2AuthorizedClientManager.class.getName()));
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
private <T> String[] getBeanNamesForType(Class<T> beanClass) {
|
||||
return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, beanClass, false, false);
|
||||
}
|
||||
|
||||
private <T> T getBeanOfType(ResolvableType resolvableType) {
|
||||
ObjectProvider<T> objectProvider = this.beanFactory.getBeanProvider(resolvableType, true);
|
||||
return objectProvider.getIfAvailable();
|
||||
}
|
||||
|
||||
}
|
|
@ -94,7 +94,7 @@ final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser {
|
|||
.rootBeanDefinition(OAuth2AuthorizationRequestRedirectFilter.class);
|
||||
String authorizationRequestResolverRef = (authorizationCodeGrantElt != null)
|
||||
? authorizationCodeGrantElt.getAttribute(ATT_AUTHORIZATION_REQUEST_RESOLVER_REF) : null;
|
||||
if (StringUtils.hasLength(authorizationRequestResolverRef)) {
|
||||
if (!StringUtils.isEmpty(authorizationRequestResolverRef)) {
|
||||
authorizationRequestRedirectFilterBuilder.addConstructorArgReference(authorizationRequestResolverRef);
|
||||
}
|
||||
else {
|
||||
|
@ -125,7 +125,7 @@ final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser {
|
|||
private BeanMetadataElement getAuthorizationRequestRepository(Element element) {
|
||||
String authorizationRequestRepositoryRef = (element != null)
|
||||
? element.getAttribute(ATT_AUTHORIZATION_REQUEST_REPOSITORY_REF) : null;
|
||||
if (StringUtils.hasLength(authorizationRequestRepositoryRef)) {
|
||||
if (!StringUtils.isEmpty(authorizationRequestRepositoryRef)) {
|
||||
return new RuntimeBeanReference(authorizationRequestRepositoryRef);
|
||||
}
|
||||
return BeanDefinitionBuilder
|
||||
|
@ -147,7 +147,7 @@ final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser {
|
|||
private BeanMetadataElement getAccessTokenResponseClient(Element element) {
|
||||
String accessTokenResponseClientRef = (element != null)
|
||||
? element.getAttribute(ATT_ACCESS_TOKEN_RESPONSE_CLIENT_REF) : null;
|
||||
if (StringUtils.hasLength(accessTokenResponseClientRef)) {
|
||||
if (!StringUtils.isEmpty(accessTokenResponseClientRef)) {
|
||||
return new RuntimeBeanReference(accessTokenResponseClientRef);
|
||||
}
|
||||
return BeanDefinitionBuilder
|
||||
|
|
|
@ -42,7 +42,7 @@ final class OAuth2ClientBeanDefinitionParserUtils {
|
|||
|
||||
static BeanMetadataElement getClientRegistrationRepository(Element element) {
|
||||
String clientRegistrationRepositoryRef = element.getAttribute(ATT_CLIENT_REGISTRATION_REPOSITORY_REF);
|
||||
if (StringUtils.hasLength(clientRegistrationRepositoryRef)) {
|
||||
if (!StringUtils.isEmpty(clientRegistrationRepositoryRef)) {
|
||||
return new RuntimeBeanReference(clientRegistrationRepositoryRef);
|
||||
}
|
||||
return new RuntimeBeanReference(ClientRegistrationRepository.class);
|
||||
|
@ -50,7 +50,7 @@ final class OAuth2ClientBeanDefinitionParserUtils {
|
|||
|
||||
static BeanMetadataElement getAuthorizedClientRepository(Element element) {
|
||||
String authorizedClientRepositoryRef = element.getAttribute(ATT_AUTHORIZED_CLIENT_REPOSITORY_REF);
|
||||
if (StringUtils.hasLength(authorizedClientRepositoryRef)) {
|
||||
if (!StringUtils.isEmpty(authorizedClientRepositoryRef)) {
|
||||
return new RuntimeBeanReference(authorizedClientRepositoryRef);
|
||||
}
|
||||
return null;
|
||||
|
@ -58,7 +58,7 @@ final class OAuth2ClientBeanDefinitionParserUtils {
|
|||
|
||||
static BeanMetadataElement getAuthorizedClientService(Element element) {
|
||||
String authorizedClientServiceRef = element.getAttribute(ATT_AUTHORIZED_CLIENT_SERVICE_REF);
|
||||
if (StringUtils.hasLength(authorizedClientServiceRef)) {
|
||||
if (!StringUtils.isEmpty(authorizedClientServiceRef)) {
|
||||
return new RuntimeBeanReference(authorizedClientServiceRef);
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -176,7 +176,7 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
|
|||
}
|
||||
Object source = parserContext.extractSource(element);
|
||||
String loginProcessingUrl = element.getAttribute(ATT_LOGIN_PROCESSING_URL);
|
||||
if (StringUtils.hasLength(loginProcessingUrl)) {
|
||||
if (!StringUtils.isEmpty(loginProcessingUrl)) {
|
||||
WebConfigUtils.validateHttpRedirect(loginProcessingUrl, parserContext, source);
|
||||
oauth2LoginAuthenticationFilterBuilder.addConstructorArgValue(loginProcessingUrl);
|
||||
}
|
||||
|
@ -189,7 +189,7 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
|
|||
.addConstructorArgValue(accessTokenResponseClient)
|
||||
.addConstructorArgValue(oauth2UserService);
|
||||
String userAuthoritiesMapperRef = element.getAttribute(ATT_USER_AUTHORITIES_MAPPER_REF);
|
||||
if (StringUtils.hasLength(userAuthoritiesMapperRef)) {
|
||||
if (!StringUtils.isEmpty(userAuthoritiesMapperRef)) {
|
||||
oauth2LoginAuthenticationProviderBuilder.addPropertyReference("authoritiesMapper",
|
||||
userAuthoritiesMapperRef);
|
||||
}
|
||||
|
@ -199,7 +199,7 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
|
|||
BeanDefinitionBuilder oauth2AuthorizationRequestRedirectFilterBuilder = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(OAuth2AuthorizationRequestRedirectFilter.class);
|
||||
String authorizationRequestResolverRef = element.getAttribute(ATT_AUTHORIZATION_REQUEST_RESOLVER_REF);
|
||||
if (StringUtils.hasLength(authorizationRequestResolverRef)) {
|
||||
if (!StringUtils.isEmpty(authorizationRequestResolverRef)) {
|
||||
oauth2AuthorizationRequestRedirectFilterBuilder.addConstructorArgReference(authorizationRequestResolverRef);
|
||||
}
|
||||
else {
|
||||
|
@ -212,7 +212,7 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
|
|||
this.oauth2AuthorizationRequestRedirectFilter = oauth2AuthorizationRequestRedirectFilterBuilder
|
||||
.getBeanDefinition();
|
||||
String authenticationSuccessHandlerRef = element.getAttribute(ATT_AUTHENTICATION_SUCCESS_HANDLER_REF);
|
||||
if (StringUtils.hasLength(authenticationSuccessHandlerRef)) {
|
||||
if (!StringUtils.isEmpty(authenticationSuccessHandlerRef)) {
|
||||
oauth2LoginAuthenticationFilterBuilder.addPropertyReference("authenticationSuccessHandler",
|
||||
authenticationSuccessHandlerRef);
|
||||
}
|
||||
|
@ -225,7 +225,7 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
|
|||
successHandlerBuilder.getBeanDefinition());
|
||||
}
|
||||
String loginPage = element.getAttribute(ATT_LOGIN_PAGE);
|
||||
if (StringUtils.hasLength(loginPage)) {
|
||||
if (!StringUtils.isEmpty(loginPage)) {
|
||||
WebConfigUtils.validateHttpRedirect(loginPage, parserContext, source);
|
||||
this.oauth2LoginAuthenticationEntryPoint = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(LoginUrlAuthenticationEntryPoint.class)
|
||||
|
@ -245,7 +245,7 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
|
|||
}
|
||||
}
|
||||
String authenticationFailureHandlerRef = element.getAttribute(ATT_AUTHENTICATION_FAILURE_HANDLER_REF);
|
||||
if (StringUtils.hasLength(authenticationFailureHandlerRef)) {
|
||||
if (!StringUtils.isEmpty(authenticationFailureHandlerRef)) {
|
||||
oauth2LoginAuthenticationFilterBuilder.addPropertyReference("authenticationFailureHandler",
|
||||
authenticationFailureHandlerRef);
|
||||
}
|
||||
|
@ -269,7 +269,7 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
|
|||
|
||||
private BeanMetadataElement getAuthorizationRequestRepository(Element element) {
|
||||
String authorizationRequestRepositoryRef = element.getAttribute(ATT_AUTHORIZATION_REQUEST_REPOSITORY_REF);
|
||||
if (StringUtils.hasLength(authorizationRequestRepositoryRef)) {
|
||||
if (!StringUtils.isEmpty(authorizationRequestRepositoryRef)) {
|
||||
return new RuntimeBeanReference(authorizationRequestRepositoryRef);
|
||||
}
|
||||
return BeanDefinitionBuilder
|
||||
|
@ -299,11 +299,11 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
|
|||
"org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider")
|
||||
.addConstructorArgValue(accessTokenResponseClient)
|
||||
.addConstructorArgValue(oidcUserService);
|
||||
if (StringUtils.hasLength(userAuthoritiesMapperRef)) {
|
||||
if (!StringUtils.isEmpty(userAuthoritiesMapperRef)) {
|
||||
oidcAuthProviderBuilder.addPropertyReference("authoritiesMapper", userAuthoritiesMapperRef);
|
||||
}
|
||||
String jwtDecoderFactoryRef = element.getAttribute(ATT_JWT_DECODER_FACTORY_REF);
|
||||
if (StringUtils.hasLength(jwtDecoderFactoryRef)) {
|
||||
if (!StringUtils.isEmpty(jwtDecoderFactoryRef)) {
|
||||
oidcAuthProviderBuilder.addPropertyReference("jwtDecoderFactory", jwtDecoderFactoryRef);
|
||||
}
|
||||
return oidcAuthProviderBuilder.getBeanDefinition();
|
||||
|
@ -311,7 +311,7 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
|
|||
|
||||
private BeanMetadataElement getOidcUserService(Element element) {
|
||||
String oidcUserServiceRef = element.getAttribute(ATT_OIDC_USER_SERVICE_REF);
|
||||
if (StringUtils.hasLength(oidcUserServiceRef)) {
|
||||
if (!StringUtils.isEmpty(oidcUserServiceRef)) {
|
||||
return new RuntimeBeanReference(oidcUserServiceRef);
|
||||
}
|
||||
return BeanDefinitionBuilder
|
||||
|
@ -321,7 +321,7 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
|
|||
|
||||
private BeanMetadataElement getOAuth2UserService(Element element) {
|
||||
String oauth2UserServiceRef = element.getAttribute(ATT_USER_SERVICE_REF);
|
||||
if (StringUtils.hasLength(oauth2UserServiceRef)) {
|
||||
if (!StringUtils.isEmpty(oauth2UserServiceRef)) {
|
||||
return new RuntimeBeanReference(oauth2UserServiceRef);
|
||||
}
|
||||
return BeanDefinitionBuilder
|
||||
|
@ -331,7 +331,7 @@ final class OAuth2LoginBeanDefinitionParser implements BeanDefinitionParser {
|
|||
|
||||
private BeanMetadataElement getAccessTokenResponseClient(Element element) {
|
||||
String accessTokenResponseClientRef = element.getAttribute(ATT_ACCESS_TOKEN_RESPONSE_CLIENT_REF);
|
||||
if (StringUtils.hasLength(accessTokenResponseClientRef)) {
|
||||
if (!StringUtils.isEmpty(accessTokenResponseClientRef)) {
|
||||
return new RuntimeBeanReference(accessTokenResponseClientRef);
|
||||
}
|
||||
return BeanDefinitionBuilder
|
||||
|
|
|
@ -166,7 +166,7 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
|
|||
|
||||
BeanMetadataElement getAuthenticationManagerResolver(Element element) {
|
||||
String authenticationManagerResolverRef = element.getAttribute(AUTHENTICATION_MANAGER_RESOLVER_REF);
|
||||
if (StringUtils.hasLength(authenticationManagerResolverRef)) {
|
||||
if (!StringUtils.isEmpty(authenticationManagerResolverRef)) {
|
||||
return new RuntimeBeanReference(authenticationManagerResolverRef);
|
||||
}
|
||||
BeanDefinitionBuilder authenticationManagerResolver = BeanDefinitionBuilder
|
||||
|
@ -177,7 +177,7 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
|
|||
|
||||
BeanMetadataElement getBearerTokenResolver(Element element) {
|
||||
String bearerTokenResolverRef = element.getAttribute(BEARER_TOKEN_RESOLVER_REF);
|
||||
if (!StringUtils.hasLength(bearerTokenResolverRef)) {
|
||||
if (StringUtils.isEmpty(bearerTokenResolverRef)) {
|
||||
return new RootBeanDefinition(DefaultBearerTokenResolver.class);
|
||||
}
|
||||
return new RuntimeBeanReference(bearerTokenResolverRef);
|
||||
|
@ -185,7 +185,7 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
|
|||
|
||||
BeanMetadataElement getEntryPoint(Element element) {
|
||||
String entryPointRef = element.getAttribute(ENTRY_POINT_REF);
|
||||
if (!StringUtils.hasLength(entryPointRef)) {
|
||||
if (StringUtils.isEmpty(entryPointRef)) {
|
||||
return this.authenticationEntryPoint;
|
||||
}
|
||||
return new RuntimeBeanReference(entryPointRef);
|
||||
|
@ -224,7 +224,7 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
|
|||
|
||||
Object getDecoder(Element element) {
|
||||
String decoderRef = element.getAttribute(DECODER_REF);
|
||||
if (StringUtils.hasLength(decoderRef)) {
|
||||
if (!StringUtils.isEmpty(decoderRef)) {
|
||||
return new RuntimeBeanReference(decoderRef);
|
||||
}
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder
|
||||
|
@ -235,7 +235,7 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
|
|||
|
||||
Object getJwtAuthenticationConverter(Element element) {
|
||||
String jwtDecoderRef = element.getAttribute(JWT_AUTHENTICATION_CONVERTER_REF);
|
||||
return (StringUtils.hasLength(jwtDecoderRef)) ? new RuntimeBeanReference(jwtDecoderRef)
|
||||
return (!StringUtils.isEmpty(jwtDecoderRef)) ? new RuntimeBeanReference(jwtDecoderRef)
|
||||
: new JwtAuthenticationConverter();
|
||||
}
|
||||
|
||||
|
@ -293,7 +293,7 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
|
|||
|
||||
BeanMetadataElement getIntrospector(Element element) {
|
||||
String introspectorRef = element.getAttribute(INTROSPECTOR_REF);
|
||||
if (StringUtils.hasLength(introspectorRef)) {
|
||||
if (!StringUtils.isEmpty(introspectorRef)) {
|
||||
return new RuntimeBeanReference(introspectorRef);
|
||||
}
|
||||
String introspectionUri = element.getAttribute(INTROSPECTION_URI);
|
||||
|
|
|
@ -174,7 +174,7 @@ public final class ClientRegistrationsBeanDefinitionParser implements BeanDefini
|
|||
if (providers.containsKey(providerId)) {
|
||||
Map<String, String> provider = providers.get(providerId);
|
||||
String issuer = provider.get(ATT_ISSUER_URI);
|
||||
if (StringUtils.hasLength(issuer)) {
|
||||
if (!StringUtils.isEmpty(issuer)) {
|
||||
ClientRegistration.Builder builder = ClientRegistrations.fromIssuerLocation(issuer)
|
||||
.registrationId(registrationId);
|
||||
return getBuilder(parserContext, builder, provider);
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.web.server;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
|
||||
|
||||
final class DefaultOidcLogoutTokenValidatorFactory implements Function<ClientRegistration, OAuth2TokenValidator<Jwt>> {
|
||||
|
||||
@Override
|
||||
public OAuth2TokenValidator<Jwt> apply(ClientRegistration clientRegistration) {
|
||||
return new DelegatingOAuth2TokenValidator<>(new JwtTimestampValidator(),
|
||||
new OidcBackChannelLogoutTokenValidator(clientRegistration));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.web.server;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken;
|
||||
|
||||
/**
|
||||
* An {@link org.springframework.security.core.Authentication} implementation that
|
||||
* represents the result of authenticating an OIDC Logout token for the purposes of
|
||||
* performing Back-Channel Logout.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
* @see OidcLogoutAuthenticationToken
|
||||
* @see <a target="_blank" href=
|
||||
* "https://openid.net/specs/openid-connect-backchannel-1_0.html">OIDC Back-Channel
|
||||
* Logout</a>
|
||||
*/
|
||||
class OidcBackChannelLogoutAuthentication extends AbstractAuthenticationToken {
|
||||
|
||||
private final OidcLogoutToken logoutToken;
|
||||
|
||||
/**
|
||||
* Construct an {@link OidcBackChannelLogoutAuthentication}
|
||||
* @param logoutToken a deserialized, verified OIDC Logout Token
|
||||
*/
|
||||
OidcBackChannelLogoutAuthentication(OidcLogoutToken logoutToken) {
|
||||
super(Collections.emptyList());
|
||||
this.logoutToken = logoutToken;
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public OidcLogoutToken getPrincipal() {
|
||||
return this.logoutToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public OidcLogoutToken getCredentials() {
|
||||
return this.logoutToken;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.web.server;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.ReactiveOidcIdTokenDecoderFactory;
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.jwt.BadJwtException;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
|
||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link AuthenticationProvider} that authenticates an OIDC Logout Token; namely
|
||||
* deserializing it, verifying its signature, and validating its claims.
|
||||
*
|
||||
* <p>
|
||||
* Intended to be included in a
|
||||
* {@link org.springframework.security.authentication.ProviderManager}
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
* @see OidcLogoutAuthenticationToken
|
||||
* @see org.springframework.security.authentication.ProviderManager
|
||||
* @see <a target="_blank" href=
|
||||
* "https://openid.net/specs/openid-connect-backchannel-1_0.html">OIDC Back-Channel
|
||||
* Logout</a>
|
||||
*/
|
||||
final class OidcBackChannelLogoutReactiveAuthenticationManager implements ReactiveAuthenticationManager {
|
||||
|
||||
private ReactiveJwtDecoderFactory<ClientRegistration> logoutTokenDecoderFactory;
|
||||
|
||||
/**
|
||||
* Construct an {@link OidcBackChannelLogoutReactiveAuthenticationManager}
|
||||
*/
|
||||
OidcBackChannelLogoutReactiveAuthenticationManager() {
|
||||
ReactiveOidcIdTokenDecoderFactory logoutTokenDecoderFactory = new ReactiveOidcIdTokenDecoderFactory();
|
||||
logoutTokenDecoderFactory.setJwtValidatorFactory(new DefaultOidcLogoutTokenValidatorFactory());
|
||||
this.logoutTokenDecoderFactory = logoutTokenDecoderFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Mono<Authentication> authenticate(Authentication authentication) throws AuthenticationException {
|
||||
if (!(authentication instanceof OidcLogoutAuthenticationToken token)) {
|
||||
return Mono.empty();
|
||||
}
|
||||
String logoutToken = token.getLogoutToken();
|
||||
ClientRegistration registration = token.getClientRegistration();
|
||||
return decode(registration, logoutToken)
|
||||
.map((jwt) -> OidcLogoutToken.withTokenValue(logoutToken)
|
||||
.claims((claims) -> claims.putAll(jwt.getClaims()))
|
||||
.build())
|
||||
.map(OidcBackChannelLogoutAuthentication::new);
|
||||
}
|
||||
|
||||
private Mono<Jwt> decode(ClientRegistration registration, String token) {
|
||||
ReactiveJwtDecoder logoutTokenDecoder = this.logoutTokenDecoderFactory.createDecoder(registration);
|
||||
try {
|
||||
return logoutTokenDecoder.decode(token);
|
||||
}
|
||||
catch (BadJwtException failed) {
|
||||
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST, failed.getMessage(),
|
||||
"https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation");
|
||||
return Mono.error(new OAuth2AuthenticationException(error, failed));
|
||||
}
|
||||
catch (Exception failed) {
|
||||
return Mono.error(new AuthenticationServiceException(failed.getMessage(), failed));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@link ReactiveJwtDecoderFactory} to generate {@link JwtDecoder}s that
|
||||
* correspond to the {@link ClientRegistration} associated with the OIDC logout token.
|
||||
* @param logoutTokenDecoderFactory the {@link JwtDecoderFactory} to use
|
||||
*/
|
||||
void setLogoutTokenDecoderFactory(ReactiveJwtDecoderFactory<ClientRegistration> logoutTokenDecoderFactory) {
|
||||
Assert.notNull(logoutTokenDecoderFactory, "logoutTokenDecoderFactory cannot be null");
|
||||
this.logoutTokenDecoderFactory = logoutTokenDecoderFactory;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.web.server;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.logout.LogoutTokenClaimAccessor;
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
|
||||
/**
|
||||
* A {@link OAuth2TokenValidator} that validates OIDC Logout Token claims in conformance
|
||||
* with the OIDC Back-Channel Logout Spec.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
* @see OidcLogoutToken
|
||||
* @see <a target="_blank" href=
|
||||
* "https://openid.net/specs/openid-connect-backchannel-1_0.html#LogoutToken">Logout
|
||||
* Token</a>
|
||||
* @see <a target="blank" href=
|
||||
* "https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation">the OIDC
|
||||
* Back-Channel Logout spec</a>
|
||||
*/
|
||||
final class OidcBackChannelLogoutTokenValidator implements OAuth2TokenValidator<Jwt> {
|
||||
|
||||
private static final String LOGOUT_VALIDATION_URL = "https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation";
|
||||
|
||||
private static final String BACK_CHANNEL_LOGOUT_EVENT = "http://schemas.openid.net/event/backchannel-logout";
|
||||
|
||||
private final String audience;
|
||||
|
||||
private final String issuer;
|
||||
|
||||
OidcBackChannelLogoutTokenValidator(ClientRegistration clientRegistration) {
|
||||
this.audience = clientRegistration.getClientId();
|
||||
this.issuer = clientRegistration.getProviderDetails().getIssuerUri();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2TokenValidatorResult validate(Jwt jwt) {
|
||||
Collection<OAuth2Error> errors = new ArrayList<>();
|
||||
|
||||
LogoutTokenClaimAccessor logoutClaims = jwt::getClaims;
|
||||
Map<String, Object> events = logoutClaims.getEvents();
|
||||
if (events == null) {
|
||||
errors.add(invalidLogoutToken("events claim must not be null"));
|
||||
}
|
||||
else if (events.get(BACK_CHANNEL_LOGOUT_EVENT) == null) {
|
||||
errors.add(invalidLogoutToken("events claim map must contain \"" + BACK_CHANNEL_LOGOUT_EVENT + "\" key"));
|
||||
}
|
||||
|
||||
String issuer = logoutClaims.getIssuer().toExternalForm();
|
||||
if (issuer == null) {
|
||||
errors.add(invalidLogoutToken("iss claim must not be null"));
|
||||
}
|
||||
else if (!this.issuer.equals(issuer)) {
|
||||
errors.add(invalidLogoutToken(
|
||||
"iss claim value must match `ClientRegistration#getProviderDetails#getIssuerUri`"));
|
||||
}
|
||||
|
||||
List<String> audience = logoutClaims.getAudience();
|
||||
if (audience == null) {
|
||||
errors.add(invalidLogoutToken("aud claim must not be null"));
|
||||
}
|
||||
else if (!audience.contains(this.audience)) {
|
||||
errors.add(invalidLogoutToken("aud claim value must include `ClientRegistration#getClientId`"));
|
||||
}
|
||||
|
||||
Instant issuedAt = logoutClaims.getIssuedAt();
|
||||
if (issuedAt == null) {
|
||||
errors.add(invalidLogoutToken("iat claim must not be null"));
|
||||
}
|
||||
|
||||
String jwtId = logoutClaims.getId();
|
||||
if (jwtId == null) {
|
||||
errors.add(invalidLogoutToken("jti claim must not be null"));
|
||||
}
|
||||
|
||||
if (logoutClaims.getSubject() == null && logoutClaims.getSessionId() == null) {
|
||||
errors.add(invalidLogoutToken("sub and sid claims must not both be null"));
|
||||
}
|
||||
|
||||
if (logoutClaims.getClaim("nonce") != null) {
|
||||
errors.add(invalidLogoutToken("nonce claim must not be present"));
|
||||
}
|
||||
|
||||
return OAuth2TokenValidatorResult.failure(errors);
|
||||
}
|
||||
|
||||
private static OAuth2Error invalidLogoutToken(String description) {
|
||||
return new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, description, LOGOUT_VALIDATION_URL);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,137 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.web.server;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.logout.LogoutHandler;
|
||||
import org.springframework.security.web.server.WebFilterExchange;
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
|
||||
import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
|
||||
/**
|
||||
* A filter for the Client-side OIDC Back-Channel Logout endpoint
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
* @see <a target="_blank" href=
|
||||
* "https://openid.net/specs/openid-connect-backchannel-1_0.html">OIDC Back-Channel Logout
|
||||
* Spec</a>
|
||||
*/
|
||||
class OidcBackChannelLogoutWebFilter implements WebFilter {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final ServerAuthenticationConverter authenticationConverter;
|
||||
|
||||
private final ReactiveAuthenticationManager authenticationManager;
|
||||
|
||||
private ServerLogoutHandler logoutHandler = new OidcBackChannelServerLogoutHandler();
|
||||
|
||||
/**
|
||||
* Construct an {@link OidcBackChannelLogoutWebFilter}
|
||||
* @param authenticationConverter the {@link AuthenticationConverter} for deriving
|
||||
* Logout Token authentication
|
||||
* @param authenticationManager the {@link AuthenticationManager} for authenticating
|
||||
* Logout Tokens
|
||||
*/
|
||||
OidcBackChannelLogoutWebFilter(ServerAuthenticationConverter authenticationConverter,
|
||||
ReactiveAuthenticationManager authenticationManager) {
|
||||
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
|
||||
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
|
||||
this.authenticationConverter = authenticationConverter;
|
||||
this.authenticationManager = authenticationManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
return this.authenticationConverter.convert(exchange).onErrorResume(AuthenticationException.class, (ex) -> {
|
||||
this.logger.debug("Failed to process OIDC Back-Channel Logout", ex);
|
||||
if (ex instanceof AuthenticationServiceException) {
|
||||
return Mono.error(ex);
|
||||
}
|
||||
return handleAuthenticationFailure(exchange.getResponse(), ex).then(Mono.empty());
|
||||
})
|
||||
.switchIfEmpty(chain.filter(exchange).then(Mono.empty()))
|
||||
.flatMap(this.authenticationManager::authenticate)
|
||||
.onErrorResume(AuthenticationException.class, (ex) -> {
|
||||
this.logger.debug("Failed to process OIDC Back-Channel Logout", ex);
|
||||
if (ex instanceof AuthenticationServiceException) {
|
||||
return Mono.error(ex);
|
||||
}
|
||||
return handleAuthenticationFailure(exchange.getResponse(), ex).then(Mono.empty());
|
||||
})
|
||||
.flatMap((authentication) -> {
|
||||
WebFilterExchange webFilterExchange = new WebFilterExchange(exchange, chain);
|
||||
return this.logoutHandler.logout(webFilterExchange, authentication);
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<Void> handleAuthenticationFailure(ServerHttpResponse response, Exception ex) {
|
||||
this.logger.debug("Failed to process OIDC Back-Channel Logout", ex);
|
||||
response.setRawStatusCode(HttpServletResponse.SC_BAD_REQUEST);
|
||||
OAuth2Error error = oauth2Error(ex);
|
||||
byte[] bytes = String.format("""
|
||||
{
|
||||
"error_code": "%s",
|
||||
"error_description": "%s",
|
||||
"error_uri: "%s"
|
||||
}
|
||||
""", error.getErrorCode(), error.getDescription(), error.getUri()).getBytes(StandardCharsets.UTF_8);
|
||||
DataBuffer buffer = response.bufferFactory().wrap(bytes);
|
||||
return response.writeWith(Flux.just(buffer));
|
||||
}
|
||||
|
||||
private OAuth2Error oauth2Error(Exception ex) {
|
||||
if (ex instanceof OAuth2AuthenticationException oauth2) {
|
||||
return oauth2.getError();
|
||||
}
|
||||
return new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST, ex.getMessage(),
|
||||
"https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation");
|
||||
}
|
||||
|
||||
/**
|
||||
* The strategy for expiring all Client sessions indicated by the logout request.
|
||||
* Defaults to {@link OidcBackChannelServerLogoutHandler}.
|
||||
* @param logoutHandler the {@link LogoutHandler} to use
|
||||
*/
|
||||
void setLogoutHandler(ServerLogoutHandler logoutHandler) {
|
||||
Assert.notNull(logoutHandler, "logoutHandler cannot be null");
|
||||
this.logoutHandler = logoutHandler;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,181 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.web.server;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken;
|
||||
import org.springframework.security.oauth2.client.oidc.server.session.InMemoryReactiveOidcSessionRegistry;
|
||||
import org.springframework.security.oauth2.client.oidc.server.session.ReactiveOidcSessionRegistry;
|
||||
import org.springframework.security.oauth2.client.oidc.session.OidcSessionInformation;
|
||||
import org.springframework.security.oauth2.client.oidc.session.OidcSessionRegistry;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.web.server.WebFilterExchange;
|
||||
import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* A {@link ServerLogoutHandler} that locates the sessions associated with a given OIDC
|
||||
* Back-Channel Logout Token and invalidates each one.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
* @see <a target="_blank" href=
|
||||
* "https://openid.net/specs/openid-connect-backchannel-1_0.html">OIDC Back-Channel Logout
|
||||
* Spec</a>
|
||||
*/
|
||||
final class OidcBackChannelServerLogoutHandler implements ServerLogoutHandler {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private ReactiveOidcSessionRegistry sessionRegistry = new InMemoryReactiveOidcSessionRegistry();
|
||||
|
||||
private WebClient web = WebClient.create();
|
||||
|
||||
private String logoutEndpointName = "/logout";
|
||||
|
||||
private String sessionCookieName = "SESSION";
|
||||
|
||||
@Override
|
||||
public Mono<Void> logout(WebFilterExchange exchange, Authentication authentication) {
|
||||
if (!(authentication instanceof OidcBackChannelLogoutAuthentication token)) {
|
||||
return Mono.defer(() -> {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
String message = "Did not perform OIDC Back-Channel Logout since authentication [%s] was of the wrong type";
|
||||
this.logger.debug(String.format(message, authentication.getClass().getSimpleName()));
|
||||
}
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
AtomicInteger totalCount = new AtomicInteger(0);
|
||||
AtomicInteger invalidatedCount = new AtomicInteger(0);
|
||||
return this.sessionRegistry.removeSessionInformation(token.getPrincipal()).concatMap((session) -> {
|
||||
totalCount.incrementAndGet();
|
||||
return eachLogout(exchange, session).flatMap((response) -> {
|
||||
invalidatedCount.incrementAndGet();
|
||||
return Mono.empty();
|
||||
}).onErrorResume((ex) -> {
|
||||
this.logger.debug("Failed to invalidate session", ex);
|
||||
return this.sessionRegistry.saveSessionInformation(session).then(Mono.just(ex.getMessage()));
|
||||
});
|
||||
}).collectList().flatMap((list) -> {
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger.trace(String.format("Invalidated %d out of %d sessions", invalidatedCount.intValue(),
|
||||
totalCount.intValue()));
|
||||
}
|
||||
if (!list.isEmpty()) {
|
||||
return handleLogoutFailure(exchange.getExchange().getResponse(), oauth2Error(list));
|
||||
}
|
||||
else {
|
||||
return Mono.empty();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<ResponseEntity<Void>> eachLogout(WebFilterExchange exchange, OidcSessionInformation session) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add(HttpHeaders.COOKIE, this.sessionCookieName + "=" + session.getSessionId());
|
||||
for (Map.Entry<String, String> credential : session.getAuthorities().entrySet()) {
|
||||
headers.add(credential.getKey(), credential.getValue());
|
||||
}
|
||||
String url = exchange.getExchange().getRequest().getURI().toString();
|
||||
String logout = UriComponentsBuilder.fromHttpUrl(url)
|
||||
.replacePath(this.logoutEndpointName)
|
||||
.build()
|
||||
.toUriString();
|
||||
return this.web.post().uri(logout).headers((h) -> h.putAll(headers)).retrieve().toBodilessEntity();
|
||||
}
|
||||
|
||||
private OAuth2Error oauth2Error(Collection<?> errors) {
|
||||
return new OAuth2Error("partial_logout", "not all sessions were terminated: " + errors,
|
||||
"https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation");
|
||||
}
|
||||
|
||||
private Mono<Void> handleLogoutFailure(ServerHttpResponse response, OAuth2Error error) {
|
||||
response.setRawStatusCode(HttpServletResponse.SC_BAD_REQUEST);
|
||||
byte[] bytes = String.format("""
|
||||
{
|
||||
"error_code": "%s",
|
||||
"error_description": "%s",
|
||||
"error_uri: "%s"
|
||||
}
|
||||
""", error.getErrorCode(), error.getDescription(), error.getUri()).getBytes(StandardCharsets.UTF_8);
|
||||
DataBuffer buffer = response.bufferFactory().wrap(bytes);
|
||||
return response.writeWith(Flux.just(buffer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@link OidcSessionRegistry} to identify sessions to invalidate. Note that
|
||||
* this class uses
|
||||
* {@link OidcSessionRegistry#removeSessionInformation(OidcLogoutToken)} to identify
|
||||
* sessions.
|
||||
* @param sessionRegistry the {@link OidcSessionRegistry} to use
|
||||
*/
|
||||
void setSessionRegistry(ReactiveOidcSessionRegistry sessionRegistry) {
|
||||
Assert.notNull(sessionRegistry, "sessionRegistry cannot be null");
|
||||
this.sessionRegistry = sessionRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@link WebClient} to perform the per-session back-channel logout
|
||||
* @param web the {@link WebClient} to use
|
||||
*/
|
||||
void setWebClient(WebClient web) {
|
||||
Assert.notNull(web, "web cannot be null");
|
||||
this.web = web;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this logout URI for performing per-session logout. Defaults to {@code /logout}
|
||||
* since that is the default URI for
|
||||
* {@link org.springframework.security.web.authentication.logout.LogoutFilter}.
|
||||
* @param logoutUri the URI to use
|
||||
*/
|
||||
void setLogoutUri(String logoutUri) {
|
||||
Assert.hasText(logoutUri, "logoutUri cannot be empty");
|
||||
this.logoutEndpointName = logoutUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this cookie name for the session identifier. Defaults to {@code JSESSIONID}.
|
||||
*
|
||||
* <p>
|
||||
* Note that if you are using Spring Session, this likely needs to change to SESSION.
|
||||
* @param sessionCookieName the cookie name to use
|
||||
*/
|
||||
void setSessionCookieName(String sessionCookieName) {
|
||||
Assert.hasText(sessionCookieName, "clientSessionCookieName cannot be empty");
|
||||
this.sessionCookieName = sessionCookieName;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.web.server;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
|
||||
/**
|
||||
* An {@link org.springframework.security.core.Authentication} instance that represents a
|
||||
* request to authenticate an OIDC Logout Token.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
*/
|
||||
class OidcLogoutAuthenticationToken extends AbstractAuthenticationToken {
|
||||
|
||||
private final String logoutToken;
|
||||
|
||||
private final ClientRegistration clientRegistration;
|
||||
|
||||
/**
|
||||
* Construct an {@link OidcLogoutAuthenticationToken}
|
||||
* @param logoutToken a signed, serialized OIDC Logout token
|
||||
* @param clientRegistration the {@link ClientRegistration client} associated with
|
||||
* this token; this is usually derived from material in the logout HTTP request
|
||||
*/
|
||||
OidcLogoutAuthenticationToken(String logoutToken, ClientRegistration clientRegistration) {
|
||||
super(AuthorityUtils.NO_AUTHORITIES);
|
||||
this.logoutToken = logoutToken;
|
||||
this.clientRegistration = clientRegistration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String getCredentials() {
|
||||
return this.logoutToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String getPrincipal() {
|
||||
return this.logoutToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the signed, serialized OIDC Logout token
|
||||
* @return the logout token
|
||||
*/
|
||||
String getLogoutToken() {
|
||||
return this.logoutToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link ClientRegistration} associated with this logout token
|
||||
* @return the {@link ClientRegistration}
|
||||
*/
|
||||
ClientRegistration getClientRegistration() {
|
||||
return this.clientRegistration;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.web.server;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
|
||||
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* An {@link AuthenticationConverter} that extracts the OIDC Logout Token authentication
|
||||
* request
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
*/
|
||||
final class OidcLogoutServerAuthenticationConverter implements ServerAuthenticationConverter {
|
||||
|
||||
private static final String DEFAULT_LOGOUT_URI = "/logout/connect/back-channel/{registrationId}";
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final ReactiveClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
private ServerWebExchangeMatcher exchangeMatcher = new PathPatternParserServerWebExchangeMatcher(DEFAULT_LOGOUT_URI,
|
||||
HttpMethod.POST);
|
||||
|
||||
OidcLogoutServerAuthenticationConverter(ReactiveClientRegistrationRepository clientRegistrationRepository) {
|
||||
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
|
||||
this.clientRegistrationRepository = clientRegistrationRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Authentication> convert(ServerWebExchange exchange) {
|
||||
return this.exchangeMatcher.matches(exchange)
|
||||
.filter(ServerWebExchangeMatcher.MatchResult::isMatch)
|
||||
.flatMap((match) -> {
|
||||
String registrationId = (String) match.getVariables().get("registrationId");
|
||||
return this.clientRegistrationRepository.findByRegistrationId(registrationId)
|
||||
.switchIfEmpty(Mono.error(() -> {
|
||||
this.logger
|
||||
.debug("Did not process OIDC Back-Channel Logout since no ClientRegistration was found");
|
||||
return new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
|
||||
}));
|
||||
})
|
||||
.flatMap((clientRegistration) -> exchange.getFormData().map((data) -> {
|
||||
String logoutToken = data.getFirst("logout_token");
|
||||
return new OidcLogoutAuthenticationToken(logoutToken, clientRegistration);
|
||||
}).switchIfEmpty(Mono.error(() -> {
|
||||
this.logger.debug("Failed to process OIDC Back-Channel Logout since no logout token was found");
|
||||
return new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
|
||||
})));
|
||||
}
|
||||
|
||||
/**
|
||||
* The logout endpoint. Defaults to
|
||||
* {@code /logout/connect/back-channel/{registrationId}}.
|
||||
* @param exchangeMatcher the {@link ServerWebExchangeMatcher} to use
|
||||
*/
|
||||
void setExchangeMatcher(ServerWebExchangeMatcher exchangeMatcher) {
|
||||
Assert.notNull(exchangeMatcher, "exchangeMatcher cannot be null");
|
||||
this.exchangeMatcher = exchangeMatcher;
|
||||
}
|
||||
|
||||
}
|
|
@ -21,7 +21,6 @@ import java.io.PrintWriter;
|
|||
import java.io.StringWriter;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
@ -29,13 +28,10 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.context.Context;
|
||||
|
||||
|
@ -71,9 +67,6 @@ import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCo
|
|||
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.WebClientReactiveAuthorizationCodeTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeReactiveAuthenticationManager;
|
||||
import org.springframework.security.oauth2.client.oidc.server.session.InMemoryReactiveOidcSessionRegistry;
|
||||
import org.springframework.security.oauth2.client.oidc.server.session.ReactiveOidcSessionRegistry;
|
||||
import org.springframework.security.oauth2.client.oidc.session.OidcSessionInformation;
|
||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcReactiveOAuth2UserService;
|
||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
|
@ -120,7 +113,6 @@ import org.springframework.security.web.server.MatcherSecurityWebFilterChain;
|
|||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
|
||||
import org.springframework.security.web.server.ServerRedirectStrategy;
|
||||
import org.springframework.security.web.server.WebFilterExchange;
|
||||
import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilter;
|
||||
import org.springframework.security.web.server.authentication.AuthenticationConverterServerWebExchangeMatcher;
|
||||
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
|
||||
|
@ -155,7 +147,6 @@ import org.springframework.security.web.server.context.SecurityContextServerWebE
|
|||
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
|
||||
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
|
||||
import org.springframework.security.web.server.csrf.CsrfServerLogoutHandler;
|
||||
import org.springframework.security.web.server.csrf.CsrfToken;
|
||||
import org.springframework.security.web.server.csrf.CsrfWebFilter;
|
||||
import org.springframework.security.web.server.csrf.ServerCsrfTokenRepository;
|
||||
import org.springframework.security.web.server.csrf.ServerCsrfTokenRequestHandler;
|
||||
|
@ -202,10 +193,8 @@ import org.springframework.web.cors.reactive.CorsWebFilter;
|
|||
import org.springframework.web.cors.reactive.DefaultCorsProcessor;
|
||||
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.ServerWebExchangeDecorator;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import org.springframework.web.server.WebSession;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
|
||||
/**
|
||||
|
@ -306,8 +295,6 @@ public class ServerHttpSecurity {
|
|||
|
||||
private OAuth2ClientSpec client;
|
||||
|
||||
private OidcLogoutSpec oidcLogout;
|
||||
|
||||
private LogoutSpec logout = new LogoutSpec();
|
||||
|
||||
private LoginPageSpec loginPage = new LoginPageSpec();
|
||||
|
@ -1106,33 +1093,6 @@ public class ServerHttpSecurity {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures OIDC Connect 1.0 Logout support.
|
||||
*
|
||||
* <pre class="code">
|
||||
* @Bean
|
||||
* public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
* http
|
||||
* // ...
|
||||
* .oidcLogout((logout) -> logout
|
||||
* .backChannel(Customizer.withDefaults())
|
||||
* );
|
||||
* return http.build();
|
||||
* }
|
||||
* </pre>
|
||||
* @param oidcLogoutCustomizer the {@link Customizer} to provide more options for the
|
||||
* {@link OidcLogoutSpec}
|
||||
* @return the {@link ServerHttpSecurity} to customize
|
||||
* @since 6.2
|
||||
*/
|
||||
public ServerHttpSecurity oidcLogout(Customizer<OidcLogoutSpec> oidcLogoutCustomizer) {
|
||||
if (this.oidcLogout == null) {
|
||||
this.oidcLogout = new OidcLogoutSpec();
|
||||
}
|
||||
oidcLogoutCustomizer.customize(this.oidcLogout);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures HTTP Response Headers. The default headers are:
|
||||
*
|
||||
|
@ -1577,9 +1537,6 @@ public class ServerHttpSecurity {
|
|||
if (this.resourceServer != null) {
|
||||
this.resourceServer.configure(this);
|
||||
}
|
||||
if (this.oidcLogout != null) {
|
||||
this.oidcLogout.configure(this);
|
||||
}
|
||||
if (this.client != null) {
|
||||
this.client.configure(this);
|
||||
}
|
||||
|
@ -3733,8 +3690,6 @@ public class ServerHttpSecurity {
|
|||
|
||||
private ServerWebExchangeMatcher authenticationMatcher;
|
||||
|
||||
private ReactiveOidcSessionRegistry oidcSessionRegistry;
|
||||
|
||||
private ServerAuthenticationSuccessHandler authenticationSuccessHandler;
|
||||
|
||||
private ServerAuthenticationFailureHandler authenticationFailureHandler;
|
||||
|
@ -3766,20 +3721,6 @@ public class ServerHttpSecurity {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link ReactiveOidcSessionRegistry} to use when logins use OIDC.
|
||||
* Default is to look the value up as a Bean, or else use an
|
||||
* {@link InMemoryReactiveOidcSessionRegistry}.
|
||||
* @param oidcSessionRegistry the registry to use
|
||||
* @return the {@link OidcLogoutSpec} to customize
|
||||
* @since 6.2
|
||||
*/
|
||||
public OAuth2LoginSpec oidcSessionRegistry(ReactiveOidcSessionRegistry oidcSessionRegistry) {
|
||||
Assert.notNull(oidcSessionRegistry, "oidcSessionRegistry cannot be null");
|
||||
this.oidcSessionRegistry = oidcSessionRegistry;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link ServerAuthenticationSuccessHandler} used after authentication
|
||||
* success. Defaults to {@link RedirectServerAuthenticationSuccessHandler}
|
||||
|
@ -3973,9 +3914,8 @@ public class ServerHttpSecurity {
|
|||
oauthRedirectFilter.setRequestCache(http.requestCache.requestCache);
|
||||
|
||||
ReactiveAuthenticationManager manager = getAuthenticationManager();
|
||||
ReactiveOidcSessionRegistry sessionRegistry = getOidcSessionRegistry();
|
||||
AuthenticationWebFilter authenticationFilter = new OidcSessionRegistryAuthenticationWebFilter(manager,
|
||||
authorizedClientRepository, sessionRegistry);
|
||||
AuthenticationWebFilter authenticationFilter = new OAuth2LoginAuthenticationWebFilter(manager,
|
||||
authorizedClientRepository);
|
||||
authenticationFilter.setRequiresAuthenticationMatcher(getAuthenticationMatcher());
|
||||
authenticationFilter
|
||||
.setServerAuthenticationConverter(getAuthenticationConverter(clientRegistrationRepository));
|
||||
|
@ -3984,8 +3924,6 @@ public class ServerHttpSecurity {
|
|||
authenticationFilter.setSecurityContextRepository(this.securityContextRepository);
|
||||
|
||||
setDefaultEntryPoints(http);
|
||||
http.addFilterAfter(new OidcSessionRegistryWebFilter(sessionRegistry),
|
||||
SecurityWebFiltersOrder.HTTP_HEADERS_WRITER);
|
||||
http.addFilterAt(oauthRedirectFilter, SecurityWebFiltersOrder.HTTP_BASIC);
|
||||
http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION);
|
||||
}
|
||||
|
@ -4030,16 +3968,6 @@ public class ServerHttpSecurity {
|
|||
http.defaultEntryPoints.add(new DelegateEntry(defaultEntryPointMatcher, defaultEntryPoint));
|
||||
}
|
||||
|
||||
private ReactiveOidcSessionRegistry getOidcSessionRegistry() {
|
||||
if (this.oidcSessionRegistry == null) {
|
||||
this.oidcSessionRegistry = getBeanOrNull(ReactiveOidcSessionRegistry.class);
|
||||
}
|
||||
if (this.oidcSessionRegistry == null) {
|
||||
this.oidcSessionRegistry = new InMemoryReactiveOidcSessionRegistry();
|
||||
}
|
||||
return this.oidcSessionRegistry;
|
||||
}
|
||||
|
||||
private ServerAuthenticationSuccessHandler getAuthenticationSuccessHandler(ServerHttpSecurity http) {
|
||||
if (this.authenticationSuccessHandler == null) {
|
||||
RedirectServerAuthenticationSuccessHandler handler = new RedirectServerAuthenticationSuccessHandler();
|
||||
|
@ -4156,157 +4084,6 @@ public class ServerHttpSecurity {
|
|||
return new InMemoryReactiveOAuth2AuthorizedClientService(getClientRegistrationRepository());
|
||||
}
|
||||
|
||||
private static final class OidcSessionRegistryWebFilter implements WebFilter {
|
||||
|
||||
private final ReactiveOidcSessionRegistry oidcSessionRegistry;
|
||||
|
||||
OidcSessionRegistryWebFilter(ReactiveOidcSessionRegistry oidcSessionRegistry) {
|
||||
this.oidcSessionRegistry = oidcSessionRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
return chain.filter(new OidcSessionRegistryServerWebExchange(exchange));
|
||||
}
|
||||
|
||||
private final class OidcSessionRegistryServerWebExchange extends ServerWebExchangeDecorator {
|
||||
|
||||
private final Mono<WebSession> sessionMono;
|
||||
|
||||
protected OidcSessionRegistryServerWebExchange(ServerWebExchange delegate) {
|
||||
super(delegate);
|
||||
this.sessionMono = delegate.getSession().map(OidcSessionRegistryWebSession::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<WebSession> getSession() {
|
||||
return this.sessionMono;
|
||||
}
|
||||
|
||||
private final class OidcSessionRegistryWebSession implements WebSession {
|
||||
|
||||
private final WebSession session;
|
||||
|
||||
OidcSessionRegistryWebSession(WebSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return this.session.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAttributes() {
|
||||
return this.session.getAttributes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
this.session.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStarted() {
|
||||
return this.session.isStarted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> changeSessionId() {
|
||||
String currentId = this.session.getId();
|
||||
return this.session.changeSessionId()
|
||||
.then(Mono.defer(() -> OidcSessionRegistryWebFilter.this.oidcSessionRegistry
|
||||
.removeSessionInformation(currentId)
|
||||
.flatMap((information) -> {
|
||||
information = information.withSessionId(this.session.getId());
|
||||
return OidcSessionRegistryWebFilter.this.oidcSessionRegistry
|
||||
.saveSessionInformation(information);
|
||||
})));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> invalidate() {
|
||||
String currentId = this.session.getId();
|
||||
return this.session.invalidate()
|
||||
.then(Mono.defer(() -> OidcSessionRegistryWebFilter.this.oidcSessionRegistry
|
||||
.removeSessionInformation(currentId)
|
||||
.then(Mono.empty())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> save() {
|
||||
return this.session.save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpired() {
|
||||
return this.session.isExpired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getCreationTime() {
|
||||
return this.session.getCreationTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getLastAccessTime() {
|
||||
return this.session.getLastAccessTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxIdleTime(Duration maxIdleTime) {
|
||||
this.session.setMaxIdleTime(maxIdleTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getMaxIdleTime() {
|
||||
return this.session.getMaxIdleTime();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class OidcSessionRegistryAuthenticationWebFilter
|
||||
extends OAuth2LoginAuthenticationWebFilter {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final ReactiveOidcSessionRegistry oidcSessionRegistry;
|
||||
|
||||
OidcSessionRegistryAuthenticationWebFilter(ReactiveAuthenticationManager authenticationManager,
|
||||
ServerOAuth2AuthorizedClientRepository authorizedClientRepository,
|
||||
ReactiveOidcSessionRegistry oidcSessionRegistry) {
|
||||
super(authenticationManager, authorizedClientRepository);
|
||||
this.oidcSessionRegistry = oidcSessionRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<Void> onAuthenticationSuccess(Authentication authentication,
|
||||
WebFilterExchange webFilterExchange) {
|
||||
if (!(authentication.getPrincipal() instanceof OidcUser user)) {
|
||||
return super.onAuthenticationSuccess(authentication, webFilterExchange);
|
||||
}
|
||||
return webFilterExchange.getExchange().getSession().doOnNext((session) -> {
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger.trace(String.format("Linking a provider [%s] session to this client's session",
|
||||
user.getIssuer()));
|
||||
}
|
||||
}).flatMap((session) -> {
|
||||
Mono<CsrfToken> csrfToken = webFilterExchange.getExchange().getAttribute(CsrfToken.class.getName());
|
||||
return (csrfToken != null)
|
||||
? csrfToken.map((token) -> new OidcSessionInformation(session.getId(),
|
||||
Map.of(token.getHeaderName(), token.getToken()), user))
|
||||
: Mono.just(new OidcSessionInformation(session.getId(), Map.of(), user));
|
||||
})
|
||||
.flatMap(this.oidcSessionRegistry::saveSessionInformation)
|
||||
.then(super.onAuthenticationSuccess(authentication, webFilterExchange));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public final class OAuth2ClientSpec {
|
||||
|
@ -4979,129 +4756,6 @@ public class ServerHttpSecurity {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures OIDC 1.0 Logout support
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
*/
|
||||
public final class OidcLogoutSpec {
|
||||
|
||||
private ReactiveClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
private ReactiveOidcSessionRegistry sessionRegistry;
|
||||
|
||||
private BackChannelLogoutConfigurer backChannel;
|
||||
|
||||
/**
|
||||
* Configures the {@link ReactiveClientRegistrationRepository}. Default is to look
|
||||
* the value up as a Bean.
|
||||
* @param clientRegistrationRepository the repository to use
|
||||
* @return the {@link OidcLogoutSpec} to customize
|
||||
*/
|
||||
public OidcLogoutSpec clientRegistrationRepository(
|
||||
ReactiveClientRegistrationRepository clientRegistrationRepository) {
|
||||
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
|
||||
this.clientRegistrationRepository = clientRegistrationRepository;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link ReactiveOidcSessionRegistry}. Default is to use the value
|
||||
* from {@link OAuth2LoginSpec#oidcSessionRegistry}, then look the value up as a
|
||||
* Bean, or else use an {@link InMemoryReactiveOidcSessionRegistry}.
|
||||
* @param sessionRegistry the registry to use
|
||||
* @return the {@link OidcLogoutSpec} to customize
|
||||
*/
|
||||
public OidcLogoutSpec oidcSessionRegistry(ReactiveOidcSessionRegistry sessionRegistry) {
|
||||
Assert.notNull(sessionRegistry, "sessionRegistry cannot be null");
|
||||
this.sessionRegistry = sessionRegistry;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure OIDC Back-Channel Logout using the provided {@link Consumer}
|
||||
* @return the {@link OidcLogoutSpec} for further configuration
|
||||
*/
|
||||
public OidcLogoutSpec backChannel(Customizer<BackChannelLogoutConfigurer> backChannelLogoutConfigurer) {
|
||||
if (this.backChannel == null) {
|
||||
this.backChannel = new OidcLogoutSpec.BackChannelLogoutConfigurer();
|
||||
}
|
||||
backChannelLogoutConfigurer.customize(this.backChannel);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true, since = "6.2")
|
||||
public ServerHttpSecurity and() {
|
||||
return ServerHttpSecurity.this;
|
||||
}
|
||||
|
||||
void configure(ServerHttpSecurity http) {
|
||||
if (this.backChannel != null) {
|
||||
this.backChannel.configure(http);
|
||||
}
|
||||
}
|
||||
|
||||
private ReactiveClientRegistrationRepository getClientRegistrationRepository() {
|
||||
if (this.clientRegistrationRepository == null) {
|
||||
this.clientRegistrationRepository = getBeanOrNull(ReactiveClientRegistrationRepository.class);
|
||||
}
|
||||
return this.clientRegistrationRepository;
|
||||
}
|
||||
|
||||
private ReactiveOidcSessionRegistry getSessionRegistry() {
|
||||
if (this.sessionRegistry == null && ServerHttpSecurity.this.oauth2Login == null) {
|
||||
return new InMemoryReactiveOidcSessionRegistry();
|
||||
}
|
||||
if (this.sessionRegistry == null) {
|
||||
return ServerHttpSecurity.this.oauth2Login.oidcSessionRegistry;
|
||||
}
|
||||
return this.sessionRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* A configurer for configuring OIDC Back-Channel Logout
|
||||
*/
|
||||
public final class BackChannelLogoutConfigurer {
|
||||
|
||||
private ServerAuthenticationConverter authenticationConverter;
|
||||
|
||||
private final ReactiveAuthenticationManager authenticationManager = new OidcBackChannelLogoutReactiveAuthenticationManager();
|
||||
|
||||
private ServerLogoutHandler logoutHandler;
|
||||
|
||||
private ServerAuthenticationConverter authenticationConverter() {
|
||||
if (this.authenticationConverter == null) {
|
||||
this.authenticationConverter = new OidcLogoutServerAuthenticationConverter(
|
||||
OidcLogoutSpec.this.getClientRegistrationRepository());
|
||||
}
|
||||
return this.authenticationConverter;
|
||||
}
|
||||
|
||||
private ReactiveAuthenticationManager authenticationManager() {
|
||||
return this.authenticationManager;
|
||||
}
|
||||
|
||||
private ServerLogoutHandler logoutHandler() {
|
||||
if (this.logoutHandler == null) {
|
||||
OidcBackChannelServerLogoutHandler logoutHandler = new OidcBackChannelServerLogoutHandler();
|
||||
logoutHandler.setSessionRegistry(OidcLogoutSpec.this.getSessionRegistry());
|
||||
this.logoutHandler = logoutHandler;
|
||||
}
|
||||
return this.logoutHandler;
|
||||
}
|
||||
|
||||
void configure(ServerHttpSecurity http) {
|
||||
OidcBackChannelLogoutWebFilter filter = new OidcBackChannelLogoutWebFilter(authenticationConverter(),
|
||||
authenticationManager());
|
||||
filter.setLogoutHandler(logoutHandler());
|
||||
http.addFilterBefore(filter, SecurityWebFiltersOrder.CSRF);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures anonymous authentication
|
||||
*
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -16,9 +16,6 @@
|
|||
|
||||
package org.springframework.security.config.annotation.web
|
||||
|
||||
import jakarta.servlet.Filter
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.checkerframework.checker.units.qual.C
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.security.authentication.AuthenticationManager
|
||||
import org.springframework.security.config.annotation.SecurityConfigurerAdapter
|
||||
|
@ -27,6 +24,9 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
|
|||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository
|
||||
import org.springframework.security.web.DefaultSecurityFilterChain
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher
|
||||
import org.springframework.util.ClassUtils
|
||||
import jakarta.servlet.Filter
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
|
||||
/**
|
||||
* Configures [HttpSecurity] using a [HttpSecurity Kotlin DSL][HttpSecurityDsl].
|
||||
|
@ -107,36 +107,6 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
|
|||
return this.http.apply(configurer).apply(configuration)
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a [SecurityConfigurerAdapter] to this [HttpSecurity]
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @Configuration
|
||||
* @EnableWebSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
* http {
|
||||
* with(CustomSecurityConfigurer<HttpSecurity>()) {
|
||||
* customProperty = "..."
|
||||
* }
|
||||
* }
|
||||
* return http.build()
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param configurer
|
||||
* the [HttpSecurity] for further customizations
|
||||
* @since 6.2
|
||||
*/
|
||||
fun <C : SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> with(configurer: C, configuration: C.() -> Unit = { }): HttpSecurity? {
|
||||
return this.http.with(configurer, configuration)
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows configuring the [HttpSecurity] to only be invoked when matching the
|
||||
* provided pattern.
|
||||
|
@ -868,38 +838,6 @@ class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecu
|
|||
this.http.oauth2ResourceServer(oauth2ResourceServerCustomizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures OIDC 1.0 logout support.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @Configuration
|
||||
* @EnableWebSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
* http {
|
||||
* oauth2Login { }
|
||||
* oidcLogout {
|
||||
* backChannel { }
|
||||
* }
|
||||
* }
|
||||
* return http.build()
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param oidcLogoutConfiguration custom configuration to configure the
|
||||
* OIDC 1.0 logout support
|
||||
* @see [OidcLogoutDsl]
|
||||
*/
|
||||
fun oidcLogout(oidcLogoutConfiguration: OidcLogoutDsl.() -> Unit) {
|
||||
val oidcLogoutCustomizer = OidcLogoutDsl().apply(oidcLogoutConfiguration).get()
|
||||
this.http.oidcLogout(oidcLogoutCustomizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures Remember Me authentication.
|
||||
*
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web
|
||||
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OidcLogoutConfigurer
|
||||
import org.springframework.security.config.annotation.web.oauth2.login.OidcBackChannelLogoutDsl
|
||||
import org.springframework.security.oauth2.client.oidc.session.OidcSessionRegistry
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure [HttpSecurity] OAuth 1.0 Logout using idiomatic Kotlin code.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
*/
|
||||
@SecurityMarker
|
||||
class OidcLogoutDsl {
|
||||
var clientRegistrationRepository: ClientRegistrationRepository? = null
|
||||
var oidcSessionRegistry: OidcSessionRegistry? = null
|
||||
|
||||
private var backChannel: ((OidcLogoutConfigurer<HttpSecurity>.BackChannelLogoutConfigurer) -> Unit)? = null
|
||||
|
||||
/**
|
||||
* Configures the OIDC 1.0 Back-Channel endpoint.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @Configuration
|
||||
* @EnableWebSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
* http {
|
||||
* oauth2Login { }
|
||||
* oidcLogout {
|
||||
* backChannel { }
|
||||
* }
|
||||
* }
|
||||
* return http.build()
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param backChannelConfig custom configurations to configure the back-channel endpoint
|
||||
* @see [OidcBackChannelLogoutDsl]
|
||||
*/
|
||||
fun backChannel(backChannelConfig: OidcBackChannelLogoutDsl.() -> Unit) {
|
||||
this.backChannel = OidcBackChannelLogoutDsl().apply(backChannelConfig).get()
|
||||
}
|
||||
|
||||
internal fun get(): (OidcLogoutConfigurer<HttpSecurity>) -> Unit {
|
||||
return { oidcLogout ->
|
||||
clientRegistrationRepository?.also { oidcLogout.clientRegistrationRepository(clientRegistrationRepository) }
|
||||
oidcSessionRegistry?.also { oidcLogout.oidcSessionRegistry(oidcSessionRegistry) }
|
||||
backChannel?.also { oidcLogout.backChannel(backChannel) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.oauth2.login
|
||||
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OidcLogoutConfigurer
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure the OIDC 1.0 Back-Channel configuration using
|
||||
* idiomatic Kotlin code.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
*/
|
||||
@OAuth2LoginSecurityMarker
|
||||
class OidcBackChannelLogoutDsl {
|
||||
internal fun get(): (OidcLogoutConfigurer<HttpSecurity>.BackChannelLogoutConfigurer) -> Unit {
|
||||
return { backChannel -> }
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -650,38 +650,6 @@ class ServerHttpSecurityDsl(private val http: ServerHttpSecurity, private val in
|
|||
this.http.oauth2ResourceServer(oauth2ResourceServerCustomizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures logout support using an OpenID Connect 1.0 Provider.
|
||||
* A [ReactiveClientRegistrationRepository] is required and must be registered as a Bean or
|
||||
* configured via [ServerOidcLogoutDsl.clientRegistrationRepository].
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @Configuration
|
||||
* @EnableWebFluxSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
* return http {
|
||||
* oauth2Login { }
|
||||
* oidcLogout {
|
||||
* backChannel { }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param oidcLogoutConfiguration custom configuration to configure the OIDC 1.0 Logout
|
||||
* @see [ServerOidcLogoutDsl]
|
||||
*/
|
||||
fun oidcLogout(oidcLogoutConfiguration: ServerOidcLogoutDsl.() -> Unit) {
|
||||
val oidcLogoutCustomizer = ServerOidcLogoutDsl().apply(oidcLogoutConfiguration).get()
|
||||
this.http.oidcLogout(oidcLogoutCustomizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply all configurations to the provided [ServerHttpSecurity]
|
||||
*/
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.web.server
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure [ServerHttpSecurity] OIDC 1.0 Back-Channel Logout support using idiomatic Kotlin code.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
*/
|
||||
@ServerSecurityMarker
|
||||
class ServerOidcBackChannelLogoutDsl {
|
||||
internal fun get(): (ServerHttpSecurity.OidcLogoutSpec.BackChannelLogoutConfigurer) -> Unit {
|
||||
return { backChannel -> }
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.web.server
|
||||
|
||||
import org.springframework.security.oauth2.client.oidc.server.session.ReactiveOidcSessionRegistry
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure [ServerHttpSecurity] OIDC 1.0 login using idiomatic Kotlin code.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 6.2
|
||||
*/
|
||||
@ServerSecurityMarker
|
||||
class ServerOidcLogoutDsl {
|
||||
var clientRegistrationRepository: ReactiveClientRegistrationRepository? = null
|
||||
var oidcSessionRegistry: ReactiveOidcSessionRegistry? = null
|
||||
|
||||
private var backChannel: ((ServerHttpSecurity.OidcLogoutSpec.BackChannelLogoutConfigurer) -> Unit)? = null
|
||||
|
||||
/**
|
||||
* Enables OIDC 1.0 Back-Channel Logout support.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @Configuration
|
||||
* @EnableWebFluxSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
* return http {
|
||||
* oauth2Login { }
|
||||
* oidcLogout {
|
||||
* backChannel { }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param backChannelConfig custom configurations to configure OIDC 1.0 Back-Channel Logout support
|
||||
* @see [ServerOidcBackChannelLogoutDsl]
|
||||
*/
|
||||
fun backChannel(backChannelConfig: ServerOidcBackChannelLogoutDsl.() -> Unit) {
|
||||
this.backChannel = ServerOidcBackChannelLogoutDsl().apply(backChannelConfig).get()
|
||||
}
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.OidcLogoutSpec) -> Unit {
|
||||
return { oidcLogout ->
|
||||
clientRegistrationRepository?.also { oidcLogout.clientRegistrationRepository(clientRegistrationRepository) }
|
||||
oidcSessionRegistry?.also { oidcLogout.oidcSessionRegistry(oidcSessionRegistry) }
|
||||
backChannel?.also { oidcLogout.backChannel(backChannel) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
http\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-6.2.xsd
|
||||
http\://www.springframework.org/schema/security/spring-security-6.2.xsd=org/springframework/security/config/spring-security-6.2.xsd
|
||||
http\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-6.1.xsd
|
||||
http\://www.springframework.org/schema/security/spring-security-6.1.xsd=org/springframework/security/config/spring-security-6.1.xsd
|
||||
http\://www.springframework.org/schema/security/spring-security-6.0.xsd=org/springframework/security/config/spring-security-6.0.xsd
|
||||
http\://www.springframework.org/schema/security/spring-security-5.8.xsd=org/springframework/security/config/spring-security-5.8.xsd
|
||||
|
@ -22,8 +21,7 @@ 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.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
|
||||
https\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-6.2.xsd
|
||||
https\://www.springframework.org/schema/security/spring-security-6.2.xsd=org/springframework/security/config/spring-security-6.2.xsd
|
||||
https\://www.springframework.org/schema/security/spring-security.xsd=org/springframework/security/config/spring-security-6.1.xsd
|
||||
https\://www.springframework.org/schema/security/spring-security-6.1.xsd=org/springframework/security/config/spring-security-6.1.xsd
|
||||
https\://www.springframework.org/schema/security/spring-security-6.0.xsd=org/springframework/security/config/spring-security-6.0.xsd
|
||||
https\://www.springframework.org/schema/security/spring-security-5.8.xsd=org/springframework/security/config/spring-security-5.8.xsd
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -55,11 +55,6 @@ public class MockServletContext extends org.springframework.mock.web.MockServlet
|
|||
return this.registrations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletRegistration getServletRegistration(String servletName) {
|
||||
return this.registrations.get(servletName);
|
||||
}
|
||||
|
||||
private static class MockServletRegistration implements ServletRegistration.Dynamic {
|
||||
|
||||
private final String name;
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.MappingMatch;
|
||||
|
||||
import org.springframework.mock.web.MockHttpServletMapping;
|
||||
|
||||
public final class TestMockHttpServletMappings {
|
||||
|
||||
private TestMockHttpServletMappings() {
|
||||
|
||||
}
|
||||
|
||||
public static MockHttpServletMapping extension(HttpServletRequest request, String extension) {
|
||||
String uri = request.getRequestURI();
|
||||
String matchValue = uri.substring(0, uri.lastIndexOf(extension));
|
||||
return new MockHttpServletMapping(matchValue, "*" + extension, "extension", MappingMatch.EXTENSION);
|
||||
}
|
||||
|
||||
public static MockHttpServletMapping path(HttpServletRequest request, String path) {
|
||||
String uri = request.getRequestURI();
|
||||
String matchValue = uri.substring(path.length());
|
||||
return new MockHttpServletMapping(matchValue, path + "/*", "path", MappingMatch.PATH);
|
||||
}
|
||||
|
||||
public static MockHttpServletMapping defaultMapping() {
|
||||
return new MockHttpServletMapping("", "/", "default", MappingMatch.DEFAULT);
|
||||
}
|
||||
|
||||
}
|
|
@ -17,13 +17,11 @@
|
|||
package org.springframework.security.config.annotation.authentication.configurers.provisioning;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
|
@ -75,7 +73,7 @@ public class UserDetailsManagerConfigurerTests {
|
|||
.authorities(authority)
|
||||
.build();
|
||||
// @formatter:on
|
||||
assertThat((Optional<GrantedAuthority>) userDetails.getAuthorities().stream().findFirst()).contains(authority);
|
||||
assertThat(userDetails.getAuthorities().stream().findFirst().get()).isEqualTo(authority);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -101,7 +99,7 @@ public class UserDetailsManagerConfigurerTests {
|
|||
.authorities(Arrays.asList(authority))
|
||||
.build();
|
||||
// @formatter:on
|
||||
assertThat((Optional<GrantedAuthority>) userDetails.getAuthorities().stream().findFirst()).contains(authority);
|
||||
assertThat(userDetails.getAuthorities().stream().findFirst().get()).isEqualTo(authority);
|
||||
}
|
||||
|
||||
private UserDetailsManagerConfigurer<AuthenticationManagerBuilder, InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder>> configurer() {
|
||||
|
|
|
@ -66,7 +66,7 @@ public class SecurityConfig {
|
|||
|
||||
@Bean
|
||||
public AuthenticationProvider authenticationProvider() {
|
||||
Assert.notNull(this.myUserRepository, "myUserRepository cannot be null");
|
||||
Assert.notNull(this.myUserRepository);
|
||||
return new AuthenticationProvider() {
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.method.configuration;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.AdviceMode;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
/**
|
||||
* @author Evgeniy Cheban
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@EnableMethodSecurity
|
||||
public @interface EnableCustomMethodSecurity {
|
||||
|
||||
@AliasFor(annotation = EnableMethodSecurity.class, attribute = "proxyTargetClass")
|
||||
boolean proxyTargetClass() default false;
|
||||
|
||||
@AliasFor(annotation = EnableMethodSecurity.class, attribute = "mode")
|
||||
AdviceMode mode() default AdviceMode.PROXY;
|
||||
|
||||
}
|
|
@ -95,21 +95,6 @@ public class PrePostMethodSecurityConfigurationTests {
|
|||
@Autowired(required = false)
|
||||
BusinessService businessService;
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void customMethodSecurityPreAuthorizeAdminWhenRoleUserThenAccessDeniedException() {
|
||||
this.spring.register(CustomMethodSecurityServiceConfig.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::preAuthorizeAdmin)
|
||||
.withMessage("Access Denied");
|
||||
}
|
||||
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
@Test
|
||||
public void customMethodSecurityPreAuthorizeAdminWhenRoleAdminThenPasses() {
|
||||
this.spring.register(CustomMethodSecurityServiceConfig.class).autowire();
|
||||
this.methodSecurityService.preAuthorizeAdmin();
|
||||
}
|
||||
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
@Test
|
||||
public void preAuthorizeWhenRoleAdminThenAccessDeniedException() {
|
||||
|
@ -451,17 +436,6 @@ public class PrePostMethodSecurityConfigurationTests {
|
|||
return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableCustomMethodSecurity
|
||||
static class CustomMethodSecurityServiceConfig {
|
||||
|
||||
@Bean
|
||||
MethodSecurityService methodSecurityService() {
|
||||
return new MethodSecurityServiceImpl();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableMethodSecurity
|
||||
static class MethodSecurityServiceConfig {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
|
@ -21,7 +21,6 @@ import java.util.List;
|
|||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.SecurityConfigurer;
|
||||
|
@ -150,19 +149,6 @@ public class AbstractConfiguredSecurityBuilderTests {
|
|||
assertThat(builder.getConfigurers(DelegateSecurityConfigurer.class)).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withWhenConfigurerThenConfigurerAdded() throws Exception {
|
||||
this.builder.with(new TestSecurityConfigurer(), Customizer.withDefaults());
|
||||
assertThat(this.builder.getConfigurers(TestSecurityConfigurer.class)).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withWhenDuplicateConfigurerAddedThenDuplicateConfigurerRemoved() throws Exception {
|
||||
this.builder.with(new TestSecurityConfigurer(), Customizer.withDefaults());
|
||||
this.builder.with(new TestSecurityConfigurer(), Customizer.withDefaults());
|
||||
assertThat(this.builder.getConfigurers(TestSecurityConfigurer.class)).hasSize(1);
|
||||
}
|
||||
|
||||
private static class ApplyAndRemoveSecurityConfigurer
|
||||
extends SecurityConfigurerAdapter<Object, TestConfiguredSecurityBuilder> {
|
||||
|
||||
|
|
|
@ -25,12 +25,8 @@ import org.springframework.http.HttpMethod;
|
|||
import org.springframework.security.test.support.ClassPathExclusions;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link AbstractRequestMatcherRegistry} with no Spring MVC in the classpath
|
||||
|
@ -45,16 +41,13 @@ public class AbstractRequestMatcherRegistryNoMvcTests {
|
|||
@BeforeEach
|
||||
public void setUp() {
|
||||
this.matcherRegistry = new TestRequestMatcherRegistry();
|
||||
WebApplicationContext context = mock(WebApplicationContext.class);
|
||||
given(context.getBeanNamesForType((Class<?>) any())).willReturn(new String[0]);
|
||||
this.matcherRegistry.setApplicationContext(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestMatchersWhenPatternAndMvcNotPresentThenReturnAntPathRequestMatcherType() {
|
||||
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/path");
|
||||
assertThat(requestMatchers).isNotEmpty();
|
||||
assertThat(requestMatchers).hasSize(1);
|
||||
assertThat(requestMatchers.size()).isEqualTo(1);
|
||||
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
|
||||
}
|
||||
|
||||
|
@ -62,7 +55,7 @@ public class AbstractRequestMatcherRegistryNoMvcTests {
|
|||
public void requestMatchersWhenHttpMethodAndPatternAndMvcNotPresentThenReturnAntPathRequestMatcherType() {
|
||||
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(HttpMethod.GET, "/path");
|
||||
assertThat(requestMatchers).isNotEmpty();
|
||||
assertThat(requestMatchers).hasSize(1);
|
||||
assertThat(requestMatchers.size()).isEqualTo(1);
|
||||
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
|
||||
}
|
||||
|
||||
|
@ -70,7 +63,7 @@ public class AbstractRequestMatcherRegistryNoMvcTests {
|
|||
public void requestMatchersWhenHttpMethodAndMvcNotPresentThenReturnAntPathMatcherType() {
|
||||
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(HttpMethod.GET);
|
||||
assertThat(requestMatchers).isNotEmpty();
|
||||
assertThat(requestMatchers).hasSize(1);
|
||||
assertThat(requestMatchers.size()).isEqualTo(1);
|
||||
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -26,11 +26,8 @@ import org.junit.jupiter.api.Test;
|
|||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.security.config.MockServletContext;
|
||||
import org.springframework.security.config.TestMockHttpServletMappings;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry.DispatcherServletDelegatingRequestMatcher;
|
||||
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher;
|
||||
|
@ -43,9 +40,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link AbstractRequestMatcherRegistry}.
|
||||
|
@ -81,7 +75,7 @@ public class AbstractRequestMatcherRegistryTests {
|
|||
List<RequestMatcher> requestMatchers = this.matcherRegistry
|
||||
.requestMatchers(new RegexRequestMatcher("/a.*", HttpMethod.GET.name()));
|
||||
assertThat(requestMatchers).isNotEmpty();
|
||||
assertThat(requestMatchers).hasSize(1);
|
||||
assertThat(requestMatchers.size()).isEqualTo(1);
|
||||
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(RegexRequestMatcher.class);
|
||||
}
|
||||
|
||||
|
@ -90,7 +84,7 @@ public class AbstractRequestMatcherRegistryTests {
|
|||
List<RequestMatcher> requestMatchers = this.matcherRegistry
|
||||
.requestMatchers(new RegexRequestMatcher("/a.*", null));
|
||||
assertThat(requestMatchers).isNotEmpty();
|
||||
assertThat(requestMatchers).hasSize(1);
|
||||
assertThat(requestMatchers.size()).isEqualTo(1);
|
||||
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(RegexRequestMatcher.class);
|
||||
}
|
||||
|
||||
|
@ -99,7 +93,7 @@ public class AbstractRequestMatcherRegistryTests {
|
|||
List<RequestMatcher> requestMatchers = this.matcherRegistry
|
||||
.requestMatchers(new AntPathRequestMatcher("/a.*", HttpMethod.GET.name()));
|
||||
assertThat(requestMatchers).isNotEmpty();
|
||||
assertThat(requestMatchers).hasSize(1);
|
||||
assertThat(requestMatchers.size()).isEqualTo(1);
|
||||
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
|
||||
}
|
||||
|
||||
|
@ -107,7 +101,7 @@ public class AbstractRequestMatcherRegistryTests {
|
|||
public void antMatchersWhenPatternParamThenReturnAntPathRequestMatcherType() {
|
||||
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(new AntPathRequestMatcher("/a.*"));
|
||||
assertThat(requestMatchers).isNotEmpty();
|
||||
assertThat(requestMatchers).hasSize(1);
|
||||
assertThat(requestMatchers.size()).isEqualTo(1);
|
||||
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
|
||||
}
|
||||
|
||||
|
@ -116,7 +110,7 @@ public class AbstractRequestMatcherRegistryTests {
|
|||
List<RequestMatcher> requestMatchers = this.matcherRegistry.dispatcherTypeMatchers(HttpMethod.GET,
|
||||
DispatcherType.ASYNC);
|
||||
assertThat(requestMatchers).isNotEmpty();
|
||||
assertThat(requestMatchers).hasSize(1);
|
||||
assertThat(requestMatchers.size()).isEqualTo(1);
|
||||
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(DispatcherTypeRequestMatcher.class);
|
||||
}
|
||||
|
||||
|
@ -124,7 +118,7 @@ public class AbstractRequestMatcherRegistryTests {
|
|||
public void dispatcherMatchersWhenPatternParamThenReturnAntPathRequestMatcherType() {
|
||||
List<RequestMatcher> requestMatchers = this.matcherRegistry.dispatcherTypeMatchers(DispatcherType.INCLUDE);
|
||||
assertThat(requestMatchers).isNotEmpty();
|
||||
assertThat(requestMatchers).hasSize(1);
|
||||
assertThat(requestMatchers.size()).isEqualTo(1);
|
||||
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(DispatcherTypeRequestMatcher.class);
|
||||
}
|
||||
|
||||
|
@ -132,7 +126,7 @@ public class AbstractRequestMatcherRegistryTests {
|
|||
public void requestMatchersWhenPatternAndMvcPresentThenReturnMvcRequestMatcherType() {
|
||||
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/path");
|
||||
assertThat(requestMatchers).isNotEmpty();
|
||||
assertThat(requestMatchers).hasSize(1);
|
||||
assertThat(requestMatchers.size()).isEqualTo(1);
|
||||
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(MvcRequestMatcher.class);
|
||||
}
|
||||
|
||||
|
@ -140,7 +134,7 @@ public class AbstractRequestMatcherRegistryTests {
|
|||
public void requestMatchersWhenHttpMethodAndPatternAndMvcPresentThenReturnMvcRequestMatcherType() {
|
||||
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(HttpMethod.GET, "/path");
|
||||
assertThat(requestMatchers).isNotEmpty();
|
||||
assertThat(requestMatchers).hasSize(1);
|
||||
assertThat(requestMatchers.size()).isEqualTo(1);
|
||||
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(MvcRequestMatcher.class);
|
||||
}
|
||||
|
||||
|
@ -148,7 +142,7 @@ public class AbstractRequestMatcherRegistryTests {
|
|||
public void requestMatchersWhenHttpMethodAndMvcPresentThenReturnMvcRequestMatcherType() {
|
||||
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(HttpMethod.GET);
|
||||
assertThat(requestMatchers).isNotEmpty();
|
||||
assertThat(requestMatchers).hasSize(1);
|
||||
assertThat(requestMatchers.size()).isEqualTo(1);
|
||||
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(MvcRequestMatcher.class);
|
||||
}
|
||||
|
||||
|
@ -165,12 +159,16 @@ public class AbstractRequestMatcherRegistryTests {
|
|||
public void requestMatchersWhenNoDispatcherServletThenAntPathRequestMatcherType() {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
given(this.context.getServletContext()).willReturn(servletContext);
|
||||
servletContext.addServlet("servletOne", Servlet.class).addMapping("/one");
|
||||
servletContext.addServlet("servletTwo", Servlet.class).addMapping("/two");
|
||||
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/**");
|
||||
assertThat(requestMatchers).isNotEmpty();
|
||||
assertThat(requestMatchers).hasSize(1);
|
||||
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
|
||||
servletContext.addServlet("servletOne", Servlet.class);
|
||||
servletContext.addServlet("servletTwo", Servlet.class);
|
||||
requestMatchers = this.matcherRegistry.requestMatchers("/**");
|
||||
assertThat(requestMatchers).isNotEmpty();
|
||||
assertThat(requestMatchers).hasSize(1);
|
||||
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -178,26 +176,7 @@ public class AbstractRequestMatcherRegistryTests {
|
|||
MockServletContext servletContext = new MockServletContext();
|
||||
given(this.context.getServletContext()).willReturn(servletContext);
|
||||
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/");
|
||||
servletContext.addServlet("servletTwo", DispatcherServlet.class).addMapping("/servlet/*");
|
||||
assertThatExceptionOfType(IllegalArgumentException.class)
|
||||
.isThrownBy(() -> this.matcherRegistry.requestMatchers("/**"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestMatchersWhenMultipleDispatcherServletMappingsThenException() {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
given(this.context.getServletContext()).willReturn(servletContext);
|
||||
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/", "/mvc/*");
|
||||
assertThatExceptionOfType(IllegalArgumentException.class)
|
||||
.isThrownBy(() -> this.matcherRegistry.requestMatchers("/**"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestMatchersWhenPathDispatcherServletAndOtherServletsThenException() {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
given(this.context.getServletContext()).willReturn(servletContext);
|
||||
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
|
||||
servletContext.addServlet("default", Servlet.class).addMapping("/");
|
||||
servletContext.addServlet("servletTwo", Servlet.class).addMapping("/servlet/**");
|
||||
assertThatExceptionOfType(IllegalArgumentException.class)
|
||||
.isThrownBy(() -> this.matcherRegistry.requestMatchers("/**"));
|
||||
}
|
||||
|
@ -214,67 +193,6 @@ public class AbstractRequestMatcherRegistryTests {
|
|||
assertThat(requestMatchers.get(0)).isInstanceOf(MvcRequestMatcher.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestMatchersWhenOnlyDispatcherServletThenAllows() {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
given(this.context.getServletContext()).willReturn(servletContext);
|
||||
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
|
||||
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/**");
|
||||
assertThat(requestMatchers).hasSize(1);
|
||||
assertThat(requestMatchers.get(0)).isInstanceOf(MvcRequestMatcher.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestMatchersWhenImplicitServletsThenAllows() {
|
||||
mockMvcIntrospector(true);
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
given(this.context.getServletContext()).willReturn(servletContext);
|
||||
servletContext.addServlet("defaultServlet", Servlet.class);
|
||||
servletContext.addServlet("jspServlet", Servlet.class).addMapping("*.jsp", "*.jspx");
|
||||
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/");
|
||||
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/**");
|
||||
assertThat(requestMatchers).hasSize(1);
|
||||
assertThat(requestMatchers.get(0)).isInstanceOf(DispatcherServletDelegatingRequestMatcher.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestMatchersWhenPathBasedNonDispatcherServletThenAllows() {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
given(this.context.getServletContext()).willReturn(servletContext);
|
||||
servletContext.addServlet("path", Servlet.class).addMapping("/services/*");
|
||||
servletContext.addServlet("default", DispatcherServlet.class).addMapping("/");
|
||||
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/services/*");
|
||||
assertThat(requestMatchers).hasSize(1);
|
||||
assertThat(requestMatchers.get(0)).isInstanceOf(DispatcherServletDelegatingRequestMatcher.class);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/services/endpoint");
|
||||
request.setHttpServletMapping(TestMockHttpServletMappings.defaultMapping());
|
||||
assertThat(requestMatchers.get(0).matcher(request).isMatch()).isTrue();
|
||||
request.setHttpServletMapping(TestMockHttpServletMappings.path(request, "/services"));
|
||||
request.setServletPath("/services");
|
||||
request.setPathInfo("/endpoint");
|
||||
assertThat(requestMatchers.get(0).matcher(request).isMatch()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenDispatcherServletThenMvc() {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
servletContext.addServlet("default", DispatcherServlet.class).addMapping("/");
|
||||
servletContext.addServlet("path", Servlet.class).addMapping("/services/*");
|
||||
MvcRequestMatcher mvc = mock(MvcRequestMatcher.class);
|
||||
AntPathRequestMatcher ant = mock(AntPathRequestMatcher.class);
|
||||
DispatcherServletDelegatingRequestMatcher requestMatcher = new DispatcherServletDelegatingRequestMatcher(ant,
|
||||
mvc, servletContext);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/services/endpoint");
|
||||
request.setHttpServletMapping(TestMockHttpServletMappings.defaultMapping());
|
||||
assertThat(requestMatcher.matches(request)).isFalse();
|
||||
verify(mvc).matches(request);
|
||||
verifyNoInteractions(ant);
|
||||
request.setHttpServletMapping(TestMockHttpServletMappings.path(request, "/services"));
|
||||
assertThat(requestMatcher.matches(request)).isFalse();
|
||||
verify(ant).matches(request);
|
||||
verifyNoMoreInteractions(mvc);
|
||||
}
|
||||
|
||||
private void mockMvcIntrospector(boolean isPresent) {
|
||||
ApplicationContext context = this.matcherRegistry.getApplicationContext();
|
||||
given(context.containsBean("mvcHandlerMappingIntrospector")).willReturn(isPresent);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -47,7 +47,6 @@ import org.springframework.security.authentication.TestingAuthenticationToken;
|
|||
import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
|
||||
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;
|
||||
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
|
@ -64,11 +63,9 @@ import org.springframework.security.core.userdetails.UserDetailsService;
|
|||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.test.web.servlet.RequestCacheResultMatcher;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
|
||||
import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter;
|
||||
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||
|
@ -76,10 +73,6 @@ import org.springframework.web.accept.ContentNegotiationStrategy;
|
|||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
@ -92,8 +85,6 @@ import static org.springframework.security.test.web.servlet.request.SecurityMock
|
|||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
|
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
|
||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
|
@ -361,40 +352,6 @@ public class HttpSecurityConfigurationTests {
|
|||
DefaultLogoutPageGeneratingFilter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenCorsConfigurationSourceThenApplyCors() {
|
||||
this.spring.register(CorsConfigurationSourceConfig.class, DefaultWithFilterChainConfig.class).autowire();
|
||||
SecurityFilterChain filterChain = this.spring.getContext().getBean(SecurityFilterChain.class);
|
||||
CorsFilter corsFilter = (CorsFilter) filterChain.getFilters()
|
||||
.stream()
|
||||
.filter((f) -> f instanceof CorsFilter)
|
||||
.findFirst()
|
||||
.get();
|
||||
Object configSource = ReflectionTestUtils.getField(corsFilter, "configSource");
|
||||
assertThat(configSource).isInstanceOf(UrlBasedCorsConfigurationSource.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenAddingCustomDslUsingWithThenApplied() throws Exception {
|
||||
this.spring.register(WithCustomDslConfig.class, UserDetailsConfig.class).autowire();
|
||||
SecurityFilterChain filterChain = this.spring.getContext().getBean(SecurityFilterChain.class);
|
||||
List<Filter> filters = filterChain.getFilters();
|
||||
assertThat(filters).hasAtLeastOneElementOfType(UsernamePasswordAuthenticationFilter.class);
|
||||
this.mockMvc.perform(formLogin()).andExpectAll(redirectedUrl("/"), authenticated());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenCustomDslAddedFromFactoriesAndDisablingUsingWithThenNotApplied() throws Exception {
|
||||
this.springFactoriesLoader
|
||||
.when(() -> SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, getClass().getClassLoader()))
|
||||
.thenReturn(List.of(new WithCustomDsl()));
|
||||
this.spring.register(WithCustomDslDisabledConfig.class, UserDetailsConfig.class).autowire();
|
||||
SecurityFilterChain filterChain = this.spring.getContext().getBean(SecurityFilterChain.class);
|
||||
List<Filter> filters = filterChain.getFilters();
|
||||
assertThat(filters).doesNotHaveAnyElementsOfTypes(UsernamePasswordAuthenticationFilter.class);
|
||||
this.mockMvc.perform(formLogin()).andExpectAll(status().isNotFound(), unauthenticated());
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class NameController {
|
||||
|
||||
|
@ -659,20 +616,6 @@ public class HttpSecurityConfigurationTests {
|
|||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CorsConfigurationSourceConfig {
|
||||
|
||||
@Bean
|
||||
CorsConfigurationSource corsConfigurationSource() {
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
CorsConfiguration corsConfiguration = new CorsConfiguration();
|
||||
corsConfiguration.setAllowedOrigins(List.of("http://localhost:8080"));
|
||||
source.registerCorsConfiguration("/**", corsConfiguration);
|
||||
return source;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class DefaultConfigurer extends AbstractHttpConfigurer<DefaultConfigurer, HttpSecurity> {
|
||||
|
||||
boolean init;
|
||||
|
@ -691,45 +634,4 @@ public class HttpSecurityConfigurationTests {
|
|||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class WithCustomDslConfig {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.with(new WithCustomDsl(), Customizer.withDefaults())
|
||||
.httpBasic(Customizer.withDefaults());
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class WithCustomDslDisabledConfig {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.with(new WithCustomDsl(), (dsl) -> dsl.disable())
|
||||
.httpBasic(Customizer.withDefaults());
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class WithCustomDsl extends AbstractHttpConfigurer<WithCustomDsl, HttpSecurity> {
|
||||
|
||||
@Override
|
||||
public void init(HttpSecurity builder) throws Exception {
|
||||
builder.formLogin(Customizer.withDefaults());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,547 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configuration;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.oauth2.client.AuthorizationCodeOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException;
|
||||
import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.JwtBearerOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.endpoint.AbstractOAuth2AuthorizationGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
||||
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses;
|
||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.oauth2.jwt.JoseHeaderNames;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimNames;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2ClientConfiguration.OAuth2AuthorizedClientManagerConfiguration}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class OAuth2AuthorizedClientManagerConfigurationTests {
|
||||
|
||||
private static OAuth2AccessTokenResponseClient<? super AbstractOAuth2AuthorizationGrantRequest> MOCK_RESPONSE_CLIENT;
|
||||
|
||||
public final SpringTestContext spring = new SpringTestContext(this);
|
||||
|
||||
@Autowired
|
||||
private OAuth2AuthorizedClientManager authorizedClientManager;
|
||||
|
||||
@Autowired
|
||||
private ClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
@Autowired
|
||||
private OAuth2AuthorizedClientRepository authorizedClientRepository;
|
||||
|
||||
@Autowired(required = false)
|
||||
private AuthorizationCodeOAuth2AuthorizedClientProvider authorizationCodeAuthorizedClientProvider;
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
private MockHttpServletResponse response;
|
||||
|
||||
@BeforeEach
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setUp() {
|
||||
MOCK_RESPONSE_CLIENT = mock(OAuth2AccessTokenResponseClient.class);
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.response = new MockHttpServletResponse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadContextWhenOAuth2ClientEnabledThenConfigured() {
|
||||
this.spring.register(MinimalOAuth2ClientConfig.class).autowire();
|
||||
assertThat(this.authorizedClientManager).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenAuthorizationCodeAuthorizedClientProviderBeanThenUsed() {
|
||||
this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
|
||||
|
||||
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null);
|
||||
// @formatter:off
|
||||
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
|
||||
.withClientRegistrationId("google")
|
||||
.principal(authentication)
|
||||
.attribute(HttpServletRequest.class.getName(), this.request)
|
||||
.attribute(HttpServletResponse.class.getName(), this.response)
|
||||
.build();
|
||||
assertThatExceptionOfType(ClientAuthorizationRequiredException.class)
|
||||
.isThrownBy(() -> this.authorizedClientManager.authorize(authorizeRequest))
|
||||
.extracting(OAuth2AuthorizationException::getError)
|
||||
.extracting(OAuth2Error::getErrorCode)
|
||||
.isEqualTo("client_authorization_required");
|
||||
// @formatter:on
|
||||
|
||||
verify(this.authorizationCodeAuthorizedClientProvider).authorize(any(OAuth2AuthorizationContext.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenRefreshTokenAccessTokenResponseClientBeanThenUsed() {
|
||||
this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire();
|
||||
testRefreshTokenGrant();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenRefreshTokenAuthorizedClientProviderBeanThenUsed() {
|
||||
this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
|
||||
testRefreshTokenGrant();
|
||||
}
|
||||
|
||||
private void testRefreshTokenGrant() {
|
||||
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
|
||||
given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class)))
|
||||
.willReturn(accessTokenResponse);
|
||||
|
||||
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null);
|
||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google");
|
||||
OAuth2AuthorizedClient existingAuthorizedClient = new OAuth2AuthorizedClient(clientRegistration,
|
||||
authentication.getName(), getExpiredAccessToken(), TestOAuth2RefreshTokens.refreshToken());
|
||||
this.authorizedClientRepository.saveAuthorizedClient(existingAuthorizedClient, authentication, this.request,
|
||||
this.response);
|
||||
// @formatter:off
|
||||
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
|
||||
.withAuthorizedClient(existingAuthorizedClient)
|
||||
.principal(authentication)
|
||||
.attribute(HttpServletRequest.class.getName(), this.request)
|
||||
.attribute(HttpServletResponse.class.getName(), this.response)
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
|
||||
assertThat(authorizedClient).isNotNull();
|
||||
|
||||
ArgumentCaptor<OAuth2RefreshTokenGrantRequest> grantRequestCaptor = ArgumentCaptor
|
||||
.forClass(OAuth2RefreshTokenGrantRequest.class);
|
||||
verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
|
||||
|
||||
OAuth2RefreshTokenGrantRequest grantRequest = grantRequestCaptor.getValue();
|
||||
assertThat(grantRequest.getClientRegistration().getRegistrationId())
|
||||
.isEqualTo(clientRegistration.getRegistrationId());
|
||||
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.REFRESH_TOKEN);
|
||||
assertThat(grantRequest.getAccessToken()).isEqualTo(existingAuthorizedClient.getAccessToken());
|
||||
assertThat(grantRequest.getRefreshToken()).isEqualTo(existingAuthorizedClient.getRefreshToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenClientCredentialsAccessTokenResponseClientBeanThenUsed() {
|
||||
this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire();
|
||||
testClientCredentialsGrant();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenClientCredentialsAuthorizedClientProviderBeanThenUsed() {
|
||||
this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
|
||||
testClientCredentialsGrant();
|
||||
}
|
||||
|
||||
private void testClientCredentialsGrant() {
|
||||
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
|
||||
given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2ClientCredentialsGrantRequest.class)))
|
||||
.willReturn(accessTokenResponse);
|
||||
|
||||
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null);
|
||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("github");
|
||||
// @formatter:off
|
||||
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
|
||||
.withClientRegistrationId(clientRegistration.getRegistrationId())
|
||||
.principal(authentication)
|
||||
.attribute(HttpServletRequest.class.getName(), this.request)
|
||||
.attribute(HttpServletResponse.class.getName(), this.response)
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
|
||||
assertThat(authorizedClient).isNotNull();
|
||||
|
||||
ArgumentCaptor<OAuth2ClientCredentialsGrantRequest> grantRequestCaptor = ArgumentCaptor
|
||||
.forClass(OAuth2ClientCredentialsGrantRequest.class);
|
||||
verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
|
||||
|
||||
OAuth2ClientCredentialsGrantRequest grantRequest = grantRequestCaptor.getValue();
|
||||
assertThat(grantRequest.getClientRegistration().getRegistrationId())
|
||||
.isEqualTo(clientRegistration.getRegistrationId());
|
||||
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenPasswordAccessTokenResponseClientBeanThenUsed() {
|
||||
this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire();
|
||||
testPasswordGrant();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenPasswordAuthorizedClientProviderBeanThenUsed() {
|
||||
this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
|
||||
testPasswordGrant();
|
||||
}
|
||||
|
||||
private void testPasswordGrant() {
|
||||
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
|
||||
given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2PasswordGrantRequest.class)))
|
||||
.willReturn(accessTokenResponse);
|
||||
|
||||
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "password");
|
||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("facebook");
|
||||
// @formatter:off
|
||||
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
|
||||
.withClientRegistrationId(clientRegistration.getRegistrationId())
|
||||
.principal(authentication)
|
||||
.attribute(HttpServletRequest.class.getName(), this.request)
|
||||
.attribute(HttpServletResponse.class.getName(), this.response)
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.request.setParameter(OAuth2ParameterNames.USERNAME, "user");
|
||||
this.request.setParameter(OAuth2ParameterNames.PASSWORD, "password");
|
||||
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
|
||||
assertThat(authorizedClient).isNotNull();
|
||||
|
||||
ArgumentCaptor<OAuth2PasswordGrantRequest> grantRequestCaptor = ArgumentCaptor
|
||||
.forClass(OAuth2PasswordGrantRequest.class);
|
||||
verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
|
||||
|
||||
OAuth2PasswordGrantRequest grantRequest = grantRequestCaptor.getValue();
|
||||
assertThat(grantRequest.getClientRegistration().getRegistrationId())
|
||||
.isEqualTo(clientRegistration.getRegistrationId());
|
||||
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.PASSWORD);
|
||||
assertThat(grantRequest.getUsername()).isEqualTo("user");
|
||||
assertThat(grantRequest.getPassword()).isEqualTo("password");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenJwtBearerAccessTokenResponseClientBeanThenUsed() {
|
||||
this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire();
|
||||
testJwtBearerGrant();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizeWhenJwtBearerAuthorizedClientProviderBeanThenUsed() {
|
||||
this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
|
||||
testJwtBearerGrant();
|
||||
}
|
||||
|
||||
private void testJwtBearerGrant() {
|
||||
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
|
||||
given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(JwtBearerGrantRequest.class))).willReturn(accessTokenResponse);
|
||||
|
||||
JwtAuthenticationToken authentication = new JwtAuthenticationToken(getJwt());
|
||||
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("okta");
|
||||
// @formatter:off
|
||||
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
|
||||
.withClientRegistrationId(clientRegistration.getRegistrationId())
|
||||
.principal(authentication)
|
||||
.attribute(HttpServletRequest.class.getName(), this.request)
|
||||
.attribute(HttpServletResponse.class.getName(), this.response)
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
|
||||
assertThat(authorizedClient).isNotNull();
|
||||
|
||||
ArgumentCaptor<JwtBearerGrantRequest> grantRequestCaptor = ArgumentCaptor.forClass(JwtBearerGrantRequest.class);
|
||||
verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
|
||||
|
||||
JwtBearerGrantRequest grantRequest = grantRequestCaptor.getValue();
|
||||
assertThat(grantRequest.getClientRegistration().getRegistrationId())
|
||||
.isEqualTo(clientRegistration.getRegistrationId());
|
||||
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.JWT_BEARER);
|
||||
assertThat(grantRequest.getJwt().getSubject()).isEqualTo("user");
|
||||
}
|
||||
|
||||
private static OAuth2AccessToken getExpiredAccessToken() {
|
||||
Instant expiresAt = Instant.now().minusSeconds(60);
|
||||
Instant issuedAt = expiresAt.minus(Duration.ofDays(1));
|
||||
return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "scopes", issuedAt, expiresAt,
|
||||
new HashSet<>(Arrays.asList("read", "write")));
|
||||
}
|
||||
|
||||
private static Jwt getJwt() {
|
||||
Instant issuedAt = Instant.now();
|
||||
return new Jwt("token", issuedAt, issuedAt.plusSeconds(300),
|
||||
Collections.singletonMap(JoseHeaderNames.ALG, "RS256"),
|
||||
Collections.singletonMap(JwtClaimNames.SUB, "user"));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class MinimalOAuth2ClientConfig extends OAuth2ClientBaseConfig {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class CustomAccessTokenResponseClientsConfig extends OAuth2ClientBaseConfig {
|
||||
|
||||
@Bean
|
||||
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeTokenResponseClient() {
|
||||
return new MockAuthorizationCodeClient();
|
||||
}
|
||||
|
||||
@Bean
|
||||
OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenTokenResponseClient() {
|
||||
return new MockRefreshTokenClient();
|
||||
}
|
||||
|
||||
@Bean
|
||||
OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsTokenResponseClient() {
|
||||
return new MockClientCredentialsClient();
|
||||
}
|
||||
|
||||
@Bean
|
||||
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient() {
|
||||
return new MockPasswordClient();
|
||||
}
|
||||
|
||||
@Bean
|
||||
OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerTokenResponseClient() {
|
||||
return new MockJwtBearerClient();
|
||||
}
|
||||
|
||||
@Bean
|
||||
OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
|
||||
return mock(DefaultOAuth2UserService.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
|
||||
return mock(OidcUserService.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class CustomAuthorizedClientProvidersConfig extends OAuth2ClientBaseConfig {
|
||||
|
||||
@Bean
|
||||
AuthorizationCodeOAuth2AuthorizedClientProvider authorizationCodeProvider() {
|
||||
return spy(new AuthorizationCodeOAuth2AuthorizedClientProvider());
|
||||
}
|
||||
|
||||
@Bean
|
||||
RefreshTokenOAuth2AuthorizedClientProvider refreshTokenProvider() {
|
||||
RefreshTokenOAuth2AuthorizedClientProvider authorizedClientProvider = new RefreshTokenOAuth2AuthorizedClientProvider();
|
||||
authorizedClientProvider.setAccessTokenResponseClient(new MockRefreshTokenClient());
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
@Bean
|
||||
ClientCredentialsOAuth2AuthorizedClientProvider clientCredentialsProvider() {
|
||||
ClientCredentialsOAuth2AuthorizedClientProvider authorizedClientProvider = new ClientCredentialsOAuth2AuthorizedClientProvider();
|
||||
authorizedClientProvider.setAccessTokenResponseClient(new MockClientCredentialsClient());
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
@Bean
|
||||
PasswordOAuth2AuthorizedClientProvider passwordProvider() {
|
||||
PasswordOAuth2AuthorizedClientProvider authorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider();
|
||||
authorizedClientProvider.setAccessTokenResponseClient(new MockPasswordClient());
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
@Bean
|
||||
JwtBearerOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider() {
|
||||
JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider();
|
||||
authorizedClientProvider.setAccessTokenResponseClient(new MockJwtBearerClient());
|
||||
return authorizedClientProvider;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
abstract static class OAuth2ClientBaseConfig {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
|
||||
.oauth2Login(Customizer.withDefaults())
|
||||
.oauth2Client(Customizer.withDefaults());
|
||||
return http.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Bean
|
||||
ClientRegistrationRepository clientRegistrationRepository() {
|
||||
// @formatter:off
|
||||
return new InMemoryClientRegistrationRepository(Arrays.asList(
|
||||
CommonOAuth2Provider.GOOGLE.getBuilder("google")
|
||||
.clientId("google-client-id")
|
||||
.clientSecret("google-client-secret")
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.build(),
|
||||
CommonOAuth2Provider.GITHUB.getBuilder("github")
|
||||
.clientId("github-client-id")
|
||||
.clientSecret("github-client-secret")
|
||||
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
|
||||
.build(),
|
||||
CommonOAuth2Provider.FACEBOOK.getBuilder("facebook")
|
||||
.clientId("facebook-client-id")
|
||||
.clientSecret("facebook-client-secret")
|
||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||
.build(),
|
||||
CommonOAuth2Provider.OKTA.getBuilder("okta")
|
||||
.clientId("okta-client-id")
|
||||
.clientSecret("okta-client-secret")
|
||||
.authorizationGrantType(AuthorizationGrantType.JWT_BEARER)
|
||||
.build()));
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Bean
|
||||
OAuth2AuthorizedClientRepository authorizedClientRepository() {
|
||||
return mock(OAuth2AuthorizedClientRepository.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
Consumer<DefaultOAuth2AuthorizedClientManager> authorizedClientManagerConsumer() {
|
||||
return (authorizedClientManager) -> authorizedClientManager
|
||||
.setContextAttributesMapper((authorizeRequest) -> {
|
||||
HttpServletRequest request = Objects
|
||||
.requireNonNull(authorizeRequest.getAttribute(HttpServletRequest.class.getName()));
|
||||
String username = request.getParameter(OAuth2ParameterNames.USERNAME);
|
||||
String password = request.getParameter(OAuth2ParameterNames.PASSWORD);
|
||||
|
||||
Map<String, Object> attributes = Collections.emptyMap();
|
||||
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
|
||||
attributes = new HashMap<>();
|
||||
attributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
|
||||
attributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
|
||||
}
|
||||
|
||||
return attributes;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MockAuthorizationCodeClient
|
||||
implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
|
||||
|
||||
@Override
|
||||
public OAuth2AccessTokenResponse getTokenResponse(
|
||||
OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest) {
|
||||
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MockRefreshTokenClient
|
||||
implements OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> {
|
||||
|
||||
@Override
|
||||
public OAuth2AccessTokenResponse getTokenResponse(OAuth2RefreshTokenGrantRequest authorizationGrantRequest) {
|
||||
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MockClientCredentialsClient
|
||||
implements OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
|
||||
|
||||
@Override
|
||||
public OAuth2AccessTokenResponse getTokenResponse(
|
||||
OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) {
|
||||
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MockPasswordClient implements OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
|
||||
|
||||
@Override
|
||||
public OAuth2AccessTokenResponse getTokenResponse(OAuth2PasswordGrantRequest authorizationGrantRequest) {
|
||||
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MockJwtBearerClient implements OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> {
|
||||
|
||||
@Override
|
||||
public OAuth2AccessTokenResponse getTokenResponse(JwtBearerGrantRequest authorizationGrantRequest) {
|
||||
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -180,10 +180,9 @@ public class OAuth2ClientConfigurationTests {
|
|||
@Test
|
||||
public void loadContextWhenAccessTokenResponseClientRegisteredTwiceThenThrowNoUniqueBeanDefinitionException() {
|
||||
// @formatter:off
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
assertThatExceptionOfType(Exception.class)
|
||||
.isThrownBy(() -> this.spring.register(AccessTokenResponseClientRegisteredTwiceConfig.class).autowire())
|
||||
.havingRootCause()
|
||||
.isInstanceOf(NoUniqueBeanDefinitionException.class)
|
||||
.withRootCauseInstanceOf(NoUniqueBeanDefinitionException.class)
|
||||
.withMessageContaining(
|
||||
"expected single matching bean but found 2: accessTokenResponseClient1,accessTokenResponseClient2");
|
||||
// @formatter:on
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -17,20 +17,14 @@
|
|||
package org.springframework.security.config.annotation.web.configuration;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledOnJre;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import reactor.core.CoreSubscriber;
|
||||
import reactor.core.publisher.BaseSubscriber;
|
||||
|
@ -41,8 +35,6 @@ import reactor.util.context.Context;
|
|||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.core.task.VirtualThreadTaskExecutor;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
@ -54,7 +46,6 @@ import org.springframework.security.config.annotation.web.configuration.Security
|
|||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||
import org.springframework.security.oauth2.client.web.reactive.function.client.MockExchangeFunction;
|
||||
|
@ -280,58 +271,6 @@ public class SecurityReactorContextConfigurationTests {
|
|||
verify(strategy, times(2)).getContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createPublisherWhenThreadFactoryIsPlatformThenSecurityContextAttributesAvailable() throws Exception {
|
||||
this.spring.register(SecurityConfig.class).autowire();
|
||||
|
||||
ThreadFactory threadFactory = Executors.defaultThreadFactory();
|
||||
assertContextAttributesAvailable(threadFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledOnJre(JRE.JAVA_17)
|
||||
public void createPublisherWhenThreadFactoryIsVirtualThenSecurityContextAttributesAvailable() throws Exception {
|
||||
this.spring.register(SecurityConfig.class).autowire();
|
||||
|
||||
ThreadFactory threadFactory = new VirtualThreadTaskExecutor().getVirtualThreadFactory();
|
||||
assertContextAttributesAvailable(threadFactory);
|
||||
}
|
||||
|
||||
private void assertContextAttributesAvailable(ThreadFactory threadFactory) throws Exception {
|
||||
Map<Object, Object> expectedContextAttributes = new HashMap<>();
|
||||
expectedContextAttributes.put(HttpServletRequest.class, this.servletRequest);
|
||||
expectedContextAttributes.put(HttpServletResponse.class, this.servletResponse);
|
||||
expectedContextAttributes.put(Authentication.class, this.authentication);
|
||||
|
||||
try (SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(threadFactory)) {
|
||||
Future<Map<Object, Object>> future = taskExecutor.submit(this::propagateRequestAttributes);
|
||||
assertThat(future.get()).isEqualTo(expectedContextAttributes);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Object, Object> propagateRequestAttributes() {
|
||||
RequestAttributes requestAttributes = new ServletRequestAttributes(this.servletRequest, this.servletResponse);
|
||||
RequestContextHolder.setRequestAttributes(requestAttributes);
|
||||
|
||||
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
|
||||
securityContext.setAuthentication(this.authentication);
|
||||
SecurityContextHolder.setContext(securityContext);
|
||||
|
||||
// @formatter:off
|
||||
return Mono.deferContextual(Mono::just)
|
||||
.filter((ctx) -> ctx.hasKey(SecurityReactorContextSubscriber.SECURITY_CONTEXT_ATTRIBUTES))
|
||||
.map((ctx) -> ctx.<Map<Object, Object>>get(SecurityReactorContextSubscriber.SECURITY_CONTEXT_ATTRIBUTES))
|
||||
.map((attributes) -> {
|
||||
Map<Object, Object> map = new HashMap<>();
|
||||
// Copy over items from lazily loaded map
|
||||
Arrays.asList(HttpServletRequest.class, HttpServletResponse.class, Authentication.class)
|
||||
.forEach((key) -> map.put(key, attributes.get(key)));
|
||||
return map;
|
||||
})
|
||||
.block();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class SecurityConfig {
|
||||
|
|
|
@ -1,349 +0,0 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jakarta.servlet.Servlet;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import org.assertj.core.api.AbstractObjectAssert;
|
||||
import org.assertj.core.api.ObjectAssert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.MockServletContext;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.AndRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.context.support.GenericWebApplicationContext;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link AbstractRequestMatcherBuilderRegistry}
|
||||
*/
|
||||
class AbstractRequestMatcherBuilderRegistryTests {
|
||||
|
||||
@Test
|
||||
void defaultServletMatchersWhenDefaultDispatcherServletThenMvc() {
|
||||
MockServletContext servletContext = MockServletContext.mvc();
|
||||
List<RequestMatcher> matchers = defaultServlet(servletContext).requestMatchers("/mvc").matchers;
|
||||
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
|
||||
assertThatMvc(matchers).servletPath().isNull();
|
||||
assertThatMvc(matchers).pattern().isEqualTo("/mvc");
|
||||
assertThatMvc(matchers).method().isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultServletHttpMethodMatchersWhenDefaultDispatcherServletThenMvc() {
|
||||
MockServletContext servletContext = MockServletContext.mvc();
|
||||
List<RequestMatcher> matchers = defaultServlet(servletContext).requestMatchers(HttpMethod.GET, "/mvc").matchers;
|
||||
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
|
||||
assertThatMvc(matchers).servletPath().isNull();
|
||||
assertThatMvc(matchers).pattern().isEqualTo("/mvc");
|
||||
assertThatMvc(matchers).method().isEqualTo(HttpMethod.GET);
|
||||
}
|
||||
|
||||
@Test
|
||||
void servletMatchersWhenPathDispatcherServletThenMvc() {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
|
||||
List<RequestMatcher> matchers = servletPattern(servletContext, "/mvc/*")
|
||||
.requestMatchers("/controller").matchers;
|
||||
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
|
||||
assertThatMvc(matchers).servletPath().isEqualTo("/mvc");
|
||||
assertThatMvc(matchers).pattern().isEqualTo("/controller");
|
||||
}
|
||||
|
||||
@Test
|
||||
void servletMatchersWhenAlsoExtraServletContainerMappingsThenMvc() {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
servletContext.addServlet("default", Servlet.class);
|
||||
servletContext.addServlet("jspServlet", Servlet.class).addMapping("*.jsp", "*.jspx");
|
||||
servletContext.addServlet("facesServlet", Servlet.class).addMapping("/faces/", "*.jsf", "*.faces", "*.xhtml");
|
||||
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
|
||||
List<RequestMatcher> matchers = servletPattern(servletContext, "/mvc/*")
|
||||
.requestMatchers("/controller").matchers;
|
||||
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
|
||||
assertThatMvc(matchers).servletPath().isEqualTo("/mvc");
|
||||
assertThatMvc(matchers).pattern().isEqualTo("/controller");
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultServletMatchersWhenOnlyDefaultServletThenAnt() {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
servletContext.addServlet("default", Servlet.class).addMapping("/");
|
||||
List<RequestMatcher> matchers = defaultServlet(servletContext).requestMatchers("/controller").matchers;
|
||||
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(AntPathRequestMatcher.class);
|
||||
assertThatAnt(matchers).pattern().isEqualTo("/controller");
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultDispatcherServletMatchersWhenNoHandlerMappingIntrospectorThenException() {
|
||||
MockServletContext servletContext = MockServletContext.mvc();
|
||||
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
|
||||
.isThrownBy(() -> defaultServlet(servletContext, (context) -> {
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void dispatcherServletMatchersWhenNoHandlerMappingIntrospectorThenException() {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
|
||||
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
|
||||
.isThrownBy(() -> servletPattern(servletContext, (context) -> {
|
||||
}, "/mvc/*"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchersWhenNoDispatchServletThenAnt() {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
servletContext.addServlet("default", Servlet.class).addMapping("/");
|
||||
servletContext.addServlet("messageDispatcherServlet", Servlet.class).addMapping("/services/*");
|
||||
List<RequestMatcher> matchers = defaultServlet(servletContext).requestMatchers("/services/endpoint").matchers;
|
||||
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(AntPathRequestMatcher.class);
|
||||
assertThatAnt(matchers).pattern().isEqualTo("/services/endpoint");
|
||||
}
|
||||
|
||||
@Test
|
||||
void servletMatchersWhenMixedServletsThenDeterminesByServletPath() {
|
||||
MockServletContext servletContext = MockServletContext.mvc();
|
||||
servletContext.addServlet("messageDispatcherServlet", Servlet.class).addMapping("/services/*");
|
||||
List<RequestMatcher> matchers = servletPattern(servletContext, "/services/*")
|
||||
.requestMatchers("/endpoint").matchers;
|
||||
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(AntPathRequestMatcher.class);
|
||||
assertThatAnt(matchers).pattern().isEqualTo("/services/endpoint");
|
||||
matchers = defaultServlet(servletContext).requestMatchers("/controller").matchers;
|
||||
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
|
||||
assertThatMvc(matchers).servletPath().isNull();
|
||||
assertThatMvc(matchers).pattern().isEqualTo("/controller");
|
||||
}
|
||||
|
||||
@Test
|
||||
void servletMatchersWhenDispatcherServletNotDefaultThenDeterminesByServletPath() {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
servletContext.addServlet("default", Servlet.class).addMapping("/");
|
||||
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
|
||||
List<RequestMatcher> matchers = servletPattern(servletContext, "/mvc/*")
|
||||
.requestMatchers("/controller").matchers;
|
||||
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
|
||||
assertThatMvc(matchers).servletPath().isEqualTo("/mvc");
|
||||
assertThatMvc(matchers).pattern().isEqualTo("/controller");
|
||||
matchers = defaultServlet(servletContext).requestMatchers("/endpoint").matchers;
|
||||
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(AntPathRequestMatcher.class);
|
||||
assertThatAnt(matchers).pattern().isEqualTo("/endpoint");
|
||||
}
|
||||
|
||||
@Test
|
||||
void servletHttpMatchersWhenDispatcherServletNotDefaultThenDeterminesByServletPath() {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
servletContext.addServlet("default", Servlet.class).addMapping("/");
|
||||
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
|
||||
List<RequestMatcher> matchers = servletPattern(servletContext, "/mvc/*").requestMatchers(HttpMethod.GET,
|
||||
"/controller").matchers;
|
||||
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
|
||||
assertThatMvc(matchers).method().isEqualTo(HttpMethod.GET);
|
||||
assertThatMvc(matchers).servletPath().isEqualTo("/mvc");
|
||||
assertThatMvc(matchers).pattern().isEqualTo("/controller");
|
||||
matchers = defaultServlet(servletContext).requestMatchers(HttpMethod.GET, "/endpoint").matchers;
|
||||
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(AntPathRequestMatcher.class);
|
||||
assertThatAnt(matchers).method().isEqualTo(HttpMethod.GET);
|
||||
assertThatAnt(matchers).pattern().isEqualTo("/endpoint");
|
||||
}
|
||||
|
||||
@Test
|
||||
void servletMatchersWhenTwoDispatcherServletsThenDeterminesByServletPath() {
|
||||
MockServletContext servletContext = MockServletContext.mvc();
|
||||
servletContext.addServlet("two", DispatcherServlet.class).addMapping("/other/*");
|
||||
List<RequestMatcher> matchers = defaultServlet(servletContext).requestMatchers("/controller").matchers;
|
||||
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
|
||||
assertThatMvc(matchers).servletPath().isNull();
|
||||
assertThatMvc(matchers).pattern().isEqualTo("/controller");
|
||||
matchers = servletPattern(servletContext, "/other/*").requestMatchers("/endpoint").matchers;
|
||||
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
|
||||
assertThatMvc(matchers).servletPath().isEqualTo("/other");
|
||||
assertThatMvc(matchers).pattern().isEqualTo("/endpoint");
|
||||
}
|
||||
|
||||
@Test
|
||||
void servletMatchersWhenMoreThanOneMappingThenDeterminesByServletPath() {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/", "/two/*");
|
||||
List<RequestMatcher> matchers = defaultServlet(servletContext).requestMatchers("/controller").matchers;
|
||||
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
|
||||
assertThatMvc(matchers).servletPath().isNull();
|
||||
assertThatMvc(matchers).pattern().isEqualTo("/controller");
|
||||
matchers = servletPattern(servletContext, "/two/*").requestMatchers("/endpoint").matchers;
|
||||
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
|
||||
assertThatMvc(matchers).servletPath().isEqualTo("/two");
|
||||
assertThatMvc(matchers).pattern().isEqualTo("/endpoint");
|
||||
}
|
||||
|
||||
@Test
|
||||
void servletMatchersWhenMoreThanOneMappingAndDefaultServletsThenDeterminesByServletPath() {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/", "/two/*");
|
||||
servletContext.addServlet("jspServlet", Servlet.class).addMapping("*.jsp", "*.jspx");
|
||||
List<RequestMatcher> matchers = defaultServlet(servletContext).requestMatchers("/controller").matchers;
|
||||
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
|
||||
assertThatMvc(matchers).servletPath().isNull();
|
||||
assertThatMvc(matchers).pattern().isEqualTo("/controller");
|
||||
matchers = servletPattern(servletContext, "/two/*").requestMatchers("/endpoint").matchers;
|
||||
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
|
||||
assertThatMvc(matchers).servletPath().isEqualTo("/two");
|
||||
assertThatMvc(matchers).pattern().isEqualTo("/endpoint");
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultServletWhenDispatcherServletThenMvc() {
|
||||
MockServletContext servletContext = MockServletContext.mvc();
|
||||
servletContext.addServlet("messageDispatcherServlet", Servlet.class).addMapping("/services/*");
|
||||
List<RequestMatcher> matchers = defaultServlet(servletContext).requestMatchers("/controller").matchers;
|
||||
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
|
||||
assertThatMvc(matchers).servletPath().isNull();
|
||||
assertThatMvc(matchers).pattern().isEqualTo("/controller");
|
||||
matchers = servletPattern(servletContext, "/services/*").requestMatchers("/endpoint").matchers;
|
||||
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(AntPathRequestMatcher.class);
|
||||
assertThatAnt(matchers).pattern().isEqualTo("/services/endpoint");
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultServletWhenNoDefaultServletThenException() {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
servletContext.addServlet("messageDispatcherServlet", Servlet.class).addMapping("/services/*");
|
||||
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> defaultServlet(servletContext));
|
||||
}
|
||||
|
||||
@Test
|
||||
void servletPathWhenNoMatchingServletThenException() {
|
||||
MockServletContext servletContext = MockServletContext.mvc();
|
||||
assertThatExceptionOfType(IllegalArgumentException.class)
|
||||
.isThrownBy(() -> servletPattern(servletContext, "/wrong/*"));
|
||||
}
|
||||
|
||||
TestServletRequestMatcherRegistry defaultServlet(ServletContext servletContext) {
|
||||
return servletPattern(servletContext, "/");
|
||||
}
|
||||
|
||||
TestServletRequestMatcherRegistry defaultServlet(ServletContext servletContext,
|
||||
Consumer<GenericWebApplicationContext> consumer) {
|
||||
return servletPattern(servletContext, consumer, "/");
|
||||
}
|
||||
|
||||
TestServletRequestMatcherRegistry servletPattern(ServletContext servletContext, String pattern) {
|
||||
return servletPattern(servletContext, (context) -> {
|
||||
context.registerBean("mvcHandlerMappingIntrospector", HandlerMappingIntrospector.class);
|
||||
context.registerBean(ObjectPostProcessor.class, () -> mock(ObjectPostProcessor.class));
|
||||
}, pattern);
|
||||
}
|
||||
|
||||
TestServletRequestMatcherRegistry servletPattern(ServletContext servletContext,
|
||||
Consumer<GenericWebApplicationContext> consumer, String pattern) {
|
||||
GenericWebApplicationContext context = new GenericWebApplicationContext(servletContext);
|
||||
consumer.accept(context);
|
||||
context.refresh();
|
||||
return new TestServletRequestMatcherRegistry(context, pattern);
|
||||
}
|
||||
|
||||
static MvcRequestMatcherAssert assertThatMvc(List<RequestMatcher> matchers) {
|
||||
RequestMatcher matcher = matchers.get(0);
|
||||
if (matcher instanceof AndRequestMatcher matching) {
|
||||
List<RequestMatcher> and = (List<RequestMatcher>) ReflectionTestUtils.getField(matching, "requestMatchers");
|
||||
assertThat(and).hasSize(2);
|
||||
assertThat(and.get(1)).isInstanceOf(MvcRequestMatcher.class);
|
||||
return new MvcRequestMatcherAssert((MvcRequestMatcher) and.get(1));
|
||||
}
|
||||
assertThat(matcher).isInstanceOf(MvcRequestMatcher.class);
|
||||
return new MvcRequestMatcherAssert((MvcRequestMatcher) matcher);
|
||||
}
|
||||
|
||||
static AntPathRequestMatcherAssert assertThatAnt(List<RequestMatcher> matchers) {
|
||||
RequestMatcher matcher = matchers.get(0);
|
||||
if (matcher instanceof AndRequestMatcher matching) {
|
||||
List<RequestMatcher> and = (List<RequestMatcher>) ReflectionTestUtils.getField(matching, "requestMatchers");
|
||||
assertThat(and).hasSize(2);
|
||||
assertThat(and.get(1)).isInstanceOf(AntPathRequestMatcher.class);
|
||||
return new AntPathRequestMatcherAssert((AntPathRequestMatcher) and.get(1));
|
||||
}
|
||||
assertThat(matcher).isInstanceOf(AntPathRequestMatcher.class);
|
||||
return new AntPathRequestMatcherAssert((AntPathRequestMatcher) matcher);
|
||||
}
|
||||
|
||||
static final class TestServletRequestMatcherRegistry
|
||||
extends AbstractRequestMatcherBuilderRegistry<TestServletRequestMatcherRegistry> {
|
||||
|
||||
List<RequestMatcher> matchers;
|
||||
|
||||
TestServletRequestMatcherRegistry(ApplicationContext context, String pattern) {
|
||||
super(context, RequestMatcherBuilders.createForServletPattern(context, pattern));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TestServletRequestMatcherRegistry chainRequestMatchers(List<RequestMatcher> requestMatchers) {
|
||||
this.matchers = requestMatchers;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static final class MvcRequestMatcherAssert extends ObjectAssert<MvcRequestMatcher> {
|
||||
|
||||
private MvcRequestMatcherAssert(MvcRequestMatcher matcher) {
|
||||
super(matcher);
|
||||
}
|
||||
|
||||
AbstractObjectAssert<?, ?> servletPath() {
|
||||
return extracting("servletPath");
|
||||
}
|
||||
|
||||
AbstractObjectAssert<?, ?> pattern() {
|
||||
return extracting("pattern");
|
||||
}
|
||||
|
||||
AbstractObjectAssert<?, ?> method() {
|
||||
return extracting("method");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static final class AntPathRequestMatcherAssert extends ObjectAssert<AntPathRequestMatcher> {
|
||||
|
||||
private AntPathRequestMatcherAssert(AntPathRequestMatcher matcher) {
|
||||
super(matcher);
|
||||
}
|
||||
|
||||
AbstractObjectAssert<?, ?> pattern() {
|
||||
return extracting("pattern");
|
||||
}
|
||||
|
||||
AbstractObjectAssert<?, ?> method() {
|
||||
return extracting("httpMethod");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -18,7 +18,6 @@ package org.springframework.security.config.annotation.web.configurers;
|
|||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import jakarta.servlet.Servlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
@ -27,7 +26,6 @@ import org.springframework.beans.factory.BeanCreationException;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
|
||||
import org.springframework.security.authentication.RememberMeAuthenticationToken;
|
||||
|
@ -35,13 +33,10 @@ import org.springframework.security.authentication.TestAuthentication;
|
|||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationEventPublisher;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.config.MockServletContext;
|
||||
import org.springframework.security.config.TestMockHttpServletMappings;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.core.GrantedAuthorityDefaults;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
|
@ -62,7 +57,6 @@ import org.springframework.web.bind.annotation.PathVariable;
|
|||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
|
||||
|
||||
|
@ -76,7 +70,6 @@ import static org.springframework.security.test.web.servlet.request.SecurityMock
|
|||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||
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.request.MockMvcRequestBuilders.head;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
|
@ -127,7 +120,7 @@ public class AuthorizeHttpRequestsConfigurerTests {
|
|||
public void configureWhenMvcMatcherAfterAnyRequestThenException() {
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() -> this.spring.register(AfterAnyRequestConfig.class).autowire())
|
||||
.withMessageContaining("Can't configure requestMatchers after anyRequest");
|
||||
.withMessageContaining("Can't configure mvcMatchers after anyRequest");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -368,7 +361,7 @@ public class AuthorizeHttpRequestsConfigurerTests {
|
|||
|
||||
@Test
|
||||
public void getWhenServletPathRoleAdminConfiguredAndRoleIsUserThenRespondsWithForbidden() throws Exception {
|
||||
this.spring.register(MvcServletPathConfig.class, BasicController.class).autowire();
|
||||
this.spring.register(ServletPathConfig.class, BasicController.class).autowire();
|
||||
// @formatter:off
|
||||
MockHttpServletRequestBuilder requestWithUser = get("/spring/")
|
||||
.servletPath("/spring")
|
||||
|
@ -381,7 +374,7 @@ public class AuthorizeHttpRequestsConfigurerTests {
|
|||
@Test
|
||||
public void getWhenServletPathRoleAdminConfiguredAndRoleIsUserAndWithoutServletPathThenRespondsWithForbidden()
|
||||
throws Exception {
|
||||
this.spring.register(MvcServletPathConfig.class, BasicController.class).autowire();
|
||||
this.spring.register(ServletPathConfig.class, BasicController.class).autowire();
|
||||
// @formatter:off
|
||||
MockHttpServletRequestBuilder requestWithUser = get("/")
|
||||
.with(user("user")
|
||||
|
@ -392,7 +385,7 @@ public class AuthorizeHttpRequestsConfigurerTests {
|
|||
|
||||
@Test
|
||||
public void getWhenServletPathRoleAdminConfiguredAndRoleIsAdminThenRespondsWithOk() throws Exception {
|
||||
this.spring.register(MvcServletPathConfig.class, BasicController.class).autowire();
|
||||
this.spring.register(ServletPathConfig.class, BasicController.class).autowire();
|
||||
// @formatter:off
|
||||
MockHttpServletRequestBuilder requestWithAdmin = get("/spring/")
|
||||
.servletPath("/spring")
|
||||
|
@ -483,43 +476,6 @@ public class AuthorizeHttpRequestsConfigurerTests {
|
|||
this.mvc.perform(requestWithRoleOther).andExpect(status().isForbidden());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenCustomRolePrefixAndRoleHasDifferentPrefixThenRespondsWithForbidden() throws Exception {
|
||||
this.spring.register(GrantedAuthorityDefaultHasRoleConfig.class, BasicController.class).autowire();
|
||||
// @formatter:off
|
||||
MockHttpServletRequestBuilder requestWithUser = get("/")
|
||||
.with(user("user")
|
||||
.authorities(new SimpleGrantedAuthority("ROLE_USER")));
|
||||
// @formatter:on
|
||||
this.mvc.perform(requestWithUser).andExpect(status().isForbidden());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenCustomRolePrefixAndHasRoleThenRespondsWithOk() throws Exception {
|
||||
this.spring.register(GrantedAuthorityDefaultHasRoleConfig.class, BasicController.class).autowire();
|
||||
// @formatter:off
|
||||
MockHttpServletRequestBuilder requestWithUser = get("/")
|
||||
.with(user("user")
|
||||
.authorities(new SimpleGrantedAuthority("CUSTOM_PREFIX_USER")));
|
||||
// @formatter:on
|
||||
this.mvc.perform(requestWithUser).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenCustomRolePrefixAndHasAnyRoleThenRespondsWithOk() throws Exception {
|
||||
this.spring.register(GrantedAuthorityDefaultHasAnyRoleConfig.class, BasicController.class).autowire();
|
||||
// @formatter:off
|
||||
MockHttpServletRequestBuilder requestWithUser = get("/")
|
||||
.with(user("user")
|
||||
.authorities(new SimpleGrantedAuthority("CUSTOM_PREFIX_USER")));
|
||||
MockHttpServletRequestBuilder requestWithAdmin = get("/")
|
||||
.with(user("user")
|
||||
.authorities(new SimpleGrantedAuthority("CUSTOM_PREFIX_ADMIN")));
|
||||
// @formatter:on
|
||||
this.mvc.perform(requestWithUser).andExpect(status().isOk());
|
||||
this.mvc.perform(requestWithAdmin).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenExpressionHasIpAddressLocalhostConfiguredIpAddressIsLocalhostThenRespondsWithOk()
|
||||
throws Exception {
|
||||
|
@ -602,232 +558,6 @@ public class AuthorizeHttpRequestsConfigurerTests {
|
|||
this.mvc.perform(requestWithUser).andExpect(status().isForbidden());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenNoDispatcherServletThenSucceeds() throws Exception {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
servletContext.addServlet("default", Servlet.class).addMapping("/");
|
||||
this.spring.register(AuthorizeHttpRequestsConfig.class)
|
||||
.postProcessor((context) -> context.setServletContext(servletContext))
|
||||
.autowire();
|
||||
this.mvc.perform(get("/path")).andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenOnlyDispatcherServletThenSucceeds() throws Exception {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
|
||||
this.spring.register(AuthorizeHttpRequestsConfig.class)
|
||||
.postProcessor((context) -> context.setServletContext(servletContext))
|
||||
.autowire();
|
||||
this.mvc.perform(get("/mvc/path").servletPath("/mvc")).andExpect(status().isNotFound());
|
||||
this.mvc.perform(get("/mvc")).andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenMultipleServletsThenSucceeds() throws Exception {
|
||||
MockServletContext servletContext = MockServletContext.mvc();
|
||||
servletContext.addServlet("path", Servlet.class).addMapping("/path/*");
|
||||
this.spring.register(AuthorizeHttpRequestsConfig.class)
|
||||
.postProcessor((context) -> context.setServletContext(servletContext))
|
||||
.autowire();
|
||||
this.mvc.perform(get("/path").with(servletPath("/path"))).andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenAmbiguousServletsThenWiringException() {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
|
||||
servletContext.addServlet("path", Servlet.class).addMapping("/path/*");
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() -> this.spring.register(AuthorizeHttpRequestsConfig.class)
|
||||
.postProcessor((context) -> context.setServletContext(servletContext))
|
||||
.autowire());
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultServletMatchersWhenDefaultServletThenPermits() throws Exception {
|
||||
this.spring.register(DefaultServletConfig.class)
|
||||
.postProcessor((context) -> context.setServletContext(MockServletContext.mvc()))
|
||||
.autowire();
|
||||
this.mvc.perform(get("/path/path").with(defaultServlet())).andExpect(status().isNotFound());
|
||||
this.mvc.perform(get("/path/path").with(servletPath("/path"))).andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultServletHttpMethodMatchersWhenDefaultServletThenPermits() throws Exception {
|
||||
this.spring.register(DefaultServletConfig.class)
|
||||
.postProcessor((context) -> context.setServletContext(MockServletContext.mvc()))
|
||||
.autowire();
|
||||
this.mvc.perform(get("/path/method").with(defaultServlet())).andExpect(status().isNotFound());
|
||||
this.mvc.perform(head("/path/method").with(defaultServlet())).andExpect(status().isUnauthorized());
|
||||
this.mvc.perform(get("/path/method").with(servletPath("/path"))).andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultServletWhenNoDefaultServletThenWiringException() {
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() -> this.spring.register(DefaultServletConfig.class)
|
||||
.postProcessor((context) -> context.setServletContext(new MockServletContext()))
|
||||
.autowire());
|
||||
}
|
||||
|
||||
@Test
|
||||
void servletPathMatchersWhenMatchingServletThenPermits() throws Exception {
|
||||
MockServletContext servletContext = MockServletContext.mvc();
|
||||
servletContext.addServlet("path", Servlet.class).addMapping("/path/*");
|
||||
this.spring.register(ServletPathConfig.class)
|
||||
.postProcessor((context) -> context.setServletContext(servletContext))
|
||||
.autowire();
|
||||
this.mvc.perform(get("/path/path").with(servletPath("/path"))).andExpect(status().isNotFound());
|
||||
this.mvc.perform(get("/path/path").with(defaultServlet())).andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
void servletPathHttpMethodMatchersWhenMatchingServletThenPermits() throws Exception {
|
||||
MockServletContext servletContext = MockServletContext.mvc();
|
||||
servletContext.addServlet("path", Servlet.class).addMapping("/path/*");
|
||||
this.spring.register(ServletPathConfig.class)
|
||||
.postProcessor((context) -> context.setServletContext(servletContext))
|
||||
.autowire();
|
||||
this.mvc.perform(get("/path/method").with(servletPath("/path"))).andExpect(status().isNotFound());
|
||||
this.mvc.perform(head("/path/method").with(servletPath("/path"))).andExpect(status().isUnauthorized());
|
||||
this.mvc.perform(get("/path/method").with(defaultServlet())).andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
void servletPathWhenNoMatchingPathThenWiringException() {
|
||||
MockServletContext servletContext = MockServletContext.mvc();
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() -> this.spring.register(ServletPathConfig.class)
|
||||
.postProcessor((context) -> context.setServletContext(servletContext))
|
||||
.autowire());
|
||||
}
|
||||
|
||||
@Test
|
||||
void servletMappingMatchersWhenMatchingServletThenPermits() throws Exception {
|
||||
MockServletContext servletContext = MockServletContext.mvc();
|
||||
servletContext.addServlet("jsp", Servlet.class).addMapping("*.jsp");
|
||||
this.spring.register(ServletMappingConfig.class)
|
||||
.postProcessor((context) -> context.setServletContext(servletContext))
|
||||
.autowire();
|
||||
this.mvc.perform(get("/path/file.jsp").with(servletExtension(".jsp"))).andExpect(status().isNotFound());
|
||||
this.mvc.perform(get("/path/file.jsp").with(defaultServlet())).andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
void servletMappingHttpMethodMatchersWhenMatchingServletThenPermits() throws Exception {
|
||||
MockServletContext servletContext = MockServletContext.mvc();
|
||||
servletContext.addServlet("jsp", Servlet.class).addMapping("*.jsp");
|
||||
this.spring.register(ServletMappingConfig.class)
|
||||
.postProcessor((context) -> context.setServletContext(servletContext))
|
||||
.autowire();
|
||||
this.mvc.perform(get("/method/file.jsp").with(servletExtension(".jsp"))).andExpect(status().isNotFound());
|
||||
this.mvc.perform(head("/method/file.jsp").with(servletExtension(".jsp"))).andExpect(status().isUnauthorized());
|
||||
this.mvc.perform(get("/method/file.jsp").with(defaultServlet())).andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
void servletMappingWhenNoMatchingExtensionThenWiringException() {
|
||||
MockServletContext servletContext = MockServletContext.mvc();
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() -> this.spring.register(ServletMappingConfig.class)
|
||||
.postProcessor((context) -> context.setServletContext(servletContext))
|
||||
.autowire());
|
||||
}
|
||||
|
||||
@Test
|
||||
void anyRequestWhenUsedWithDefaultServletThenDoesNotWire() {
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() -> this.spring.register(MixedServletEndpointConfig.class).autowire())
|
||||
.withMessageContaining("forServletPattern");
|
||||
}
|
||||
|
||||
@Test
|
||||
void servletWhenNoMatchingPathThenDenies() throws Exception {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
servletContext.addServlet("default", Servlet.class).addMapping("/");
|
||||
servletContext.addServlet("jspServlet", Servlet.class).addMapping("*.jsp");
|
||||
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
|
||||
this.spring.register(DefaultServletAndServletPathConfig.class)
|
||||
.postProcessor((context) -> context.setServletContext(servletContext))
|
||||
.autowire();
|
||||
this.mvc.perform(get("/js/color.js").with(servletPath("/js"))).andExpect(status().isUnauthorized());
|
||||
this.mvc.perform(get("/mvc/controller").with(defaultServlet())).andExpect(status().isUnauthorized());
|
||||
this.mvc.perform(get("/js/color.js").with(defaultServlet())).andExpect(status().isNotFound());
|
||||
this.mvc.perform(get("/mvc/controller").with(servletPath("/mvc"))).andExpect(status().isUnauthorized());
|
||||
this.mvc.perform(get("/mvc/controller").with(user("user")).with(servletPath("/mvc")))
|
||||
.andExpect(status().isNotFound());
|
||||
}
|
||||
|
||||
@Test
|
||||
void permitAllWhenDefaultServletThenDoesNotWire() {
|
||||
assertThatExceptionOfType(BeanCreationException.class)
|
||||
.isThrownBy(() -> this.spring.register(MixedServletPermitAllConfig.class).autowire())
|
||||
.withMessageContaining("forServletPattern");
|
||||
}
|
||||
|
||||
static RequestPostProcessor defaultServlet() {
|
||||
return (request) -> {
|
||||
String uri = request.getRequestURI();
|
||||
request.setHttpServletMapping(TestMockHttpServletMappings.defaultMapping());
|
||||
request.setServletPath(uri);
|
||||
request.setPathInfo("");
|
||||
return request;
|
||||
};
|
||||
}
|
||||
|
||||
static RequestPostProcessor servletPath(String path) {
|
||||
return (request) -> {
|
||||
String uri = request.getRequestURI();
|
||||
request.setHttpServletMapping(TestMockHttpServletMappings.path(request, path));
|
||||
request.setServletPath(path);
|
||||
request.setPathInfo(uri.substring(path.length()));
|
||||
return request;
|
||||
};
|
||||
}
|
||||
|
||||
static RequestPostProcessor servletExtension(String extension) {
|
||||
return (request) -> {
|
||||
String uri = request.getRequestURI();
|
||||
request.setHttpServletMapping(TestMockHttpServletMappings.extension(request, extension));
|
||||
request.setServletPath(uri);
|
||||
request.setPathInfo("");
|
||||
return request;
|
||||
};
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class GrantedAuthorityDefaultHasRoleConfig {
|
||||
|
||||
@Bean
|
||||
GrantedAuthorityDefaults grantedAuthorityDefaults() {
|
||||
return new GrantedAuthorityDefaults("CUSTOM_PREFIX_");
|
||||
}
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain myFilterChain(HttpSecurity http) throws Exception {
|
||||
return http.authorizeHttpRequests((c) -> c.anyRequest().hasRole("USER")).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class GrantedAuthorityDefaultHasAnyRoleConfig {
|
||||
|
||||
@Bean
|
||||
GrantedAuthorityDefaults grantedAuthorityDefaults() {
|
||||
return new GrantedAuthorityDefaults("CUSTOM_PREFIX_");
|
||||
}
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain myFilterChain(HttpSecurity http) throws Exception {
|
||||
return http.authorizeHttpRequests((c) -> c.anyRequest().hasAnyRole("USER", "ADMIN")).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
static class NoRequestsConfig {
|
||||
|
@ -893,7 +623,6 @@ public class AuthorizeHttpRequestsConfigurerTests {
|
|||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
static class AfterAnyRequestConfig {
|
||||
|
||||
@Bean
|
||||
|
@ -1155,7 +884,7 @@ public class AuthorizeHttpRequestsConfigurerTests {
|
|||
@Configuration
|
||||
@EnableWebMvc
|
||||
@EnableWebSecurity
|
||||
static class MvcServletPathConfig {
|
||||
static class ServletPathConfig {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {
|
||||
|
@ -1337,163 +1066,6 @@ public class AuthorizeHttpRequestsConfigurerTests {
|
|||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
static class AuthorizeHttpRequestsConfig {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain chain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.httpBasic(withDefaults())
|
||||
.authorizeHttpRequests((requests) -> requests
|
||||
.requestMatchers("/path/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
static class DefaultServletConfig {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain chain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.httpBasic(withDefaults())
|
||||
.authorizeHttpRequests((requests) -> requests
|
||||
.forServletPattern("/", (root) -> root
|
||||
.requestMatchers(HttpMethod.GET, "/path/method/**").permitAll()
|
||||
.requestMatchers("/path/path/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
static class ServletPathConfig {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain chain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.httpBasic(withDefaults())
|
||||
.authorizeHttpRequests((requests) -> requests
|
||||
.forServletPattern("/path/*", (root) -> root
|
||||
.requestMatchers(HttpMethod.GET, "/method/**").permitAll()
|
||||
.requestMatchers("/path/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
static class ServletMappingConfig {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain chain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.httpBasic(withDefaults())
|
||||
.authorizeHttpRequests((requests) -> requests
|
||||
.forServletPattern("*.jsp", (jsp) -> jsp
|
||||
.requestMatchers(HttpMethod.GET, "/method/**").permitAll()
|
||||
.requestMatchers("/path/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
static class MixedServletEndpointConfig {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain chain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.httpBasic(withDefaults())
|
||||
.authorizeHttpRequests((requests) -> requests
|
||||
.forServletPattern("/", (root) -> root.anyRequest().permitAll())
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
static class MixedServletPermitAllConfig {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain chain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.formLogin((form) -> form.loginPage("/page").permitAll())
|
||||
.authorizeHttpRequests((requests) -> requests
|
||||
.forServletPattern("/", (root) -> root
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
static class DefaultServletAndServletPathConfig {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain chain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.httpBasic(withDefaults())
|
||||
.authorizeHttpRequests((requests) -> requests
|
||||
.forServletPattern("/", (root) -> root
|
||||
.requestMatchers("/js/**", "/css/**").permitAll()
|
||||
)
|
||||
.forServletPattern("/mvc/*", (mvc) -> mvc
|
||||
.requestMatchers("/controller/**").authenticated()
|
||||
)
|
||||
.forServletPattern("*.jsp", (jsp) -> jsp
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class AuthorizationEventPublisherConfig {
|
||||
|
||||
|
|
|
@ -85,10 +85,10 @@ public class DefaultFiltersTests {
|
|||
List<SecurityFilterChain> filterChains = this.spring.getContext()
|
||||
.getBean(FilterChainProxy.class)
|
||||
.getFilterChains();
|
||||
assertThat(filterChains).hasSize(1);
|
||||
assertThat(filterChains.size()).isEqualTo(1);
|
||||
DefaultSecurityFilterChain filterChain = (DefaultSecurityFilterChain) filterChains.get(0);
|
||||
assertThat(filterChain.getRequestMatcher()).isInstanceOf(AnyRequestMatcher.class);
|
||||
assertThat(filterChain.getFilters()).hasSize(1);
|
||||
assertThat(filterChain.getFilters().size()).isEqualTo(1);
|
||||
long filter = filterChain.getFilters()
|
||||
.stream()
|
||||
.filter((it) -> it instanceof UsernamePasswordAuthenticationFilter)
|
||||
|
@ -102,25 +102,25 @@ public class DefaultFiltersTests {
|
|||
List<SecurityFilterChain> filterChains = this.spring.getContext()
|
||||
.getBean(FilterChainProxy.class)
|
||||
.getFilterChains();
|
||||
assertThat(filterChains).hasSize(2);
|
||||
assertThat(filterChains.size()).isEqualTo(2);
|
||||
DefaultSecurityFilterChain firstFilter = (DefaultSecurityFilterChain) filterChains.get(0);
|
||||
DefaultSecurityFilterChain secondFilter = (DefaultSecurityFilterChain) filterChains.get(1);
|
||||
assertThat(firstFilter.getFilters().isEmpty()).isEqualTo(true);
|
||||
assertThat(secondFilter.getRequestMatcher()).isInstanceOf(AnyRequestMatcher.class);
|
||||
List<Class<? extends Filter>> classes = secondFilter.getFilters()
|
||||
List<? extends Class<? extends Filter>> classes = secondFilter.getFilters()
|
||||
.stream()
|
||||
.map(Filter::getClass)
|
||||
.collect(Collectors.toList());
|
||||
assertThat(classes).contains(WebAsyncManagerIntegrationFilter.class);
|
||||
assertThat(classes).contains(SecurityContextHolderFilter.class);
|
||||
assertThat(classes).contains(HeaderWriterFilter.class);
|
||||
assertThat(classes).contains(LogoutFilter.class);
|
||||
assertThat(classes).contains(CsrfFilter.class);
|
||||
assertThat(classes).contains(RequestCacheAwareFilter.class);
|
||||
assertThat(classes).contains(SecurityContextHolderAwareRequestFilter.class);
|
||||
assertThat(classes).contains(AnonymousAuthenticationFilter.class);
|
||||
assertThat(classes).contains(ExceptionTranslationFilter.class);
|
||||
assertThat(classes).contains(FilterSecurityInterceptor.class);
|
||||
assertThat(classes.contains(WebAsyncManagerIntegrationFilter.class)).isTrue();
|
||||
assertThat(classes.contains(SecurityContextHolderFilter.class)).isTrue();
|
||||
assertThat(classes.contains(HeaderWriterFilter.class)).isTrue();
|
||||
assertThat(classes.contains(LogoutFilter.class)).isTrue();
|
||||
assertThat(classes.contains(CsrfFilter.class)).isTrue();
|
||||
assertThat(classes.contains(RequestCacheAwareFilter.class)).isTrue();
|
||||
assertThat(classes.contains(SecurityContextHolderAwareRequestFilter.class)).isTrue();
|
||||
assertThat(classes.contains(AnonymousAuthenticationFilter.class)).isTrue();
|
||||
assertThat(classes.contains(ExceptionTranslationFilter.class)).isTrue();
|
||||
assertThat(classes.contains(FilterSecurityInterceptor.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue