Compare commits
111 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b220d9aa87 | |||
| 7627c2df46 | |||
| 342735043d | |||
| 779541b340 | |||
| 5b293d2116 | |||
| 7fcf44f8d9 | |||
| 18e88366d2 | |||
| 59a9aa3268 | |||
| aeafcc1377 | |||
| b4ce77c028 | |||
| 48babb7efa | |||
| f026e29771 | |||
| 620e6e0c34 | |||
| 4ebfa2c804 | |||
| 461bf9a09c | |||
| f03224fe7f | |||
| 3feb809b35 | |||
| 74dc3fd7b1 | |||
| 771d9cd8b6 | |||
| a580856bb2 | |||
| 9b7a110704 | |||
| b80a1de9fa | |||
| db37bdfe94 | |||
| ce012a4661 | |||
| b64d5395c5 | |||
| 629540f9d8 | |||
| 96d1763fc4 | |||
| 14b328e3ed | |||
| 3cff9ff04a | |||
| 98f9306a7c | |||
| a4d8c62ad7 | |||
| bbf2dd7091 | |||
| 741bdcff62 | |||
| 8efbbb3eb5 | |||
| 681681cbb8 | |||
| 612909ac4f | |||
| 54f7eacae6 | |||
| bcfa4adc44 | |||
| 0df1884372 | |||
| 5fb6f5768c | |||
| 28f98b3351 | |||
| ed96e2cddf | |||
| 037c7cbf2d | |||
| fd2b2b7974 | |||
| 7d4e5bf6b7 | |||
| 509c6c2952 | |||
| b83377bc77 | |||
| e5bef7ad3b | |||
| 9ddb94f887 | |||
| b38af6d048 | |||
| 97f6ca6f82 | |||
| ecc20ceffd | |||
| e147b22130 | |||
| d2cddb7e2d | |||
| 67e1832922 | |||
| 0426d27698 | |||
| 7afadfc0ef | |||
| 5f2a8c46d7 | |||
| 7200f76ac1 | |||
| 094b71b329 | |||
| 491f0f647c | |||
| 321deb30cc | |||
| d2d1f19133 | |||
| ca0140c586 | |||
| 35d879191c | |||
| 17e9fec6eb | |||
| 1db8734101 | |||
| 67bdbf6c56 | |||
| b5f42b509b | |||
| 8368c234a5 | |||
| 3edbdc6e87 | |||
| 90936537dc | |||
| 36d0ca340a | |||
| a8fcfaa428 | |||
| 8d4a024809 | |||
| bcd4dcc15c | |||
| 8df8d4022e | |||
| de1357cbd1 | |||
| ea19f82b8a | |||
| cd6f33c03e | |||
| 9a50ca74b4 | |||
| c20100dab7 | |||
| 4b44a2d924 | |||
| b7efa15591 | |||
| 1f27b18398 | |||
| e8b9a35494 | |||
| a6c3817a45 | |||
| c4b773b150 | |||
| 82e5f62079 | |||
| d4d715d8e1 | |||
| 4257a97504 | |||
| 30bc2634d7 | |||
| 1416b0649e | |||
| 461d6edd85 | |||
| 13ca7ac4d4 | |||
| 442d3fb99d | |||
| ee13216882 | |||
| c4f061c63d | |||
| 441655b6a6 | |||
| 0d2315cf3a | |||
| 2c6d053ceb | |||
| ff4745a461 | |||
| e37f6e5e63 | |||
| 76b5d17c83 | |||
| 38a7ffcc9b | |||
| 6f55fc60bb | |||
| d0491c07e9 | |||
| 486c5118d2 | |||
| d02ab50577 | |||
| 17d8293be1 | |||
| 0686b461af |
+2
-1
@@ -1,4 +1,5 @@
|
||||
import io.spring.gradle.IncludeRepoTask
|
||||
import trang.RncToXsd
|
||||
|
||||
buildscript {
|
||||
dependencies {
|
||||
@@ -174,7 +175,7 @@ if (hasProperty('buildScan')) {
|
||||
|
||||
nohttp {
|
||||
source.exclude "buildSrc/build/**"
|
||||
|
||||
source.builtBy(project(':spring-security-config').tasks.withType(RncToXsd))
|
||||
}
|
||||
|
||||
tasks.register('cloneSamples', IncludeRepoTask) {
|
||||
|
||||
@@ -86,7 +86,7 @@ dependencies {
|
||||
implementation localGroovy()
|
||||
|
||||
implementation 'io.github.gradle-nexus:publish-plugin:1.1.0'
|
||||
implementation 'io.projectreactor:reactor-core:3.5.8'
|
||||
implementation 'io.projectreactor:reactor-core:3.5.9'
|
||||
implementation 'org.gretty:gretty:4.0.3'
|
||||
implementation 'com.apollographql.apollo:apollo-runtime:2.4.5'
|
||||
implementation 'com.github.ben-manes:gradle-versions-plugin:0.38.0'
|
||||
|
||||
@@ -34,7 +34,7 @@ class JacocoPlugin implements Plugin<Project> {
|
||||
project.tasks.check.dependsOn project.tasks.jacocoTestReport
|
||||
|
||||
project.jacoco {
|
||||
toolVersion = '0.8.7'
|
||||
toolVersion = '0.8.9'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-4
@@ -34,6 +34,14 @@ class RepositoryConventionPlugin implements Plugin<Project> {
|
||||
if (forceMavenRepositories?.contains('local')) {
|
||||
mavenLocal()
|
||||
}
|
||||
maven {
|
||||
name = 'shibboleth'
|
||||
url = 'https://build.shibboleth.net/nexus/content/repositories/releases/'
|
||||
content {
|
||||
includeGroupByRegex('org\\.opensaml.*')
|
||||
includeGroupByRegex('net\\.shibboleth.*')
|
||||
}
|
||||
}
|
||||
mavenCentral()
|
||||
if (isSnapshot) {
|
||||
maven {
|
||||
@@ -67,12 +75,11 @@ class RepositoryConventionPlugin implements Plugin<Project> {
|
||||
password project.artifactoryPassword
|
||||
}
|
||||
}
|
||||
content {
|
||||
excludeGroup('net.minidev')
|
||||
}
|
||||
url = 'https://repo.spring.io/release/'
|
||||
}
|
||||
maven {
|
||||
name = 'shibboleth'
|
||||
url = 'https://build.shibboleth.net/nexus/content/repositories/releases/'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.plugins.JavaPlugin
|
||||
import org.gradle.api.tasks.bundling.Zip
|
||||
import org.springframework.gradle.xsd.CreateVersionlessXsdTask
|
||||
|
||||
public class SchemaZipPlugin implements Plugin<Project> {
|
||||
|
||||
@@ -15,7 +16,10 @@ public class SchemaZipPlugin implements Plugin<Project> {
|
||||
schemaZip.archiveClassifier = 'schema'
|
||||
schemaZip.description = "Builds -${schemaZip.archiveClassifier} archive containing all " +
|
||||
"XSDs for deployment at static.springframework.org/schema."
|
||||
|
||||
def versionlessXsd = project.tasks.create("versionlessXsd", CreateVersionlessXsdTask) {
|
||||
description = "Generates spring-security.xsd"
|
||||
versionlessXsdFile = project.layout.buildDirectory.file("versionlessXsd/spring-security.xsd")
|
||||
}
|
||||
project.rootProject.subprojects.each { module ->
|
||||
|
||||
module.getPlugins().withType(JavaPlugin.class).all {
|
||||
@@ -36,17 +40,14 @@ public class SchemaZipPlugin implements Plugin<Project> {
|
||||
duplicatesStrategy 'exclude'
|
||||
from xsdFile.path
|
||||
}
|
||||
}
|
||||
File symlink = module.sourceSets.main.resources.find {
|
||||
it.path.endsWith('org/springframework/security/config/spring-security.xsd')
|
||||
}
|
||||
if (symlink != null) {
|
||||
schemaZip.into('security') {
|
||||
duplicatesStrategy 'exclude'
|
||||
from symlink.path
|
||||
}
|
||||
versionlessXsd.getInputFiles().from(xsdFile.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
schemaZip.into("security") {
|
||||
from(versionlessXsd.getOutputs())
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.gradle.xsd;
|
||||
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.file.ConfigurableFileCollection;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.tasks.*;
|
||||
import org.gradle.work.DisableCachingByDefault;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Creates the spring-security.xsd automatically
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@DisableCachingByDefault(because = "not worth it")
|
||||
public abstract class CreateVersionlessXsdTask extends DefaultTask {
|
||||
|
||||
@InputFiles
|
||||
public abstract ConfigurableFileCollection getInputFiles();
|
||||
|
||||
@OutputFile
|
||||
abstract RegularFileProperty getVersionlessXsdFile();
|
||||
|
||||
@TaskAction
|
||||
void createVersionlessXsd() throws IOException {
|
||||
XsdFileMajorMinorVersion largest = null;
|
||||
ConfigurableFileCollection inputFiles = getInputFiles();
|
||||
if (inputFiles.isEmpty()) {
|
||||
throw new IllegalStateException("No Inputs configured");
|
||||
}
|
||||
for (File file : inputFiles) {
|
||||
XsdFileMajorMinorVersion current = XsdFileMajorMinorVersion.create(file);
|
||||
if (current == null) {
|
||||
continue;
|
||||
}
|
||||
if (largest == null) {
|
||||
largest = current;
|
||||
}
|
||||
else if (current.getVersion().isGreaterThan(largest.getVersion())) {
|
||||
largest = current;
|
||||
}
|
||||
}
|
||||
if (largest == null) {
|
||||
throw new IllegalStateException("Could not create versionless xsd file because no files matching spring-security-<digit>.xsd were found in " + inputFiles.getFiles());
|
||||
}
|
||||
Path to = getVersionlessXsdFile().getAsFile().get().toPath();
|
||||
Path from = largest.getFile().toPath();
|
||||
Files.copy(from, to, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
|
||||
}
|
||||
|
||||
static class XsdFileMajorMinorVersion {
|
||||
private final File file;
|
||||
|
||||
private final MajorMinorVersion version;
|
||||
|
||||
private XsdFileMajorMinorVersion(File file, MajorMinorVersion version) {
|
||||
this.file = file;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
private static final Pattern FILE_MAJOR_MINOR_VERSION_PATTERN = Pattern.compile("^spring-security-(\\d+)\\.(\\d+)\\.xsd$");
|
||||
|
||||
/**
|
||||
* If matches xsd with major minor version (e.g. spring-security-5.1.xsd returns it, otherwise null
|
||||
* @param file
|
||||
* @return
|
||||
*/
|
||||
static XsdFileMajorMinorVersion create(File file) {
|
||||
String fileName = file.getName();
|
||||
Matcher matcher = FILE_MAJOR_MINOR_VERSION_PATTERN.matcher(fileName);
|
||||
if (!matcher.find()) {
|
||||
return null;
|
||||
}
|
||||
int major = Integer.parseInt(matcher.group(1));
|
||||
int minor = Integer.parseInt(matcher.group(2));
|
||||
MajorMinorVersion version = new MajorMinorVersion(major, minor);
|
||||
return new XsdFileMajorMinorVersion(file, version);
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public MajorMinorVersion getVersion() {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
|
||||
static class MajorMinorVersion {
|
||||
private final int major;
|
||||
|
||||
private final int minor;
|
||||
|
||||
MajorMinorVersion(int major, int minor) {
|
||||
this.major = major;
|
||||
this.minor = minor;
|
||||
}
|
||||
|
||||
public int getMajor() {
|
||||
return major;
|
||||
}
|
||||
|
||||
public int getMinor() {
|
||||
return minor;
|
||||
}
|
||||
|
||||
public boolean isGreaterThan(MajorMinorVersion version) {
|
||||
if (getMajor() > version.getMajor()) {
|
||||
return true;
|
||||
}
|
||||
if (getMajor() < version.getMajor()) {
|
||||
return false;
|
||||
}
|
||||
if (getMinor() > version.getMinor()) {
|
||||
return true;
|
||||
}
|
||||
if (getMinor() < version.getMinor()) {
|
||||
return false;
|
||||
}
|
||||
// they are equal
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -125,27 +125,27 @@ public class RepositoryConventionPluginTests {
|
||||
|
||||
private void assertSnapshotRepository(RepositoryHandler repositories) {
|
||||
assertThat(repositories).extracting(ArtifactRepository::getName).hasSize(5);
|
||||
assertThat(((MavenArtifactRepository) repositories.get(0)).getUrl().toString())
|
||||
.isEqualTo("https://repo.maven.apache.org/maven2/");
|
||||
assertThat(((MavenArtifactRepository) repositories.get(1)).getUrl().toString())
|
||||
.isEqualTo("https://repo.spring.io/snapshot/");
|
||||
.isEqualTo("https://repo.maven.apache.org/maven2/");
|
||||
assertThat(((MavenArtifactRepository) repositories.get(2)).getUrl().toString())
|
||||
.isEqualTo("https://repo.spring.io/snapshot/");
|
||||
assertThat(((MavenArtifactRepository) repositories.get(3)).getUrl().toString())
|
||||
.isEqualTo("https://repo.spring.io/milestone/");
|
||||
}
|
||||
|
||||
private void assertMilestoneRepository(RepositoryHandler repositories) {
|
||||
assertThat(repositories).extracting(ArtifactRepository::getName).hasSize(4);
|
||||
assertThat(((MavenArtifactRepository) repositories.get(0)).getUrl().toString())
|
||||
.isEqualTo("https://repo.maven.apache.org/maven2/");
|
||||
assertThat(((MavenArtifactRepository) repositories.get(1)).getUrl().toString())
|
||||
.isEqualTo("https://repo.maven.apache.org/maven2/");
|
||||
assertThat(((MavenArtifactRepository) repositories.get(2)).getUrl().toString())
|
||||
.isEqualTo("https://repo.spring.io/milestone/");
|
||||
}
|
||||
|
||||
private void assertReleaseRepository(RepositoryHandler repositories) {
|
||||
assertThat(repositories).extracting(ArtifactRepository::getName).hasSize(3);
|
||||
assertThat(((MavenArtifactRepository) repositories.get(0)).getUrl().toString())
|
||||
.isEqualTo("https://repo.maven.apache.org/maven2/");
|
||||
assertThat(((MavenArtifactRepository) repositories.get(1)).getUrl().toString())
|
||||
.isEqualTo("https://repo.maven.apache.org/maven2/");
|
||||
assertThat(((MavenArtifactRepository) repositories.get(2)).getUrl().toString())
|
||||
.isEqualTo("https://repo.spring.io/release/");
|
||||
}
|
||||
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.gradle.xsd;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.gradle.xsd.CreateVersionlessXsdTask.MajorMinorVersion;
|
||||
import org.springframework.gradle.xsd.CreateVersionlessXsdTask.XsdFileMajorMinorVersion;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
class CreateVersionlessXsdTaskTests {
|
||||
|
||||
@Test
|
||||
void xsdCreateWhenValid() {
|
||||
File file = new File("spring-security-2.0.xsd");
|
||||
XsdFileMajorMinorVersion xsdFile = XsdFileMajorMinorVersion.create(file);
|
||||
assertThat(xsdFile).isNotNull();
|
||||
assertThat(xsdFile.getFile()).isEqualTo(file);
|
||||
assertThat(xsdFile.getVersion().getMajor()).isEqualTo(2);
|
||||
assertThat(xsdFile.getVersion().getMinor()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void xsdCreateWhenPatchReleaseThenNull() {
|
||||
File file = new File("spring-security-2.0.1.xsd");
|
||||
XsdFileMajorMinorVersion xsdFile = XsdFileMajorMinorVersion.create(file);
|
||||
assertThat(xsdFile).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void xsdCreateWhenNotXsdFileThenNull() {
|
||||
File file = new File("spring-security-2.0.txt");
|
||||
XsdFileMajorMinorVersion xsdFile = XsdFileMajorMinorVersion.create(file);
|
||||
assertThat(xsdFile).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void xsdCreateWhenNotStartWithSpringSecurityThenNull() {
|
||||
File file = new File("spring-securityNO-2.0.xsd");
|
||||
XsdFileMajorMinorVersion xsdFile = XsdFileMajorMinorVersion.create(file);
|
||||
assertThat(xsdFile).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isGreaterWhenMajorLarger() {
|
||||
MajorMinorVersion larger = new MajorMinorVersion(2,0);
|
||||
MajorMinorVersion smaller = new MajorMinorVersion(1,0);
|
||||
assertThat(larger.isGreaterThan(smaller)).isTrue();
|
||||
assertThat(smaller.isGreaterThan(larger)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isGreaterWhenMinorLarger() {
|
||||
MajorMinorVersion larger = new MajorMinorVersion(1,1);
|
||||
MajorMinorVersion smaller = new MajorMinorVersion(1,0);
|
||||
assertThat(larger.isGreaterThan(smaller)).isTrue();
|
||||
assertThat(smaller.isGreaterThan(larger)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isGreaterWhenMajorAndMinorLarger() {
|
||||
MajorMinorVersion larger = new MajorMinorVersion(2,1);
|
||||
MajorMinorVersion smaller = new MajorMinorVersion(1,0);
|
||||
assertThat(larger.isGreaterThan(smaller)).isTrue();
|
||||
assertThat(smaller.isGreaterThan(larger)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isGreaterWhenSame() {
|
||||
MajorMinorVersion first = new MajorMinorVersion(1,0);
|
||||
MajorMinorVersion second = new MajorMinorVersion(1,0);
|
||||
assertThat(first.isGreaterThan(second)).isFalse();
|
||||
assertThat(second.isGreaterThan(first)).isFalse();
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import org.springframework.gradle.xsd.CreateVersionlessXsdTask
|
||||
import trang.RncToXsd
|
||||
|
||||
apply plugin: 'io.spring.convention.spring-module'
|
||||
apply plugin: 'trang'
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
repositories {
|
||||
maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
management platform(project(":spring-security-dependencies"))
|
||||
// NB: Don't add other compile time dependencies to the config module as this breaks tooling
|
||||
@@ -119,13 +117,31 @@ dependencies {
|
||||
testRuntimeOnly 'org.hsqldb:hsqldb'
|
||||
}
|
||||
|
||||
def versionlessXsd = project.tasks.create("versionlessXsd", CreateVersionlessXsdTask) {
|
||||
inputFiles.from(project.sourceSets.main.resources)
|
||||
versionlessXsdFile = project.layout.buildDirectory.file("versionlessXsd/spring-security.xsd")
|
||||
}
|
||||
|
||||
rncToXsd {
|
||||
processResources {
|
||||
from(versionlessXsd) {
|
||||
into 'org/springframework/security/config/'
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named('rncToXsd', RncToXsd).configure {
|
||||
rncDir = file('src/main/resources/org/springframework/security/config/')
|
||||
xsdDir = rncDir
|
||||
xslFile = new File(rncDir, 'spring-security.xsl')
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
resources {
|
||||
srcDir(tasks.named('rncToXsd'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(KotlinCompile).configureEach {
|
||||
kotlinOptions {
|
||||
languageVersion = "1.7"
|
||||
@@ -134,5 +150,3 @@ tasks.withType(KotlinCompile).configureEach {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
}
|
||||
|
||||
build.dependsOn rncToXsd
|
||||
|
||||
+4
@@ -36,6 +36,10 @@ class MethodSecurityAdvisorRegistrar implements ImportBeanDefinitionRegistrar {
|
||||
}
|
||||
|
||||
private void registerAsAdvisor(String prefix, BeanDefinitionRegistry registry) {
|
||||
String advisorName = prefix + "Advisor";
|
||||
if (registry.containsBeanDefinition(advisorName)) {
|
||||
return;
|
||||
}
|
||||
String interceptorName = prefix + "MethodInterceptor";
|
||||
if (!registry.containsBeanDefinition(interceptorName)) {
|
||||
return;
|
||||
|
||||
+51
-4
@@ -16,8 +16,11 @@
|
||||
|
||||
package org.springframework.security.config.annotation.method.configuration;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
@@ -25,6 +28,9 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Role;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||
import org.springframework.security.authorization.AuthorizationEventPublisher;
|
||||
@@ -36,7 +42,9 @@ import org.springframework.security.authorization.method.PostFilterAuthorization
|
||||
import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager;
|
||||
import org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor;
|
||||
import org.springframework.security.config.core.GrantedAuthorityDefaults;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||
import org.springframework.util.function.SingletonSupplier;
|
||||
|
||||
/**
|
||||
* Base {@link Configuration} for enabling Spring Security Method Security.
|
||||
@@ -59,7 +67,7 @@ final class PrePostMethodSecurityConfiguration {
|
||||
PreFilterAuthorizationMethodInterceptor preFilter = new PreFilterAuthorizationMethodInterceptor();
|
||||
strategyProvider.ifAvailable(preFilter::setSecurityContextHolderStrategy);
|
||||
preFilter.setExpressionHandler(
|
||||
expressionHandlerProvider.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, context)));
|
||||
new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider, defaultsProvider, context));
|
||||
return preFilter;
|
||||
}
|
||||
|
||||
@@ -73,7 +81,7 @@ final class PrePostMethodSecurityConfiguration {
|
||||
ObjectProvider<ObservationRegistry> registryProvider, ApplicationContext context) {
|
||||
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
|
||||
manager.setExpressionHandler(
|
||||
expressionHandlerProvider.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, context)));
|
||||
new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider, defaultsProvider, context));
|
||||
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor
|
||||
.preAuthorize(manager(manager, registryProvider));
|
||||
strategyProvider.ifAvailable(preAuthorize::setSecurityContextHolderStrategy);
|
||||
@@ -91,7 +99,7 @@ final class PrePostMethodSecurityConfiguration {
|
||||
ObjectProvider<ObservationRegistry> registryProvider, ApplicationContext context) {
|
||||
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
|
||||
manager.setExpressionHandler(
|
||||
expressionHandlerProvider.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, context)));
|
||||
new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider, defaultsProvider, context));
|
||||
AuthorizationManagerAfterMethodInterceptor postAuthorize = AuthorizationManagerAfterMethodInterceptor
|
||||
.postAuthorize(manager(manager, registryProvider));
|
||||
strategyProvider.ifAvailable(postAuthorize::setSecurityContextHolderStrategy);
|
||||
@@ -108,7 +116,7 @@ final class PrePostMethodSecurityConfiguration {
|
||||
PostFilterAuthorizationMethodInterceptor postFilter = new PostFilterAuthorizationMethodInterceptor();
|
||||
strategyProvider.ifAvailable(postFilter::setSecurityContextHolderStrategy);
|
||||
postFilter.setExpressionHandler(
|
||||
expressionHandlerProvider.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, context)));
|
||||
new DeferringMethodSecurityExpressionHandler(expressionHandlerProvider, defaultsProvider, context));
|
||||
return postFilter;
|
||||
}
|
||||
|
||||
@@ -125,4 +133,43 @@ final class PrePostMethodSecurityConfiguration {
|
||||
return new DeferringObservationAuthorizationManager<>(registryProvider, delegate);
|
||||
}
|
||||
|
||||
private static final class DeferringMethodSecurityExpressionHandler implements MethodSecurityExpressionHandler {
|
||||
|
||||
private final Supplier<MethodSecurityExpressionHandler> expressionHandler;
|
||||
|
||||
private DeferringMethodSecurityExpressionHandler(
|
||||
ObjectProvider<MethodSecurityExpressionHandler> expressionHandlerProvider,
|
||||
ObjectProvider<GrantedAuthorityDefaults> defaultsProvider, ApplicationContext applicationContext) {
|
||||
this.expressionHandler = SingletonSupplier.of(() -> expressionHandlerProvider
|
||||
.getIfAvailable(() -> defaultExpressionHandler(defaultsProvider, applicationContext)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionParser getExpressionParser() {
|
||||
return this.expressionHandler.get().getExpressionParser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EvaluationContext createEvaluationContext(Authentication authentication, MethodInvocation invocation) {
|
||||
return this.expressionHandler.get().createEvaluationContext(authentication, invocation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication,
|
||||
MethodInvocation invocation) {
|
||||
return this.expressionHandler.get().createEvaluationContext(authentication, invocation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object filter(Object filterTarget, Expression filterExpression, EvaluationContext ctx) {
|
||||
return this.expressionHandler.get().filter(filterTarget, filterExpression, ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReturnObject(Object returnObject, EvaluationContext ctx) {
|
||||
this.expressionHandler.get().setReturnObject(returnObject, ctx);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+32
-4
@@ -18,6 +18,8 @@ package org.springframework.security.config.annotation.web;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -194,18 +196,31 @@ public abstract class AbstractRequestMatcherRegistry<C> {
|
||||
if (servletContext == null) {
|
||||
return requestMatchers(RequestMatchers.antMatchersAsArray(method, patterns));
|
||||
}
|
||||
Map<String, ? extends ServletRegistration> registrations = servletContext.getServletRegistrations();
|
||||
if (registrations == null) {
|
||||
Map<String, ? extends ServletRegistration> registrations = mappableServletRegistrations(servletContext);
|
||||
if (registrations.isEmpty()) {
|
||||
return requestMatchers(RequestMatchers.antMatchersAsArray(method, patterns));
|
||||
}
|
||||
if (!hasDispatcherServlet(registrations)) {
|
||||
return requestMatchers(RequestMatchers.antMatchersAsArray(method, patterns));
|
||||
}
|
||||
Assert.isTrue(registrations.size() == 1,
|
||||
"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); otherwise, please use requestMatchers(AntPathRequestMatcher).");
|
||||
if (registrations.size() > 1) {
|
||||
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) {
|
||||
Map<String, ServletRegistration> mappable = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, ? extends ServletRegistration> entry : servletContext.getServletRegistrations()
|
||||
.entrySet()) {
|
||||
if (!entry.getValue().getMappings().isEmpty()) {
|
||||
mappable.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return mappable;
|
||||
}
|
||||
|
||||
private boolean hasDispatcherServlet(Map<String, ? extends ServletRegistration> registrations) {
|
||||
if (registrations == null) {
|
||||
return false;
|
||||
@@ -226,6 +241,19 @@ public abstract class AbstractRequestMatcherRegistry<C> {
|
||||
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); "
|
||||
+ "otherwise, please use requestMatchers(AntPathRequestMatcher).\n\n"
|
||||
+ "This is because there is more than one mappable servlet in your servlet context: %s.\n\n"
|
||||
+ "For each MvcRequestMatcher, call MvcRequestMatcher#setServletPath to indicate the servlet path.";
|
||||
Map<String, Collection<String>> mappings = new LinkedHashMap<>();
|
||||
for (ServletRegistration registration : registrations) {
|
||||
mappings.put(registration.getClassName(), registration.getMappings());
|
||||
}
|
||||
return String.format(template, mappings);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* If the {@link HandlerMappingIntrospector} is available in the classpath, maps to an
|
||||
|
||||
+2
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* 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.
|
||||
@@ -127,11 +127,7 @@ final class FilterOrderRegistration {
|
||||
* @param position the position to associate with the {@link Filter}
|
||||
*/
|
||||
void put(Class<? extends Filter> filter, int position) {
|
||||
String className = filter.getName();
|
||||
if (this.filterToOrder.containsKey(className)) {
|
||||
return;
|
||||
}
|
||||
this.filterToOrder.put(className, position);
|
||||
this.filterToOrder.putIfAbsent(filter.getName(), position);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+8
-5
@@ -268,6 +268,9 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
}
|
||||
}
|
||||
this.initDefaultLoginFilter(http);
|
||||
if (this.authenticationManager == null) {
|
||||
registerDefaultAuthenticationProvider(http);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,10 +286,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
filter.setAuthenticationRequestRepository(getAuthenticationRequestRepository(http));
|
||||
http.addFilter(postProcess(filter));
|
||||
super.configure(http);
|
||||
if (this.authenticationManager == null) {
|
||||
registerDefaultAuthenticationProvider(http);
|
||||
}
|
||||
else {
|
||||
if (this.authenticationManager != null) {
|
||||
this.saml2WebSsoAuthenticationFilter.setAuthenticationManager(this.authenticationManager);
|
||||
}
|
||||
}
|
||||
@@ -359,7 +359,10 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
}
|
||||
|
||||
private void registerDefaultAuthenticationProvider(B http) {
|
||||
http.authenticationProvider(postProcess(new OpenSaml4AuthenticationProvider()));
|
||||
OpenSaml4AuthenticationProvider provider = getBeanOrNull(http, OpenSaml4AuthenticationProvider.class);
|
||||
if (provider == null) {
|
||||
http.authenticationProvider(postProcess(new OpenSaml4AuthenticationProvider()));
|
||||
}
|
||||
}
|
||||
|
||||
private void registerDefaultCsrfOverride(B http) {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
spring-security-6.1.xsd
|
||||
@@ -16,8 +16,10 @@
|
||||
|
||||
package org.springframework.security.config;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -35,7 +37,7 @@ public class MockServletContext extends org.springframework.mock.web.MockServlet
|
||||
|
||||
public static MockServletContext mvc() {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class);
|
||||
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/");
|
||||
return servletContext;
|
||||
}
|
||||
|
||||
@@ -59,6 +61,8 @@ public class MockServletContext extends org.springframework.mock.web.MockServlet
|
||||
|
||||
private final Class<?> clazz;
|
||||
|
||||
private final Set<String> mappings = new LinkedHashSet<>();
|
||||
|
||||
MockServletRegistration(String name, Class<?> clazz) {
|
||||
this.name = name;
|
||||
this.clazz = clazz;
|
||||
@@ -91,12 +95,13 @@ public class MockServletContext extends org.springframework.mock.web.MockServlet
|
||||
|
||||
@Override
|
||||
public Set<String> addMapping(String... urlPatterns) {
|
||||
return null;
|
||||
this.mappings.addAll(Arrays.asList(urlPatterns));
|
||||
return this.mappings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getMappings() {
|
||||
return null;
|
||||
return this.mappings;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+15
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* 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.
|
||||
@@ -20,6 +20,7 @@ import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
@@ -64,6 +65,8 @@ import org.springframework.security.test.context.support.WithSecurityContextTest
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.web.context.ConfigurableWebApplicationContext;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@@ -418,6 +421,17 @@ public class PrePostMethodSecurityConfigurationTests {
|
||||
assertThat(this.spring.getContext().containsBean("annotationSecurityAspect$0")).isFalse();
|
||||
}
|
||||
|
||||
// gh-13572
|
||||
@Test
|
||||
public void configureWhenBeanOverridingDisallowedThenWorks() {
|
||||
this.spring.register(MethodSecurityServiceConfig.class, BusinessServiceConfig.class)
|
||||
.postProcessor(disallowBeanOverriding()).autowire();
|
||||
}
|
||||
|
||||
private static Consumer<ConfigurableWebApplicationContext> disallowBeanOverriding() {
|
||||
return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableMethodSecurity
|
||||
static class MethodSecurityServiceConfig {
|
||||
|
||||
+14
-2
@@ -174,12 +174,24 @@ public class AbstractRequestMatcherRegistryTests {
|
||||
public void requestMatchersWhenAmbiguousServletsThenException() {
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
given(this.context.getServletContext()).willReturn(servletContext);
|
||||
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class);
|
||||
servletContext.addServlet("servletTwo", Servlet.class);
|
||||
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/");
|
||||
servletContext.addServlet("servletTwo", Servlet.class).addMapping("/servlet/**");
|
||||
assertThatExceptionOfType(IllegalArgumentException.class)
|
||||
.isThrownBy(() -> this.matcherRegistry.requestMatchers("/**"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestMatchersWhenUnmappableServletsThenSkips() {
|
||||
mockMvcIntrospector(true);
|
||||
MockServletContext servletContext = new MockServletContext();
|
||||
given(this.context.getServletContext()).willReturn(servletContext);
|
||||
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/");
|
||||
servletContext.addServlet("servletTwo", Servlet.class);
|
||||
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/**");
|
||||
assertThat(requestMatchers).hasSize(1);
|
||||
assertThat(requestMatchers.get(0)).isInstanceOf(MvcRequestMatcher.class);
|
||||
}
|
||||
|
||||
private void mockMvcIntrospector(boolean isPresent) {
|
||||
ApplicationContext context = this.matcherRegistry.getApplicationContext();
|
||||
given(context.containsBean("mvcHandlerMappingIntrospector")).willReturn(isPresent);
|
||||
|
||||
+34
@@ -42,6 +42,7 @@ import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.MockHttpSession;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
|
||||
@@ -59,6 +60,7 @@ import org.springframework.security.saml2.core.Saml2ErrorCodes;
|
||||
import org.springframework.security.saml2.core.Saml2Utils;
|
||||
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
|
||||
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
|
||||
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
|
||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
||||
@@ -353,6 +355,15 @@ public class Saml2LoginConfigurerTests {
|
||||
.andExpect(redirectedUrl("http://localhost/saml2/authenticate/registration-id"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saml2LoginWhenCustomAuthenticationProviderThenUses() throws Exception {
|
||||
this.spring.register(CustomAuthenticationProviderConfig.class).autowire();
|
||||
AuthenticationProvider provider = this.spring.getContext().getBean(AuthenticationProvider.class);
|
||||
this.mvc.perform(post("/login/saml2/sso/registration-id").param("SAMLResponse", SIGNED_RESPONSE))
|
||||
.andExpect(status().isFound());
|
||||
verify(provider).authenticate(any());
|
||||
}
|
||||
|
||||
private void performSaml2Login(String expected) throws IOException, ServletException {
|
||||
// setup authentication parameters
|
||||
this.request.setRequestURI("/login/saml2/sso/registration-id");
|
||||
@@ -663,6 +674,29 @@ public class Saml2LoginConfigurerTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
@Import(Saml2LoginConfigBeans.class)
|
||||
static class CustomAuthenticationProviderConfig {
|
||||
|
||||
private final OpenSaml4AuthenticationProvider provider = spy(new OpenSaml4AuthenticationProvider());
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain web(HttpSecurity http) throws Exception {
|
||||
http.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
|
||||
.saml2Login(Customizer.withDefaults());
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
AuthenticationProvider provider() {
|
||||
return this.provider;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class Saml2LoginConfigBeans {
|
||||
|
||||
@Bean
|
||||
|
||||
+7
-8
@@ -68,14 +68,14 @@ public abstract class AbstractAuthenticationToken implements Authentication, Cre
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
if (this.getPrincipal() instanceof UserDetails) {
|
||||
return ((UserDetails) this.getPrincipal()).getUsername();
|
||||
if (this.getPrincipal() instanceof UserDetails userDetails) {
|
||||
return userDetails.getUsername();
|
||||
}
|
||||
if (this.getPrincipal() instanceof AuthenticatedPrincipal) {
|
||||
return ((AuthenticatedPrincipal) this.getPrincipal()).getName();
|
||||
if (this.getPrincipal() instanceof AuthenticatedPrincipal authenticatedPrincipal) {
|
||||
return authenticatedPrincipal.getName();
|
||||
}
|
||||
if (this.getPrincipal() instanceof Principal) {
|
||||
return ((Principal) this.getPrincipal()).getName();
|
||||
if (this.getPrincipal() instanceof Principal principal) {
|
||||
return principal.getName();
|
||||
}
|
||||
return (this.getPrincipal() == null) ? "" : this.getPrincipal().toString();
|
||||
}
|
||||
@@ -119,10 +119,9 @@ public abstract class AbstractAuthenticationToken implements Authentication, Cre
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof AbstractAuthenticationToken)) {
|
||||
if (!(obj instanceof AbstractAuthenticationToken test)) {
|
||||
return false;
|
||||
}
|
||||
AbstractAuthenticationToken test = (AbstractAuthenticationToken) obj;
|
||||
if (!this.authorities.equals(test.authorities)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
+1
-2
@@ -74,8 +74,7 @@ public class AnonymousAuthenticationToken extends AbstractAuthenticationToken im
|
||||
if (!super.equals(obj)) {
|
||||
return false;
|
||||
}
|
||||
if (obj instanceof AnonymousAuthenticationToken) {
|
||||
AnonymousAuthenticationToken test = (AnonymousAuthenticationToken) obj;
|
||||
if (obj instanceof AnonymousAuthenticationToken test) {
|
||||
return (this.getKeyHash() == test.getKeyHash());
|
||||
}
|
||||
return false;
|
||||
|
||||
+2
-3
@@ -58,9 +58,8 @@ public final class JaasGrantedAuthority implements GrantedAuthority {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj instanceof JaasGrantedAuthority) {
|
||||
JaasGrantedAuthority jga = (JaasGrantedAuthority) obj;
|
||||
return this.role.equals(jga.role) && this.principal.equals(jga.principal);
|
||||
if (obj instanceof JaasGrantedAuthority jga) {
|
||||
return this.role.equals(jga.getAuthority()) && this.principal.equals(jga.getPrincipal());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* 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.
|
||||
@@ -50,8 +50,8 @@ public final class SimpleGrantedAuthority implements GrantedAuthority {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj instanceof SimpleGrantedAuthority) {
|
||||
return this.role.equals(((SimpleGrantedAuthority) obj).role);
|
||||
if (obj instanceof SimpleGrantedAuthority sga) {
|
||||
return this.role.equals(sga.getAuthority());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -179,8 +179,8 @@ public class User implements UserDetails, CredentialsContainer {
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof User) {
|
||||
return this.username.equals(((User) obj).username);
|
||||
if (obj instanceof User user) {
|
||||
return this.username.equals(user.getUsername());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -208,7 +208,7 @@ public class User implements UserDetails, CredentialsContainer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a UserBuilder with a specified user name
|
||||
* Creates a UserBuilder with a specified username
|
||||
* @param username the username to use
|
||||
* @return the UserBuilder
|
||||
*/
|
||||
|
||||
+4
-4
@@ -12,12 +12,12 @@ dependencies {
|
||||
api platform("io.rsocket:rsocket-bom:1.1.4")
|
||||
api platform("org.junit:junit-bom:5.9.3")
|
||||
api platform("org.mockito:mockito-bom:4.8.1")
|
||||
api platform("org.springframework.data:spring-data-bom:2022.0.8")
|
||||
api platform("org.springframework.data:spring-data-bom:2022.0.9")
|
||||
api platform("org.jetbrains.kotlin:kotlin-bom:$kotlinVersion")
|
||||
api platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4")
|
||||
api platform("com.fasterxml.jackson:jackson-bom:2.14.3")
|
||||
constraints {
|
||||
api "ch.qos.logback:logback-classic:1.4.8"
|
||||
api "ch.qos.logback:logback-classic:1.4.11"
|
||||
api "com.google.inject:guice:3.0"
|
||||
api "com.nimbusds:nimbus-jose-jwt:9.31"
|
||||
api "com.nimbusds:oauth2-oidc-sdk:9.43.3"
|
||||
@@ -25,7 +25,7 @@ dependencies {
|
||||
api "com.squareup.okhttp3:okhttp:3.14.9"
|
||||
api "com.unboundid:unboundid-ldapsdk:6.0.9"
|
||||
api "commons-collections:commons-collections:3.2.2"
|
||||
api "io.mockk:mockk:1.13.5"
|
||||
api "io.mockk:mockk:1.13.7"
|
||||
api "io.micrometer:micrometer-observation:$micrometerVersion"
|
||||
api "jakarta.annotation:jakarta.annotation-api:2.1.1"
|
||||
api "jakarta.inject:jakarta.inject-api:2.0.1"
|
||||
@@ -67,7 +67,7 @@ dependencies {
|
||||
api "org.skyscreamer:jsonassert:1.5.1"
|
||||
api "org.slf4j:log4j-over-slf4j:1.7.36"
|
||||
api "org.slf4j:slf4j-api:2.0.7"
|
||||
api "org.springframework.ldap:spring-ldap-core:3.0.4"
|
||||
api "org.springframework.ldap:spring-ldap-core:3.0.5"
|
||||
api "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
|
||||
api 'org.apache.maven.resolver:maven-resolver-connector-basic:1.8.2'
|
||||
api 'org.apache.maven.resolver:maven-resolver-impl:1.8.2'
|
||||
|
||||
@@ -25,7 +25,7 @@ Without proper configuration, the application server can not know that the load
|
||||
|
||||
To fix this, you can use https://tools.ietf.org/html/rfc7239[RFC 7239] to specify that a load balancer is being used.
|
||||
To make the application aware of this, you need to configure your application server to be aware of the X-Forwarded headers.
|
||||
For example, Tomcat uses https://tomcat.apache.org/tomcat-8.0-doc/api/org/apache/catalina/valves/RemoteIpValve.html[`RemoteIpValve`] and Jetty uses https://download.eclipse.org/jetty/stable-9/apidocs/org/eclipse/jetty/server/ForwardedRequestCustomizer.html[`ForwardedRequestCustomizer`].
|
||||
For example, Tomcat uses https://tomcat.apache.org/tomcat-10.1-doc/api/org/apache/catalina/valves/RemoteIpValve.html[`RemoteIpValve`] and Jetty uses https://eclipse.dev/jetty/javadoc/jetty-11/org/eclipse/jetty/server/ForwardedRequestCustomizer.html[`ForwardedRequestCustomizer`].
|
||||
Alternatively, Spring users can use https://github.com/spring-projects/spring-framework/blob/v4.3.3.RELEASE/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java[`ForwardedHeaderFilter`].
|
||||
|
||||
Spring Boot users can use the `server.use-forward-headers` property to configure the application.
|
||||
|
||||
@@ -191,7 +191,7 @@ Alternatively, you can manually add the starter:
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
dependencies {
|
||||
compile "org.springframework.boot:spring-boot-starter-security"
|
||||
implementation "org.springframework.boot:spring-boot-starter-security"
|
||||
}
|
||||
----
|
||||
|
||||
@@ -245,8 +245,8 @@ A minimal Spring Security Maven set of dependencies typically looks like the fol
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
dependencies {
|
||||
compile "org.springframework.security:spring-security-web"
|
||||
compile "org.springframework.security:spring-security-config"
|
||||
implementation "org.springframework.security:spring-security-web"
|
||||
implementation "org.springframework.security:spring-security-config"
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
@@ -448,8 +448,8 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
[[webflux-headers-referrer]]
|
||||
== Referrer Policy
|
||||
|
||||
By default, Spring Security does not add xref:features/exploits/headers.adoc#headers-referrer[Referrer Policy] headers.
|
||||
You can enable the Referrer Policy header using configuration as shown below:
|
||||
Spring Security adds the xref:features/exploits/headers.adoc#headers-referrer[Referrer Policy] header by default with the directive `no-referrer`.
|
||||
You can change the Referrer Policy header using configuration as shown below:
|
||||
|
||||
.Referrer Policy Configuration
|
||||
[tabs]
|
||||
|
||||
@@ -7,7 +7,7 @@ Without proper configuration, the application server will not know that the load
|
||||
|
||||
To fix this you can use https://tools.ietf.org/html/rfc7239[RFC 7239] to specify that a load balancer is being used.
|
||||
To make the application aware of this, you need to either configure your application server aware of the X-Forwarded headers.
|
||||
For example Tomcat uses the https://tomcat.apache.org/tomcat-8.0-doc/api/org/apache/catalina/valves/RemoteIpValve.html[RemoteIpValve] and Jetty uses https://www.eclipse.org/jetty/javadoc/jetty-9/org/eclipse/jetty/server/ForwardedRequestCustomizer.html[ForwardedRequestCustomizer].
|
||||
For example Tomcat uses the https://tomcat.apache.org/tomcat-10.1-doc/api/org/apache/catalina/valves/RemoteIpValve.html[RemoteIpValve] and Jetty uses https://eclipse.dev/jetty/javadoc/jetty-11/org/eclipse/jetty/server/ForwardedRequestCustomizer.html[ForwardedRequestCustomizer].
|
||||
Alternatively, Spring 4.3+ users can leverage https://github.com/spring-projects/spring-framework/blob/v4.3.3.RELEASE/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java[ForwardedHeaderFilter].
|
||||
|
||||
Spring Boot users may use the `server.use-forward-headers` property to configure the application.
|
||||
|
||||
@@ -157,7 +157,39 @@ XML::
|
||||
|
||||
The following beans are required in an application context to enable remember-me services:
|
||||
|
||||
[source,xml]
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
RememberMeAuthenticationFilter rememberMeFilter() {
|
||||
RememberMeAuthenticationFilter rememberMeFilter = new RememberMeAuthenticationFilter();
|
||||
rememberMeFilter.setRememberMeServices(rememberMeServices());
|
||||
rememberMeFilter.setAuthenticationManager(theAuthenticationManager);
|
||||
return rememberMeFilter;
|
||||
}
|
||||
|
||||
@Bean
|
||||
TokenBasedRememberMeServices rememberMeServices() {
|
||||
TokenBasedRememberMeServices rememberMeServices = new TokenBasedRememberMeServices();
|
||||
rememberMeServices.setUserDetailsService(myUserDetailsService);
|
||||
rememberMeServices.setKey("springRocks");
|
||||
return rememberMeServices;
|
||||
}
|
||||
|
||||
@Bean
|
||||
RememberMeAuthenticationProvider rememberMeAuthenticationProvider() {
|
||||
RememberMeAuthenticationProvider rememberMeAuthenticationProvider = new RememberMeAuthenticationProvider();
|
||||
rememberMeAuthenticationProvider.setKey("springRocks");
|
||||
return rememberMeAuthenticationProvider;
|
||||
}
|
||||
----
|
||||
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<bean id="rememberMeFilter" class=
|
||||
"org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
|
||||
@@ -176,6 +208,7 @@ The following beans are required in an application context to enable remember-me
|
||||
<property name="key" value="springRocks"/>
|
||||
</bean>
|
||||
----
|
||||
======
|
||||
|
||||
Remember to add your `RememberMeServices` implementation to your `UsernamePasswordAuthenticationFilter.setRememberMeServices()` property, include the `RememberMeAuthenticationProvider` in your `AuthenticationManager.setProviders()` list, and add `RememberMeAuthenticationFilter` into your `FilterChainProxy` (typically immediately after your `UsernamePasswordAuthenticationFilter`).
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ It maps the certificate to an application user and loads that user's set of gran
|
||||
|
||||
You can also use SSL with "`mutual authentication`". The server then requests a valid certificate from the client as part of the SSL handshake.
|
||||
The server authenticates the client by checking that its certificate is signed by an acceptable authority.
|
||||
For example, if you use Tomcat, you should read the https://tomcat.apache.org/tomcat-9.0-doc/ssl-howto.html[Tomcat SSL instructions].
|
||||
For example, if you use Tomcat, you should read the https://tomcat.apache.org/tomcat-10.1-doc/ssl-howto.html[Tomcat SSL instructions].
|
||||
You should get this working before trying it out with Spring Security.
|
||||
|
||||
|
||||
|
||||
@@ -253,10 +253,11 @@ Java::
|
||||
----
|
||||
@Bean
|
||||
static RoleHierarchy roleHierarchy() {
|
||||
var hierarchy = new RoleHierarchyImpl();
|
||||
RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
|
||||
hierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF\n" +
|
||||
"ROLE_STAFF > ROLE_USER\n" +
|
||||
"ROLE_USER > ROLE_GUEST");
|
||||
return hierarchy;
|
||||
}
|
||||
|
||||
// and, if using method security also add
|
||||
|
||||
@@ -52,6 +52,7 @@ In many cases, your authorization rules will be more sophisticated than that, so
|
||||
* I have an app that uses `authorizeRequests` and I want to <<migrate-authorize-requests,migrate it to `authorizeHttpRequests`>>
|
||||
* I want to <<request-authorization-architecture,understand how the `AuthorizationFilter` components work>>
|
||||
* I want to <<match-requests, match requests>> based on a pattern; specifically <<match-by-regex,regex>>
|
||||
* I want to match request, and I map Spring MVC to <<mvc-not-default-servlet, something other than the default servlet>>
|
||||
* I want to <<authorize-requests, authorize requests>>
|
||||
* I want to <<match-by-custom, match a request programmatically>>
|
||||
* I want to <<authorize-requests, authorize a request programmatically>>
|
||||
@@ -561,8 +562,8 @@ http
|
||||
----
|
||||
http {
|
||||
authorizeHttpRequests {
|
||||
authorize(DispatcherType.FORWARD, permitAll)
|
||||
authorize(DispatcherType.ERROR, permitAll)
|
||||
authorize(DispatcherTypeRequestMatcher(DispatcherType.FORWARD), permitAll)
|
||||
authorize(DispatcherTypeRequestMatcher(DispatcherType.ERROR), permitAll)
|
||||
authorize("/endpoint", permitAll)
|
||||
authorize(anyRequest, denyAll)
|
||||
}
|
||||
@@ -570,6 +571,71 @@ http {
|
||||
----
|
||||
====
|
||||
|
||||
[[match-by-mvc]]
|
||||
=== Using an MvcRequestMatcher
|
||||
|
||||
Generally speaking, you can use `requestMatchers(String)` as demonstrated above.
|
||||
|
||||
However, if you map Spring MVC to a different servlet path, then you need to account for that in your security configuration.
|
||||
|
||||
For example, if Spring MVC is mapped to `/spring-mvc` instead of `/` (the default), then you may have an endpoint like `/spring-mvc/my/controller` that you want to authorize.
|
||||
|
||||
You need to use `MvcRequestMatcher` to split the servlet path and the controller path in your configuration like so:
|
||||
|
||||
.Match by MvcRequestMatcher
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) {
|
||||
return new MvcRequestMatcher.Builder(introspector).servletPath("/spring-mvc");
|
||||
}
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain appEndpoints(HttpSecurity http, MvcRequestMatcher.Builder mvc) {
|
||||
http
|
||||
.authorizeHttpRequests((authorize) -> authorize
|
||||
.requestMatchers(mvc.pattern("/my/controller/**")).hasAuthority("controller")
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun mvc(introspector: HandlerMappingIntrospector): MvcRequestMatcher.Builder =
|
||||
MvcRequestMatcher.Builder(introspector).servletPath("/spring-mvc");
|
||||
|
||||
@Bean
|
||||
fun appEndpoints(http: HttpSecurity, mvc: MvcRequestMatcher.Builder): SecurityFilterChain =
|
||||
http {
|
||||
authorizeHttpRequests {
|
||||
authorize(mvc.pattern("/my/controller/**"), hasAuthority("controller"))
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
<intercept-url servlet-path="/spring-mvc" pattern="/my/controller/**" access="hasAuthority('controller')"/>
|
||||
<intercept-url pattern="/**" access="authenticated"/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
|
||||
This need can arise in at least two different ways:
|
||||
|
||||
* If you use the `spring.mvc.servlet.path` Boot property to change the default path (`/`) to something else
|
||||
* If you register more than one Spring MVC `DispatcherServlet` (thus requiring that one of them not be the default path)
|
||||
|
||||
[[match-by-custom]]
|
||||
=== Using a Custom Matcher
|
||||
|
||||
|
||||
@@ -286,7 +286,7 @@ public class Config {
|
||||
|
||||
The code is invoked in the following order:
|
||||
|
||||
* Code in the `Config.configure` method is invoked
|
||||
* Code in the `Config.filterChain` method is invoked
|
||||
* Code in the `MyCustomDsl.init` method is invoked
|
||||
* Code in the `MyCustomDsl.configure` method is invoked
|
||||
|
||||
|
||||
@@ -241,16 +241,15 @@ fun rest(): RestTemplate {
|
||||
val rest = RestTemplate()
|
||||
rest.interceptors.add(ClientHttpRequestInterceptor { request, body, execution ->
|
||||
val authentication: Authentication? = SecurityContextHolder.getContext().authentication
|
||||
if (authentication != null) {
|
||||
execution.execute(request, body)
|
||||
if (authentication == null) {
|
||||
return execution.execute(request, body)
|
||||
}
|
||||
|
||||
if (authentication!!.credentials !is AbstractOAuth2Token) {
|
||||
execution.execute(request, body)
|
||||
if (authentication.credentials !is AbstractOAuth2Token) {
|
||||
return execution.execute(request, body)
|
||||
}
|
||||
|
||||
val token: AbstractOAuth2Token = authentication.credentials as AbstractOAuth2Token
|
||||
request.headers.setBearerAuth(token.tokenValue)
|
||||
request.headers.setBearerAuth(authentication.credentials.tokenValue)
|
||||
execution.execute(request, body)
|
||||
})
|
||||
return rest
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
id 'org.antora' version '1.0.0'
|
||||
id 'io.spring.antora.generate-antora-yml' version '0.0.1'
|
||||
id 'io.spring.convention.repository'
|
||||
}
|
||||
|
||||
apply plugin: 'io.spring.convention.docs'
|
||||
@@ -61,10 +62,3 @@ def resolvedVersions(Configuration configuration) {
|
||||
.resolvedArtifacts
|
||||
.collectEntries { [(it.name + '-version'): it.moduleVersion.id.version] }
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url 'https://repo.spring.io/release' }
|
||||
maven { url 'https://repo.spring.io/milestone' }
|
||||
maven { url 'https://repo.spring.io/snapshot' }
|
||||
}
|
||||
|
||||
+4
-4
@@ -1,11 +1,11 @@
|
||||
aspectjVersion=1.9.19
|
||||
reactorVersion=2022.0.9
|
||||
aspectjVersion=1.9.20
|
||||
reactorVersion=2022.0.10
|
||||
springJavaformatVersion=0.0.39
|
||||
springBootVersion=3.1.1
|
||||
springFrameworkVersion=6.0.11
|
||||
micrometerVersion=1.10.9
|
||||
micrometerVersion=1.10.10
|
||||
openSamlVersion=4.1.1
|
||||
version=6.1.2
|
||||
version=6.1.4
|
||||
kotlinVersion=1.8.22
|
||||
samplesBranch=main
|
||||
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* 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.
|
||||
@@ -113,14 +113,13 @@ public class LdapAuthority implements GrantedAuthority {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof LdapAuthority)) {
|
||||
if (!(obj instanceof LdapAuthority other)) {
|
||||
return false;
|
||||
}
|
||||
LdapAuthority other = (LdapAuthority) obj;
|
||||
if (!this.dn.equals(other.dn)) {
|
||||
if (!this.dn.equals(other.getDn())) {
|
||||
return false;
|
||||
}
|
||||
return this.role.equals(other.role);
|
||||
return this.role.equals(other.getAuthority());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+9
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* 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.
|
||||
@@ -30,16 +30,23 @@ import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
* {@link OAuth2AuthenticationException}.
|
||||
*
|
||||
* @author Dennis Neufeld
|
||||
* @author Steve Riesenberg
|
||||
* @since 5.3.4
|
||||
* @see OAuth2AuthenticationException
|
||||
* @see OAuth2ClientJackson2Module
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true, value = { "cause", "stackTrace", "suppressedExceptions" })
|
||||
abstract class OAuth2AuthenticationExceptionMixin {
|
||||
|
||||
@JsonProperty("error")
|
||||
abstract OAuth2Error getError();
|
||||
|
||||
@JsonProperty("detailMessage")
|
||||
abstract String getMessage();
|
||||
|
||||
@JsonCreator
|
||||
OAuth2AuthenticationExceptionMixin(@JsonProperty("error") OAuth2Error error,
|
||||
@JsonProperty("detailMessage") String message) {
|
||||
|
||||
+8
-2
@@ -34,10 +34,16 @@ import org.springframework.security.saml2.provider.service.authentication.Saml2A
|
||||
* @see Saml2Jackson2Module
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true, value = { "cause", "stackTrace", "suppressedExceptions" })
|
||||
class Saml2AuthenticationExceptionMixin {
|
||||
abstract class Saml2AuthenticationExceptionMixin {
|
||||
|
||||
@JsonProperty("error")
|
||||
abstract Saml2Error getSaml2Error();
|
||||
|
||||
@JsonProperty("detailMessage")
|
||||
abstract String getMessage();
|
||||
|
||||
@JsonCreator
|
||||
Saml2AuthenticationExceptionMixin(@JsonProperty("error") Saml2Error error,
|
||||
|
||||
+10
-14
@@ -19,8 +19,6 @@ package org.springframework.security.saml2.provider.service.metadata;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
@@ -126,21 +124,19 @@ public final class RequestMatcherMetadataResponseResolver implements Saml2Metada
|
||||
Iterable<RelyingPartyRegistration> registrations) {
|
||||
Map<String, RelyingPartyRegistration> results = new LinkedHashMap<>();
|
||||
for (RelyingPartyRegistration registration : registrations) {
|
||||
results.put(registration.getEntityId(), registration);
|
||||
}
|
||||
Collection<RelyingPartyRegistration> resolved = new ArrayList<>();
|
||||
for (RelyingPartyRegistration registration : results.values()) {
|
||||
UriResolver uriResolver = RelyingPartyRegistrationPlaceholderResolvers.uriResolver(request, registration);
|
||||
String entityId = uriResolver.resolve(registration.getEntityId());
|
||||
String ssoLocation = uriResolver.resolve(registration.getAssertionConsumerServiceLocation());
|
||||
String sloLocation = uriResolver.resolve(registration.getSingleLogoutServiceLocation());
|
||||
String sloResponseLocation = uriResolver.resolve(registration.getSingleLogoutServiceResponseLocation());
|
||||
resolved.add(registration.mutate().entityId(entityId).assertionConsumerServiceLocation(ssoLocation)
|
||||
.singleLogoutServiceLocation(sloLocation).singleLogoutServiceResponseLocation(sloResponseLocation)
|
||||
.build());
|
||||
results.computeIfAbsent(entityId, (e) -> {
|
||||
String ssoLocation = uriResolver.resolve(registration.getAssertionConsumerServiceLocation());
|
||||
String sloLocation = uriResolver.resolve(registration.getSingleLogoutServiceLocation());
|
||||
String sloResponseLocation = uriResolver.resolve(registration.getSingleLogoutServiceResponseLocation());
|
||||
return registration.mutate().entityId(entityId).assertionConsumerServiceLocation(ssoLocation)
|
||||
.singleLogoutServiceLocation(sloLocation)
|
||||
.singleLogoutServiceResponseLocation(sloResponseLocation).build();
|
||||
});
|
||||
}
|
||||
String metadata = this.metadata.resolve(resolved);
|
||||
String value = (resolved.size() == 1) ? resolved.iterator().next().getRegistrationId()
|
||||
String metadata = this.metadata.resolve(results.values());
|
||||
String value = (results.size() == 1) ? results.values().iterator().next().getRegistrationId()
|
||||
: UUID.randomUUID().toString();
|
||||
String fileName = this.filename.replace("{registrationId}", value);
|
||||
try {
|
||||
|
||||
+20
-2
@@ -20,6 +20,7 @@ import java.util.Collection;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
@@ -101,6 +102,23 @@ public final class RequestMatcherMetadataResponseResolverTests {
|
||||
assertThat(resolver.resolve(new MockHttpServletRequest())).isNull();
|
||||
}
|
||||
|
||||
// gh-13700
|
||||
@Test
|
||||
void resolveWhenNoRegistrationIdThenResolvesEntityIds() {
|
||||
RelyingPartyRegistration one = withEntityId("one");
|
||||
RelyingPartyRegistration two = withEntityId("two");
|
||||
RelyingPartyRegistrationRepository registrations = new InMemoryRelyingPartyRegistrationRepository(one, two);
|
||||
RequestMatcherMetadataResponseResolver resolver = new RequestMatcherMetadataResponseResolver(registrations,
|
||||
this.metadataFactory);
|
||||
given(this.metadataFactory.resolve(any(Collection.class))).willReturn("metadata");
|
||||
resolver.resolve(get("/saml2/metadata"));
|
||||
ArgumentCaptor<Collection<RelyingPartyRegistration>> captor = ArgumentCaptor.forClass(Collection.class);
|
||||
verify(this.metadataFactory).resolve(captor.capture());
|
||||
Collection<RelyingPartyRegistration> resolved = captor.getValue();
|
||||
assertThat(resolved).hasSize(2);
|
||||
assertThat(resolved.iterator().next().getEntityId()).isEqualTo("one");
|
||||
}
|
||||
|
||||
private MockHttpServletRequest get(String uri) {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", uri);
|
||||
request.setServletPath(uri);
|
||||
@@ -108,8 +126,8 @@ public final class RequestMatcherMetadataResponseResolverTests {
|
||||
}
|
||||
|
||||
private RelyingPartyRegistration withEntityId(String entityId) {
|
||||
return TestRelyingPartyRegistrations.relyingPartyRegistration().registrationId(entityId).entityId(entityId)
|
||||
.build();
|
||||
return TestRelyingPartyRegistrations.relyingPartyRegistration().registrationId(entityId)
|
||||
.entityId("{registrationId}").build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-3
@@ -64,9 +64,8 @@ public final class SwitchUserGrantedAuthority implements GrantedAuthority {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj instanceof SwitchUserGrantedAuthority) {
|
||||
SwitchUserGrantedAuthority swa = (SwitchUserGrantedAuthority) obj;
|
||||
return this.role.equals(swa.role) && this.source.equals(swa.source);
|
||||
if (obj instanceof SwitchUserGrantedAuthority swa) {
|
||||
return this.role.equals(swa.getAuthority()) && this.source.equals(swa.getSource());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ public class BasicAuthenticationEntryPoint implements AuthenticationEntryPoint,
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response,
|
||||
AuthenticationException authException) throws IOException {
|
||||
response.addHeader("WWW-Authenticate", "Basic realm=\"" + this.realmName + "\"");
|
||||
response.setHeader("WWW-Authenticate", "Basic realm=\"" + this.realmName + "\"");
|
||||
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -178,7 +178,7 @@ public final class CookieCsrfTokenRepository implements CsrfTokenRepository {
|
||||
*/
|
||||
public static CookieCsrfTokenRepository withHttpOnlyFalse() {
|
||||
CookieCsrfTokenRepository result = new CookieCsrfTokenRepository();
|
||||
result.setCookieCustomizer((cookie) -> cookie.httpOnly(false));
|
||||
result.cookieHttpOnly = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -127,10 +127,9 @@ public class JaasApiIntegrationFilter extends GenericFilterBean {
|
||||
if (!authentication.isAuthenticated()) {
|
||||
return null;
|
||||
}
|
||||
if (!(authentication instanceof JaasAuthenticationToken)) {
|
||||
if (!(authentication instanceof JaasAuthenticationToken token)) {
|
||||
return null;
|
||||
}
|
||||
JaasAuthenticationToken token = (JaasAuthenticationToken) authentication;
|
||||
LoginContext loginContext = token.getLoginContext();
|
||||
if (loginContext == null) {
|
||||
return null;
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* 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.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.security.web.savedrequest;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@@ -77,7 +78,7 @@ public class CookieRequestCache implements RequestCache {
|
||||
int port = getPort(uriComponents);
|
||||
return builder.setScheme(uriComponents.getScheme()).setServerName(uriComponents.getHost())
|
||||
.setRequestURI(uriComponents.getPath()).setQueryString(uriComponents.getQuery()).setServerPort(port)
|
||||
.setMethod(request.getMethod()).build();
|
||||
.setMethod(request.getMethod()).setLocales(Collections.list(request.getLocales())).build();
|
||||
}
|
||||
|
||||
private int getPort(UriComponents uriComponents) {
|
||||
|
||||
+10
-5
@@ -28,6 +28,8 @@ import org.springframework.security.web.PortResolverImpl;
|
||||
import org.springframework.security.web.util.UrlUtils;
|
||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* {@code RequestCache} which stores the {@code SavedRequest} in the HttpSession.
|
||||
@@ -100,11 +102,14 @@ public class HttpSessionRequestCache implements RequestCache {
|
||||
|
||||
@Override
|
||||
public HttpServletRequest getMatchingRequest(HttpServletRequest request, HttpServletResponse response) {
|
||||
if (this.matchingRequestParameterName != null
|
||||
&& request.getParameter(this.matchingRequestParameterName) == null) {
|
||||
this.logger.trace(
|
||||
"matchingRequestParameterName is required for getMatchingRequest to lookup a value, but not provided");
|
||||
return null;
|
||||
if (this.matchingRequestParameterName != null) {
|
||||
if (!StringUtils.hasText(request.getQueryString())
|
||||
|| !UriComponentsBuilder.fromUriString(UrlUtils.buildRequestUrl(request)).build().getQueryParams()
|
||||
.containsKey(this.matchingRequestParameterName)) {
|
||||
this.logger.trace(
|
||||
"matchingRequestParameterName is required for getMatchingRequest to lookup a value, but not provided");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
SavedRequest saved = getRequest(request, response);
|
||||
if (saved == null) {
|
||||
|
||||
+19
@@ -16,8 +16,12 @@
|
||||
|
||||
package org.springframework.security.web.authentication.www;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
@@ -61,4 +65,19 @@ public class BasicAuthenticationEntryPointTests {
|
||||
assertThat(response.getHeader("WWW-Authenticate")).isEqualTo("Basic realm=\"hello\"");
|
||||
}
|
||||
|
||||
// gh-13737
|
||||
@Test
|
||||
void commenceWhenResponseHasHeaderThenOverride() throws IOException {
|
||||
BasicAuthenticationEntryPoint ep = new BasicAuthenticationEntryPoint();
|
||||
ep.setRealmName("hello");
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setRequestURI("/some_path");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"test\"");
|
||||
ep.commence(request, response, new DisabledException("Disabled"));
|
||||
List<String> headers = response.getHeaders("WWW-Authenticate");
|
||||
assertThat(headers).hasSize(1);
|
||||
assertThat(headers.get(0)).isEqualTo("Basic realm=\"hello\"");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+13
@@ -423,6 +423,19 @@ class CookieCsrfTokenRepositoryTests {
|
||||
assertThat(((MockCookie) tokenCookie).getSameSite()).isEqualTo(sameSitePolicy);
|
||||
}
|
||||
|
||||
// gh-13659
|
||||
@Test
|
||||
void withHttpOnlyFalseWhenCookieCustomizerThenStillDefaultsToFalse() {
|
||||
CookieCsrfTokenRepository repository = CookieCsrfTokenRepository.withHttpOnlyFalse();
|
||||
repository.setCookieCustomizer((customizer) -> customizer.maxAge(1000));
|
||||
CsrfToken token = repository.generateToken(this.request);
|
||||
repository.saveToken(token, this.request, this.response);
|
||||
Cookie tokenCookie = this.response.getCookie(CookieCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME);
|
||||
assertThat(tokenCookie).isNotNull();
|
||||
assertThat(tokenCookie.getMaxAge()).isEqualTo(1000);
|
||||
assertThat(tokenCookie.isHttpOnly()).isEqualTo(Boolean.FALSE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void setCookieNameNullIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setCookieName(null));
|
||||
|
||||
+23
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,7 +16,10 @@
|
||||
|
||||
package org.springframework.security.web.savedrequest;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@@ -182,6 +185,25 @@ public class CookieRequestCacheTests {
|
||||
assertThat(expiredCookie.getMaxAge()).isZero();
|
||||
}
|
||||
|
||||
// gh-13792
|
||||
@Test
|
||||
public void matchingRequestWhenMatchThenKeepOriginalRequestLocale() {
|
||||
CookieRequestCache cookieRequestCache = new CookieRequestCache();
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setServerPort(443);
|
||||
request.setSecure(true);
|
||||
request.setScheme("https");
|
||||
request.setServerName("example.com");
|
||||
request.setRequestURI("/destination");
|
||||
request.setPreferredLocales(Arrays.asList(Locale.FRENCH, Locale.GERMANY));
|
||||
String redirectUrl = "https://example.com/destination";
|
||||
request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, encodeCookie(redirectUrl)));
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
HttpServletRequest matchingRequest = cookieRequestCache.getMatchingRequest(request, response);
|
||||
assertThat(matchingRequest).isNotNull();
|
||||
assertThat(Collections.list(matchingRequest.getLocales())).contains(Locale.FRENCH, Locale.GERMANY);
|
||||
}
|
||||
|
||||
private static String encodeCookie(String cookieValue) {
|
||||
return Base64.getEncoder().encodeToString(cookieValue.getBytes());
|
||||
}
|
||||
|
||||
+19
-5
@@ -32,8 +32,9 @@ import org.springframework.security.web.PortResolverImpl;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
@@ -100,7 +101,7 @@ public class HttpSessionRequestCacheTests {
|
||||
public void getMatchingRequestWhenMatchingRequestParameterNameSetThenSessionNotAccessed() {
|
||||
HttpSessionRequestCache cache = new HttpSessionRequestCache();
|
||||
cache.setMatchingRequestParameterName("success");
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
HttpServletRequest request = spy(new MockHttpServletRequest());
|
||||
HttpServletRequest matchingRequest = cache.getMatchingRequest(request, new MockHttpServletResponse());
|
||||
assertThat(matchingRequest).isNull();
|
||||
verify(request, never()).getSession();
|
||||
@@ -115,7 +116,6 @@ public class HttpSessionRequestCacheTests {
|
||||
cache.saveRequest(request, new MockHttpServletResponse());
|
||||
MockHttpServletRequest requestToMatch = new MockHttpServletRequest();
|
||||
requestToMatch.setQueryString("success"); // gh-12665
|
||||
requestToMatch.setParameter("success", "");
|
||||
requestToMatch.setSession(request.getSession());
|
||||
HttpServletRequest matchingRequest = cache.getMatchingRequest(requestToMatch, new MockHttpServletResponse());
|
||||
assertThat(matchingRequest).isNotNull();
|
||||
@@ -131,7 +131,6 @@ public class HttpSessionRequestCacheTests {
|
||||
cache.saveRequest(request, new MockHttpServletResponse());
|
||||
MockHttpServletRequest requestToMatch = new MockHttpServletRequest();
|
||||
requestToMatch.setQueryString("param=true&success");
|
||||
requestToMatch.setParameter("success", "");
|
||||
requestToMatch.setSession(request.getSession());
|
||||
HttpServletRequest matchingRequest = cache.getMatchingRequest(requestToMatch, new MockHttpServletResponse());
|
||||
assertThat(matchingRequest).isNotNull();
|
||||
@@ -146,13 +145,28 @@ public class HttpSessionRequestCacheTests {
|
||||
assertThat(request.getSession().getAttribute(HttpSessionRequestCache.SAVED_REQUEST)).isNotNull();
|
||||
MockHttpServletRequest requestToMatch = new MockHttpServletRequest();
|
||||
requestToMatch.setQueryString("success");
|
||||
requestToMatch.setParameter("success", "");
|
||||
requestToMatch.setSession(request.getSession());
|
||||
HttpServletRequest matchingRequest = cache.getMatchingRequest(requestToMatch, new MockHttpServletResponse());
|
||||
assertThat(matchingRequest).isNotNull();
|
||||
assertThat(request.getSession().getAttribute(HttpSessionRequestCache.SAVED_REQUEST)).isNull();
|
||||
}
|
||||
|
||||
// gh-13731
|
||||
@Test
|
||||
public void getMatchingRequestWhenMatchingRequestParameterNameSetThenDoesNotInvokeGetParameterMethods() {
|
||||
HttpSessionRequestCache cache = new HttpSessionRequestCache();
|
||||
cache.setMatchingRequestParameterName("success");
|
||||
MockHttpServletRequest mockRequest = new MockHttpServletRequest();
|
||||
mockRequest.setQueryString("success");
|
||||
HttpServletRequest request = spy(mockRequest);
|
||||
HttpServletRequest matchingRequest = cache.getMatchingRequest(request, new MockHttpServletResponse());
|
||||
assertThat(matchingRequest).isNull();
|
||||
verify(request, never()).getParameter(anyString());
|
||||
verify(request, never()).getParameterValues(anyString());
|
||||
verify(request, never()).getParameterNames();
|
||||
verify(request, never()).getParameterMap();
|
||||
}
|
||||
|
||||
private static final class CustomSavedRequest implements SavedRequest {
|
||||
|
||||
private final SavedRequest delegate;
|
||||
|
||||
Reference in New Issue
Block a user